725 Commits
Author SHA1 Message Date
Ulises Vargas 4348e89860 chore: prepare for deployment
Publish Docker Images / Smoke test (api) (push) Canceled after 0s
Publish Docker Images / Smoke test (dashboard) (push) Canceled after 0s
Publish Docker Images / Smoke test (jobs) (push) Canceled after 0s
Publish Docker Images / build-and-push (./docker/Dockerfile.api, api, api) (push) Canceled after 0s
Publish Docker Images / build-and-push (./docker/Dockerfile.dashboard, dashboard, dashboard) (push) Canceled after 0s
Publish Docker Images / build-and-push (./docker/Dockerfile.jobs, jobs, jobs) (push) Canceled after 0s
MCP package build check / build (push) Canceled after 0s
Package build check / build (push) Canceled after 0s
Upload OpenAPI Spec to R2 / generate-and-upload-openapi (push) Canceled after 0s
2026-08-24 08:56:48 -07:00
Rotimi BestandRotimi Best 44f09bbe6b fix(course): restore mobile mark complete and outline progress card (#942)
* fix(course): pass progress into mobile outline sheet card

The outline sheet bound CourseProgressCard with {courseProgress}, which
sets a courseProgress prop the card does not read. progress was
undefined, so opening the sheet crashed on progress.percent. Pass
progress={courseProgress} and treat missing progress as zeros.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(course): keep mark complete on mobile student lessons

The mobile bottom nav hid the whole lesson action row, so students had no way to mark a lesson complete. Hide only prev/next when the bottom bar is shown.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(course): show a check on the right of a completed lesson title

When a student marks a lesson complete, hide the disabled Mark as Complete
button and place a filled check icon on the right of the lesson title.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(course): show completion check on mobile bottom-nav title

Move the filled check from the lesson page heading to the right of
the active lesson title in the mobile bottom bar.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

---------
2026-08-20 18:03:03 +01:00
Rotimi Best 4fae088626 fix(course): stop mobile sidebar crash when path is undefined (#934)
The course layout treated `path` as a SvelteKit prop, but parents never
pass it. CourseMobileBottomNav then called isContentItemInPath with
undefined, which crashed on `.split()` and prevented the mobile sidebar
from opening. Use page.url.pathname and guard missing paths.
2026-08-19 13:24:20 +01:00
Rotimi BestandRotimi Best 55ae6dd4fe feat(dashboard): improve student course sidebar navigation (#814)
* docs(prototypes): add course sidebar navigation HTML prototypes

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* docs(prototypes): add downloadable zip for course sidebar mockups

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* feat(dashboard): improve student course sidebar navigation

Collapse all sections except the active one for students, show section
completion as counts (e.g. 3/4), and replace the footer progress card with
prev/next controls plus a thin progress bar.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(dashboard): address PR review and remove sidebar prototypes

Remove HTML prototype files from the PR. Match content IDs as exact path
segments for active-item resolution, and use content-neutral prev/next labels.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(dashboard): keep ClassroomIO brand untranslated in powered-by footer

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(dashboard): restore progress summary in course sidebar footer

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(dashboard): move progress bar below summary in sidebar footer

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* feat(course): add mobile student bottom nav with outline sheet

Implement prototype 3 mobile navigation for course learners: a fixed
bottom bar with progress ring, prev/next controls, and a sheet showing
the progress card plus collapsible course outline with scroll-to-active.

Extends public-course UI primitives (ring bottom nav, collapsible
sidebar sections, mobile sheet progress slot) and wires them into the
authenticated course layout for isCourseLearnerView on lesson/exercise
routes. Hides duplicate header prev/next and progress popover on mobile.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(course): address review feedback and exclude prototypes

- Add .coderabbit.yaml and .greptile/config.json to skip prototypes/**
- Document prototypes/ as out-of-scope in AGENTS.md and Greptile rules
- Render locked mobile outline rows without href navigation targets
- Replace nested IconButton in collapsible section trigger with chevron icon
- Reset public-course sidebar section collapse on every mobile sheet reopen

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

---------
2026-08-19 11:52:58 +01:00
Rotimi Best bf3a7e7057 fix(dashboard): hide compliance in-progress badge and enforce deadline on view-site publish (#913)
* fix(dashboard): hide in-progress badge on compliance learner cards

Keep the due date on My Learning and LMS current-course cards, and only
show compliance status badges for states that need attention.

Co-authored-by: best <best@classroomio.com>

* fix(dashboard): enforce compliance deadline when publishing from view course site

Reuse the settings deadline-required dialog and navigate to the completion
deadline section so unpublished-site publish matches settings and the API.

Co-authored-by: best <best@classroomio.com>

* fix(dashboard): keep attention highlight visible after deadline navigation

Prevent goto and dialog focus restoration from scrolling away from the
completion deadline section before the highlight pulse can render.

Co-authored-by: best <best@classroomio.com>

* fix(dashboard): keep deadline attention highlight visible longer

Use onMount plus afterNavigate so the completion deadline ring is not
lost to navigation scroll races, and show a stronger primary ring for
five seconds.

Co-authored-by: best <best@classroomio.com>

* test(dashboard): use Jest APIs in compliance badge tests

Drop the Vitest import so the new test matches the dashboard's declared
Jest runner and @types/jest globals.

Co-authored-by: best <best@classroomio.com>

---------
2026-08-19 11:47:43 +01:00
Richard Kennethandricharddkenneth ce663fe365 fix: convert areachart to working barchart in trend-chart.svelte and fixed empty data issue (#930)
Co-authored-by: richarddkenneth <richard-kenneth@users.noreply.github.com>
2026-08-18 17:20:57 +01:00
Rotimi Best 913db2a525 fix(dashboard): repair certificates tab handler so main can build (#929)
* fix(dashboard): repair certificates tab handler so main can build

#854 left a duplicated, unclosed handleActiveTabChange that made the dashboard production build fail with a Svelte parse error. Keep the active tab in local state and sync the query string with replaceState so UnsavedChanges is not triggered.

Co-authored-by: best <best@classroomio.com>

* fix(dashboard): resync certificate tab when the course page is reused

SvelteKit reuses courses/[id]/certificates across course ids, so a one-time tab initializer could stick. Resync from page.url in $effect.pre without reading local tab state, since replaceState does not update page.url.

Co-authored-by: best <best@classroomio.com>

---------
2026-08-18 13:58:26 +01:00
Rotimi BestandRotimi Best 4cd2f5f295 fix(dashboard): open upgrade modal without triggering unsaved-changes prompt (#919)
* fix(dashboard): open the upgrade modal without navigating

Opening the upgrade modal set `?upgrade=true` via `goto`, which SvelteKit treats
as a navigation. On pages guarded by UnsavedChanges (the exercise editor, course
settings, certificates) that triggered the native "You have unsaved changes"
confirm before the modal could appear, and cancelling blocked it entirely.

The modal now lives in app state instead of the URL, so opening and closing it
never navigates. `?upgrade=true` is still honoured for billing emails and the
post-checkout return URL: it is consumed once on load and stripped from the
address bar with shallow routing.

Because checkout is an external redirect that unloads the page, editors can
register a handoff to persist in-progress work first. The exercise editor stashes
its draft, and the checkout return URL now points back at the page the user
upgraded from, so their questions are restored instead of lost.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(dashboard): close question type select before opening upgrade modal

The select dropdown sits at z-250 while the upgrade dialog is z-200, so
opening the modal while the menu was still mounted left it covering the
dialog. Close the select first, wait for its close animation, then open
the upgrade modal.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(dashboard): close question-type select before opening upgrade modal

The premium-type button left the select open, and its z-menu-elevated
content sat in front of the upgrade dialog. Close the select first and
wait for the close animation before opening the modal.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

---------

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
2026-08-18 13:17:06 +01:00
Victor Jackgreptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>Rotimi Best
564b0735f0 fix(dashboard): Save button not showing in certificate settings page (#854)
* Fixed Validation of allowing teachers create compliance courses without setting deadline

* greptile highlightCertificateSettings decraration suggestion

* greptile multiple error bar showing fix suggestion

* organization race condition

* Delete screenshots/Screenshot 2026-07-22 at 11.52.00 AM.png

* deleted unwanted files

* code review fixes

* Changed AttentionHightlight component to work with just one parameter

created fixed Route Enums, Paths and Sections for

* Kebab-case naming fix

* Changes for code comments

* Made changes to add tab to the query param

* Apply suggestion from @greptile-apps[bot]

Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2026-08-18 13:15:49 +01:00
Victor JackandRotimi Best 74ab74e364 fix: make public courses page filters responsive on mobile (#801)
* responsiveness styling

* Changed plain text to use the translation

* AI comment fixes

* Made changes to the Theme

* debug

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
2026-08-18 13:09:05 +01:00
41a77ff025 fix: save regenerated course link on generate (#904)
* improvement: save regenerated course link on generate

* improvements: remove disabled prop from "new course link generate" btn as loading prop already serves; added console error logs for uncaught errs during save or new link generation

---------

Co-authored-by: richarddkenneth <richard-kenneth@users.noreply.github.com>
Co-authored-by: Rotimi Best <best@classroomio.com>
2026-08-18 12:00:52 +01:00
Rotimi BestandRotimi Best 2ebb544a81 fix(dashboard): cache org stats in Redis with versioned invalidation (#883)
* fix(dashboard): cache org stats in Redis with versioned invalidation

Replace the never-expiring process-local cache in the org dashboard page
load with API-side Redis caching keyed per org. A version stamp prevents
slow reads from writing pre-purge data back into the cache.

Invalidate on course create/update/delete, student enroll/remove, certificate
claim, and progress reset. Remove the dashboard SSR module cache so every
page load reads through the API cache layer.

Fixes #852

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(dashboard): address PR review on stats cache invalidation

- Validate bust via ZDashStats instead of raw query access
- Use import type for OrganisationAnalytics
- Resolve orgId before course delete so invalidation is reliable
- Invalidate immediately after persisted writes, before fallible side effects
- Invalidate on member role changes and program course access enrollment

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(dashboard): complete stats cache invalidation coverage

Wire org stats cache purge on lesson completion (manual toggle and
video-watch auto-complete) so topCourses completion percentages stay
fresh. Harden bulk member adds with sequential inserts and a finally
invalidation boundary, and only purge org invite enrollments when new
group memberships were actually inserted.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(core): make lesson completion stats purge best-effort

Wrap org stats cache invalidation in try/catch so a lookup or Redis
failure cannot turn a successful lesson completion into a 500.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(dashboard): keep org stats cache through lesson completion

Lesson complete is a high-frequency event and usually does not change
the rounded dashboard percentages. Rely on a 10 minute TTL instead of
purging the snapshot on every completion.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* docs: explain how admin dashboard numbers are calculated

Add a user-facing guide covering headline counts, top courses,
certificates, login activity, and when the 10 minute snapshot refreshes.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

---------
2026-08-16 22:15:34 +01:00
Rotimi BestandRotimi Best 9fb75e11d2 fix: improve MCP page setup UX and remove automation credit quotas (#821)
* Remove MCP credit quotas and improve MCP setup page

- Always show client setup snippets with placeholder keys
- Add View Docs link to MCP page header
- Remove monthly automation credit enforcement and UI
- Update plan feature text to reflect free MCP access

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix: address PR review feedback for MCP page changes

- Clear generatedSecret when revoking MCP keys
- Use Button link mode for View Docs with rel noopener noreferrer
- Update all dashboard locale files for MCP translations
- Make MCP rate-limit admission atomic with transactional reservations
- Wrap MCP route handlers with withMcpAutomationUsage helper

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* revert: remove atomic MCP rate-limit reservations

The transactional advisory-lock reservation was unnecessary complexity
for our MCP usage model. Restore the simpler check-then-record flow.

Also only clear generatedSecret when revoking the key it belongs to.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix: address remaining MCP page bot review feedback

- Add blank lines after confirm guards in key handlers
- Skip showing rotated secret if key was revoked during rotation
- Fix vi keys_title and de rate_limits_title translations

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix: prevent late key rotation from un-revoking or exposing secrets

- Rotate only when revoked_at IS NULL at update time (DB)
- Reject rotate requests for already-revoked keys (API)
- Ignore stale rotate responses in automation client state
- Guard MCP UI against showing secrets for revoked keys

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

---------
2026-08-16 22:12:04 +01:00
Richard Kenneth b314d5995a fix: translation issue in cohort goals widgett (#879) 2026-08-15 19:05:04 +01:00
Victor Jack ad5c8db5a9 feat(dashboard): moved course completion deadline to course settings page (#842) 2026-08-15 18:55:51 +01:00
Richard Kenneth b267db6319 fix: missing import in cohort newsfeed (#880) 2026-08-15 17:01:47 +01:00
Rotimi Best d3bfd58bb0 fix(dashboard): make Explore page course cards fully clickable to open the preview modal (#899) 2026-08-15 17:00:47 +01:00
Rotimi Best 020f96f2a5 feat(onboarding): remove sell and teach goal options (#908)
Drop sell-online and teach-online from the "What brings you here" list so onboarding only offers employee training, customer courses, and expanding from another platform.
2026-08-15 13:33:19 +01:00
5b63c784b4 fix(course): use distinct shield-check icon for Compliance nav (#906)
* fix(course): use shield-check icon for compliance nav

Certificates and Compliance both used CertificateIcon in the course
sidebar; point Compliance at Lucide's shield-check instead.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(ui): add ShieldCheckIcon moving icon for compliance nav

Replace the Lucide shield-check import with a moving-icons component
matching sibling hover-animation patterns, and use it for Compliance.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(course): use moving BotIcon for AI tutor nav

Switch the course sidebar AI Assistant item from Lucide bot to the
shared moving-icons BotIcon so hover animation matches other nav items.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
2026-08-15 12:12:19 +01:00
Victor Jack d48d85df2f fix(api): allow inviting students to compliance courses without a certificate deadline (#840)
* Allow teachers invite students

* Greptile comment fix

* coderabbit comment fix
2026-08-15 10:30:45 +01:00
Victor Jack ecd456829c Coderabbit fix 2026-08-14 14:14:18 +01:00
Richard Kenneth 96bade402a fix: sequential progression bug in course with ordered contents (#889)
* fix: sequential progression bug in course with ordered contents

* test(utils): add course progression tests and unify navigation ordering

Reuse flattenNavigableItems in getOrderedNavigableContent so next/previous
navigation follows the same section-aware order as progression locks.
Add vitest coverage for grouped multi-section flattening and sequential
access rules.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix: address PR review comments for course progression

Simplify grouped flatten ordering, only block progression for lessons and
exercises explicitly, integrate flattenNavigableItems in the sequential access
test, and include @cio/core watch in api:dev:full.

---------
2026-08-14 13:50:59 +01:00
Victor Jack ae39b62369 Greptile comment 2026-08-14 13:35:06 +01:00
Victor Jack b59b645d65 made the course card clickable everywhere on the body 2026-08-14 12:08:07 +01:00
Victor Jack 0be63fe558 changed the checked value from status to isPublished (#882) 2026-08-14 11:02:16 +01:00
Richard Kennethandrichardkenneth 7ba551aa43 fix(dashboard): hide admin callout banner and actions on cohort courses page for students (#895)
* fix(dashboard): hide admin callout banner and actions on cohort courses page for students

* fix(dashboard): update empty state description for student users

---------

Co-authored-by: richardkenneth <richard-kenneth@users.noreply.github.com>
2026-08-14 10:52:13 +01:00
Victor JackandRotimi Best 176949d8c8 feat: add back button on login page to return to landing page (#808)
* Back button in Login page

Modified the back button to have two options, href option and onlick

The onClick bceomes usable when there's no href prop

and the onClick takes you directly to the previous page

While the href takes you to the url you indicated

* added a showBack condition to show the button

* modified the onClick function

* Removed the back button, made the Image act as the back button

* Changed the OAuth place holder

* Update back-button.svelte

* remove f

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
2026-08-12 07:55:19 +01:00
Richard Kennethandrichardkenneth f0695e9d18 fix(dashboard): Certain menus inside of modals are hidden (#875)
* fix: update z-index utility class for Select component across tag category and tutor selection views

* refactor: standardize z-index management using ui:z-menu-elevated, ui:z-app-bar, ui:z-app-bar-elevated and ui:z-modal classes across UI components

---------

Co-authored-by: richardkenneth <richard-kenneth@users.noreply.github.com>
2026-08-12 07:09:54 +01:00
Victor JackandRotimi Best 6537613000 fix: preserve landing page section content when toggled off (#838)
* preserve section content when toggled off

* Update types.ts

* Remove comment on LandingPageLinks enabled property

Removed comment about the default visibility of the section.

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
2026-08-11 14:31:58 +01:00
Victor JackandRotimi Best a83e172e6d fix: show clear error when creating or updating organization site name to an existing one (#793)
* fixed error of not showing the org name API response error

* Fixed the error also for organization settings page

* modified

* Keep field-specific mapping in OrgApi

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
2026-08-06 06:24:07 +01:00
Victor JackandRotimi Best 60056a6236 fix: validation of allowing teachers create compliance courses without setting deadline (#778)
* Fixed Validation of allowing teachers create compliance courses without setting deadline

* greptile highlightCertificateSettings decraration suggestion

* greptile multiple error bar showing fix suggestion

* organization race condition

* Delete screenshots/Screenshot 2026-07-22 at 11.52.00 AM.png

* deleted unwanted files

* code review fixes

* Changed AttentionHightlight component to work with just one parameter

created fixed Route Enums, Paths and Sections for

* Kebab-case naming fix

* Changes for code comments

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
2026-08-05 13:28:32 +01:00
6ba746c80b fix(dashboard): ensure @cio/ui output.css exists before build (#835)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
2026-08-05 07:07:19 +01:00
9b9dccc07a feat(dashboard): add last updated at course sort option (#823)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
2026-08-04 18:20:25 +01:00
ee4d4e031d fix(course): coerce metadata.discount to number before save (#830)
* fix(course): coerce metadata.discount to number before save

Normalize string discount values from the landing page pricing form and
coerce legacy string metadata on ZCourseUpdate validation so course
settings saves do not fail with a Zod type error.

Fixes classroomio/classroomio#828

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* refactor(utils): add shared toFiniteNumber for metadata discount coercion

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(utils): only coerce string discount values in metadata preprocess

Reject broad Number() coercion on booleans and arrays. String and finite
number discounts are normalized; invalid types are left for Zod to reject.

Addresses Greptile review on #830

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* refactor(dashboard): use writable $derived in pricing form again

Keep toFiniteNumber coercion in setter effect; derived fields stay in
sync when course loads or updates (Svelte 5 writable derived).

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(utils): reject invalid metadata.discount instead of clearing it

Only coerce numeric strings in preprocess; leave invalid strings,
non-finite numbers, and other types for Zod to reject.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
2026-08-04 15:24:24 +01:00
Rotimi BestandRotimi Best dc55f936a1 fix(dashboard): persist notification preferences after save (#824)
* fix(dashboard): persist notification preferences after save

Personal notification toggles were reset when a stale GET response
arrived after save. Org toggles also triggered a spurious org save
because change detection compared against stale currentOrg settings,
and the UI reset because currentOrg was not updated after org save.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* refactor(dashboard): send only changed settings key and tidy load guard

The org update endpoint deep-merges settings server-side, so spreading
local settings into the payload risked writing stale values over
unrelated keys. Also drop a redundant load-version bump and let only
the latest in-flight preferences load clear the loading flag.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* refactor(dashboard): hydrate org notification defaults on org id transition

The sync effect read orgHasChanges and savedOrgToggles while writing them
through applyOrgToggleState, causing an extra effect pass after hydration.
/account returns settings together with the org id and role, so gating
hydration on org id transitions is sufficient and also discards unsaved
edits when switching orgs instead of saving them against the wrong org.

Addresses CodeRabbit review on PR #824.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

---------
2026-08-03 02:18:26 +01:00
Rotimi Best e79d920aeb fix(newsfeed): tighten card spacing and reposition pinned icon (#818)
* fix(newsfeed): tighten card spacing and reposition pinned icon

- Move pinned icon to top-left corner with a tilted rotation
- Remove the extra pin row that added excess top padding
- Reduce spacing between post content, reactions, and comments
- Remove card box shadow

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(newsfeed): place pinned icon on card corner instead of avatar

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(newsfeed): stop dropdown menus locking body scroll

bits-ui defaults DropdownMenu.Content's preventScroll to true, so every
open ran a body scroll lock: a forced full-document style recalc and
reflow, a horizontal shift by the scrollbar width, and pointer-events:
none on the body. On the newsfeed page — a long list of cards each with
a scrollable comment thread — that made the comment ellipsis menu feel
sluggish to open. Menus don't need scroll locking; only modal layers do.

Also tighten card spacing, use the primary color for the pinned icon,
give the card menu trigger a pointer cursor, and switch the comment
save and submit buttons to the loading and secondary variants.

* refactor(newsfeed): store reply targets as data, not markup

Replies used to prepend a presentational <blockquote> — Tailwind classes,
hardcoded English and a copied snippet of the parent — onto the comment
body, and that whole string was persisted to course_newsfeed_comment.content.
Editing a reply therefore showed raw markup in the textarea, the quoted
snippet froze at write time, the label could never be localized, and two of
its ui: utilities never compiled because they appeared only in dashboard code.

Add a replyToCommentId column, distinct from parentId (the thread root), so
a reply can name the comment it answers. The service verifies the target
belongs to the same feed and the same thread; the paginated query joins it
to return replyToAuthorFullname, so the label is rendered from live data by
the new CommentTree.ReplyingTo component using the existing replying_to key.

Rows written the old way still display their stored quote, and
stripLegacyReplyQuote keeps that markup out of the edit textarea.

* refactor(newsfeed): drop redundant comments on reply target plumbing

* fix(newsfeed): address review feedback on pinned icon and quote stripping

- The pin icon carried an unprefixed `text-muted-foreground` plus
  `ui:stroke-primary`, which is emitted nowhere because it appears only in
  dashboard code, so the outline fell back to the muted colour. Use
  `ui:text-primary` and let the stroke inherit currentColor.
- Give the pinned state an accessible name: the icon is `aria-hidden`, and
  header.svelte no longer carries an indicator, so screen readers had nothing.
- Filter feeds on `isPinned` alone. `is_pinned` is only the database column
  name; drizzle maps it to `isPinned` and the API never returns the snake_case
  form, so the `as any` fallback was dead and let the page and card disagree.
- `ui:transition-color` is not a Tailwind utility and emitted nothing; the
  plural `ui:transition-colors` is what applies the transition.
- Strip a legacy reply quote only when the blockquote carries the `reply-quote`
  class the old code always wrote, so a blockquote the author typed is kept.

* docs(agents): forbid presentational markup in persisted content columns

---------
2026-08-02 23:23:18 +01:00
0482790126 feat(ui): add AttachmentList component for lesson file attachments (#822)
* feat(ui): add AttachmentList component for lesson file attachments

Introduce a reusable AttachmentList molecule with view and edit modes.
View mode shows a header with file count and view/download actions; edit
mode supports drag-to-reorder, view, and delete. Includes Storybook
stories and package exports.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(ui): address AttachmentList PR review feedback

- Sync edit-mode file state using full metadata signature, not IDs only
- Clamp formatFileSize unit index and guard non-finite inputs
- Recognize common MIME types for file-type icon styling
- Scope role=list to row container; use dragHandleZone for reordering
- Add reorder label for drag handle accessibility

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(ui): guard AttachmentList against undefined files prop

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(ui): render AttachmentList stories and guard missing props

Use Storybook template snippets and default args so stories mount
correctly. Default mode and labels in the component for autodocs, and
align dragHandleZone items with rendered rows.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* feat(dashboard): use AttachmentList on lesson documents tab

Replace the horizontal DocumentCard grid with AttachmentList in view and
edit modes, including drag-to-reorder in edit. Tighten the list header to
p-2 with xs text, add document mapping helpers, and translate new labels.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(dashboard): reset document upload modal state on close

Clear selected file, success message, and upload progress when the add
document modal closes so reopening starts fresh.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(dashboard): raise PDF viewer above sidebar and AI chat

Use z-modal (200) instead of z-100 so the full-screen PDF overlay stacks
above z-app-bar-elevated (150) course sidebar and z-app-bar (100) AI panel.
Add the missing z-modal utility to dashboard app.css to mirror the UI scale.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* chore: minor changes

* feat(dashboard): show spinner and snackbar on document download

AttachmentList download button now shows the Button loading spinner
immediately while the async download runs. On success, a snackbar confirms
the download; on failure, an error snackbar is shown instead of opening
a new tab.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(ui): address PR review feedback on AttachmentList

- Reject negative byte counts in formatFileSize instead of using Math.abs
- Make AttachmentList mode/files/labels props optional; disable cross-list drops
- Include file name in drag-handle aria-label for screen readers
- Prevent upload modal from closing while an upload is in progress
- Use ICU plural rules for attachment file_count across all locales

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
2026-08-02 21:34:06 +01:00
Rotimi Best 06c3a7feba feat(ui): Page.SettingsActions for settings save bar (#819)
* feat(ui): add Page.SettingsActions for settings save bar

Introduce a sticky bottom save/discard bar on Page and migrate settings
pages to use it instead of header or inline save buttons. Includes
profile, org, customize-lms, notifications, auth, AI tutor, course
settings, certificates, and cohort settings.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(ui): always show Page.SettingsActions, disable when clean

Keep the settings save bar visible at all times and disable Save and
Discard when hasChanges is false instead of hiding the bar entirely.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(ui): add bottom spacing and shadow to Page.SettingsActions

Wrap the actions card in a sticky container with bottom padding so it
does not sit flush against the viewport edge, and use shadow-md on the
card for clearer elevation.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(ui): stop viewport-sticking Page.SettingsActions on scroll

Remove sticky bottom positioning so the save bar stays at the end of the
page content and scrolls away when viewing upper sections.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(ui): use sticky bottom for Page.SettingsActions dock behavior

Replace the IntersectionObserver fixed/dock toggle that caused layout
thrashing and flipping with native position: sticky; bottom: 0. Move
overflow-x-hidden from Page.Root to Page.Body so sticky is not broken.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(ui): keep Page.SettingsActions sticky and above editor content

Page.Root carried md:overflow-x-auto, which makes overflow-y compute to
auto, turning the root into the sticky bar's nearest scrolling ancestor.
Since the root sizes to its content it never scrolls internally, so the
bar rendered at its natural flow position instead of pinning to the
viewport. Drop the overflow so sticky resolves against the viewport;
horizontal containment stays on Page.Body.

Raise the bar to z-50 so it is not painted over by the editor root,
which is relative z-50. Matching rather than exceeding 50 keeps
body-portaled dialogs and sheets above the bar.

Show the shadow only while the bar is pinned away from its flow
position, tracked with an IntersectionObserver on a 1px sentinel
sibling. Unlike the previous observer this toggles a box-shadow only, so
it cannot feed back into layout.

* fix(ui): tighten settings actions spacing and tab visibility

- Remove mt-6 and lower z-index so popovers sit above the bar
- Wrap sentinel in a single root to avoid extra Page.Root flex gap
- Remove Page.Body mb-4 redundant with settings actions spacing
- Show certificate save bar only on the settings tab
- Raise theme color picker z-index via --picker-z-index

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(dashboard): address PR review feedback for settings actions

- Capture profile/org saved baselines only on initial load, not on edits
- Reset course settings errors with explicit field shape in handleDiscard
- Restore VisitOrgSiteButton on org profile settings header
- Derive AI tutor dirty state from stores directly instead of mirrored state
- Use discard-specific French label and document SettingsActions props

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* docs(ui): clarify Page.Root ownership for Page.SettingsActions

"Last child of Page.Root" is a runtime relationship. Several settings
pages get their Page.Root from a +layout.svelte, so the page file holds
only Page.Header, Page.Body, and Page.SettingsActions. Document that this
is correct and list the pages, since adding a nested Page.Root stacks two
min-height containers and shortens the sticky bar's containing block.

Also document that Page.SettingsActions must never be rendered from
inside Page.Body: overflow-x-hidden makes Page.Body a scroll container
that never scrolls, so a sticky bar in it has no travel.

---------
2026-08-02 09:11:09 +01:00
c01b8dea3c fix(dashboard): use secondary variant for customize design button (#816)
Change the Customize design button on the certificate design page from
primary to secondary styling for better visual hierarchy.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
2026-08-01 19:55:06 +01:00
20e34e29dd feat(exercise): add open-ended thumbs up/down question type (#780)
* feat(ui): add THUMBS question type renderer

- Add seven new renderer files under renderers/thumbs/:
  thumbs-correct.ts, take.svelte, edit.svelte, preview.svelte,
  thumbs-review-toggle.svelte, review.svelte, submission.svelte
- Register THUMBS in renderer-contract.ts with edit/take/preview/submission/review
- Export ThumbsEdit, ThumbsTake, ThumbsPreview, ThumbsSubmission,
  ThumbsReview from exercise-question/index.ts
- Add THUMBS_FIXTURE, four thumbs label keys, and Thumbs story
  in storybook exercise-question files
- Add THUMBS case to question-type-modes.svelte submission builder

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* feat(question-types): add THUMBS contracts, scoring, and seed

Introduce the THUMBS question type (id 14) with boolean answer data,
codec, auto-grading, Zod validation, DB seed/schema docs, and binary
normalize support shared with TRUE_FALSE. Dashboard wiring is deferred.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* feat(dashboard): wire THUMBS question type into exercise UI

Connect THUMBS option sync in edit/view modes, labels, picker icon,
and locale strings so the existing contracts surface in the dashboard.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(ui): polish thumbs question UI and move premium upgrade icon

Widen thumbs Yes/No toggles, inherit selected icon color via custom class,
replace badge-based review correctness with solid fill states and bare
Check/X icons, add a success pill in preview, and place the premium
upgrade icon at the end of the question type select row.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(ui): align thumbs review colors with prototypes

Tune correct/incorrect selected states so review buttons use dark
emerald/red content colors on solid fills, matching the design comps.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(ui): address remaining PR review comments

Use a shared timestamp local in thumbs edit fallback options.
Move border-beam animation-delay after the animation shorthand.
Alias --spacing to --ui-spacing for tw-animate compatibility.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(db): sync question_type sequence before seeding new types

Avoid duplicate key errors when question_type_id_seq lags behind rows
inserted via restore or explicit ids (e.g. THUMBS on restored databases).

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(exercise): resolve thumbs review label ICU crash

Remove {value} ICU placeholder from thumbs.review.correct_answer_label
since labels are resolved at map-build time without context. Append the
dynamic yes/no label in thumbs-review-toggle instead.

Also bind question points through the questionnaire store index to avoid
the Svelte non-reactive property binding warning in edit-mode.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* docs(agent): add THUMBS guidance and sync question type docs

Add shared agent question-type guide helpers in @cio/question-types and
use them across the teacher prompt, add/update_questions tool schemas,
legacy ai-assistant schemas, and MCP exercise tools.

- Document when to use THUMBS (Yes/No sentiment) vs TRUE_FALSE (factual)
- Fix stale MCP question type ids and add THUMBS=14
- Exclude disabled MATCHING/HOTSPOT from enabled type lists

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(thumbs): clarify auto-grading and add editable option labels

THUMBS is auto-graded like TRUE_FALSE; the difference is thumbs UI with
customizable Yes/No-style labels and the teacher selecting the correct
option. Render take/preview/review from question.options labels and add
label inputs in the thumbs edit renderer. Update agent and MCP docs to
stop implying THUMBS is manual or non-gradable.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* chore(mcp): bump version to 0.0.10

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(question-types): make THUMBS open-ended with no correct answer

THUMBS is a sentiment/poll question type, not an auto-graded binary like
TRUE_FALSE. Remove correct-answer UI, scoring, and validation; update agent
and MCP docs accordingly.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(dashboard): stop treating THUMBS as auto-graded in editor validation

Normalize thumbs questions on load/save, show an Open-ended badge instead
of Auto, and only enforce points/correct-answer rules on auto-gradable types.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* revert(dashboard): remove unsolicited open-ended grading badge

THUMBS uses the existing Manual badge like other non-auto-gradable types.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(exercise): show question title validation errors in editor

Display mapped title errors beneath the question title input instead of
only highlighting the question card border. Pass the exercise entity to
mapZodErrorsToTranslations so messages resolve to validations.exercise.*
keys, and use a translated snackbar key for save validation failures.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(mcp): build workspace deps before MCP publish bundle

The MCP esbuild step imports @cio/question-types and @cio/utils dist
outputs. CI failed on a clean checkout because mcp:build only built the
MCP package itself. Build those dependencies first so 0.0.10 can publish.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
2026-07-31 14:59:38 +01:00
0abdfe2102 fix(dashboard): align hero enroll CTA with pricing section (#810)
The hero Enroll button was disabled unless metadata.allowNewStudent was
explicitly true, while the pricing CTA treated unset values as open
(matching the enroll page and API). This left the hero button inert on
courses where enrollment was allowed but the flag was not set.

Also sync pricing CTA disabled state, use activeOrg for paid enroll on
public org-site pages, and keep hero/pricing disabled flags aligned via
alignHeroCtaWithPricing.

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
2026-07-31 14:53:01 +01:00
Nwosu EmmanuelandRotimi Best a1c4ee720f feat(newsfeed): Improved UI flow for the course newsfeed (#791)
* feat(newsfeed): improved the newsfeed ui

* feat(newsfeed): updated the ui

* feat(newsfeed): some improvements

* feat(newsfeed): cleanup

* chore(newsfeed): added translations

* fix(newsfeed): fixing comments and reviews from greptile and coderabbit

* fix(newsfeed): fixing comments and reviews from greptile and coderabbit, improved replies to a reply

* fix(newsfeed): fixing reviews

* fix(ui): expose cancelLabel prop in comment-tree-input and pass localized text

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
2026-07-31 00:26:59 +01:00
Victor JackandRotimi Best 88c4cf03b3 Fixed the embeded code responsiveness (#799)
Co-authored-by: Rotimi Best <best@classroomio.com>
2026-07-31 00:11:25 +01:00
Victor JackandRotimi Best 7ece82840c Fix: Exercice saving validation error (#774)
Fixed the error that keeps previous deleted questions before saving

Co-authored-by: Rotimi Best <best@classroomio.com>
2026-07-24 14:18:07 +01:00
Victor Jack 05833f441f fix: Chart Circular Dependency (#772)
* Fix: Chart Circular Dependency

* Corrections to greptile reviews

* code rabbit coding guideline

---------
2026-07-24 14:09:09 +01:00
2ceafcc6d1 fix(dashboard): stop ERR_CONTENT_DECODING_FAILED on auth/proxy (#779)
* fix(dashboard): prevent ERR_CONTENT_DECODING_FAILED on API proxy

Node fetch decompresses gzip/br upstream bodies while leaving
Content-Encoding set, so browsers fail auth/proxy responses with
net::ERR_CONTENT_DECODING_FAILED. Request identity encoding and strip
encoding headers from proxied responses. Also allow data: fonts in CSP
and skip UserJot on self-hosted.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(dashboard): preserve Set-Cookie and use Jest for proxy tests

Copy Set-Cookie via getSetCookie/append so multi-cookie auth responses
are not collapsed on Expires commas. Convert proxy tests to Jest and
add CSP domain normalization coverage.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
2026-07-23 17:43:52 +01:00
Abdulmuizz HamzatandRotimi Best b599f1a017 chore(docs): migrate the docs site from Fumadocs to Blume (#753)
* Update documentation for ClassroomIO: change Fumadocs to Blume, revise MDX format conventions, and clarify sidebar navigation requirements.

* docs: update README and scripts for improved preview and error handling

* docs: update Discord link in configuration and documentation

* docs: add token-auth route and update documentation formatting

* Fix formatting in auto-comment-issues.yml

* Fix JSON syntax by adding a comma

* fix(docs): declare Blume's Astro build deps so Cloudflare build resolves them

blume build generates an Astro project under apps/docs/.blume/ that imports
astro, @astrojs/mdx, @tailwindcss/vite, tailwindcss, @tailwindcss/typography,
@shikijs/twoslash and @scalar/astro. Under pnpm's isolated node_modules these
are nested inside blume and not resolvable from the docs app, so a clean
--frozen-lockfile build (Cloudflare) fails at 'Cannot find module astro/config'.
Declare them directly in @cio/docs, pinned to blume's resolved versions.

* refactor(docs): update API reference paths and documentation for clarity

* fix: change platofrm url to docs home page

* Refactor documentation for self-hosting ClassroomIO

- Removed the Supabase Cloud setup guide as it is no longer needed.
- Updated the Enterprise SSO documentation to include a reference link for SSO & licensing.
- Added new documentation for Content Security Policy (CSP) configuration.
- Created a new section for configuring SMTP for email notifications.
- Introduced a comprehensive configuration index for self-hosted ClassroomIO, detailing environment variables and their usage.
- Added documentation for optional integrations including Google OAuth, OpenAI, and Unsplash.
- Documented the generation and usage of required secrets for ClassroomIO.
- Created a section for SSO & licensing, detailing the requirements for Enterprise features.
- Added object storage configuration documentation, covering both bundled MinIO and external S3-compatible services.
- Documented versioning and upgrade processes for ClassroomIO.
- Updated the Coolify and Docker self-hosting guides to streamline configuration references and upgrade instructions.
- Enhanced the Railway self-hosting guide with links to relevant configuration sections.
- Updated token authentication documentation to reference SSO & licensing.

* Add comprehensive guides for ClassroomIO features and functionalities

- Introduced a new guide for the Student Dashboard, detailing navigation and features available to students.
- Added a guide on using Tags for organizing and filtering courses within ClassroomIO.
- Created a guide on how to take attendance in courses, including marking attendance and related functionalities.
- Developed a Terminology guide to clarify key terms used throughout ClassroomIO.
- Implemented a Token Auth guide for signing users into ClassroomIO from external applications using JWT.
- Added instructions for using math in the editor with LaTeX support.
- Created a guide for customizing the welcome email sent to students upon enrollment in courses.
- Added a guide explaining what ClassroomIO is and its key features.
- Updated the home documentation to provide a clearer overview of ClassroomIO and its resources.
- Enhanced self-hosted configuration documentation with a new guide for deploying ClassroomIO on Dokploy.
- Updated links in the self-hosted configuration documentation for better navigation.
- Added new images to support the Dokploy deployment guide.
- Updated asset packaging script to handle redirects for the new guide structure.
- Fixed footer link to point to the correct Getting Started guide.

* chore: uprage version

* feat(docs): add DigitalOcean deployment guide and flatten guides structure

- Add a guide for deploying ClassroomIO on DigitalOcean using Docker Compose
  and Caddy for automatic HTTPS.
- List DigitalOcean as a deployment option in the self-hosting index.
- Move guides out of content/docs/guides into content/docs (student-dashboard,
  tags, terminology, token-auth, what-is-classroomio, audience, cohorts,
  community, course, enterprise-sso, mcp, organization, quickstart/*).
- Rename home.mdx to index.mdx.
- Update package-assets script to emit redirects for the old guides paths.

---------

Co-authored-by: Rotimi Best <best@classroomio.com>
2026-07-22 22:30:10 +01:00
e09b3caa8a fix(i18n): translate untranslated English strings in all locale files (#770)
* fix(i18n): translate untranslated English strings in all locale files

Audited all 9 non-English dashboard locale files against en.json and
found 495 unique keys (2,422 total entries) still containing English
text. Manually translated all user-facing strings across da, de, es,
fr, hi, pl, pt, ru, and vi while preserving ICU plural syntax,
placeholders, and brand names.

Remaining identical strings are intentional: international brand names
(Okta, Instagram), technical acronyms (SSO, API, cURL), URLs,
placeholders, and words that are spelled identically in the target
language (e.g. German Status, French Navigation).

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(i18n): translate exercise plural labels in kpi_lessons_exercises

Address Greptile review: ICU plural blocks still contained English
'exercise' / 'exercises' across all nine non-English locale files.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
2026-07-19 12:39:40 +01:00
Rotimi BestandRotimi Best 0822f5f5a3 fix(dashboard): course landing page editor section and currency bugs (#768)
* fix(dashboard): course landing page editor section and currency bugs

- Raise currency select z-index so the dropdown is clickable in the
  full-screen editor shell
- Show dashed top/bottom borders and section title on hover and when
  active in EditableLandingSection
- Map hero/header section keys in the course editor preview context

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(ui): format course landing page pricing with Intl currency

Use getCourseCurrencyFormatter (matching dashboard getCurrencyFormatter)
so NGN and USD prices render as proper locale currency strings instead of
raw numbers with a currency code suffix.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(ui): prefix group class for landing section hover badge

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(ui): remove unused formatCourseCurrency helper

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* refactor: move currency formatter to @cio/utils

Shared course price formatting belongs in @cio/utils, not the UI
package. Dashboard re-exports the helper to keep existing imports.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

---------
2026-07-19 12:09:38 +01:00
Rotimi BestandRotimi Best 6c3a982daf feat(course): auto-enrol button for paid course join requests (#769)
* feat(course): add auto-enrol flow for paid course join requests

- Extend teacher buy-request email with manual access steps and an
  Auto Enrol this Student button
- Link teachers to the course People page with grantAccess query param
- Add grant-access modal that sends the same org audience invite used
  by the Invite new students flow

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(i18n): translate grant access modal strings

Replace English placeholders copied into non-en locale files with
proper translations for each supported language.

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(email): shorten auto-enrol button label per review

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(email): use American spelling Enroll in auto-enroll CTA

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

* fix(i18n): localize Danish grant-access send invite label

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>

---------

Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
2026-07-19 12:02:11 +01:00