After email login, auto-enroll was redirecting to /lms or the admin
/courses/:id/lessons route. Unverified users cannot use the LMS yet
(VerifyEmailModal on app routes), which felt like a permission error.
Enroll without verification, but stay on the enroll page with a verify
prompt instead of redirecting until email is confirmed. Also fix custom
domain org lookup to use hostname without backend port.
Co-authored-by: Rotimi Best <[email protected]>
* fix(dashboard): surface save errors, force public site light mode, global verify modal
- Show snackbar with first validation error when course update fails client-side validation
- Force light mode on public org-site pages; lesson player and dashboard keep theme selector
- Mount VerifyEmailModal in root layout for all authenticated routes; keep trigger=app email guard
Co-authored-by: Rotimi Best <[email protected]>
* fix: instructor editor sync, auto-enroll after auth, email verification API
- Initialize instructor form from org fallbacks and batch metadata updates
- Auto-complete course enrollment after login/signup return from enroll page
- Block new org auto-enroll and course enroll until email is verified
Co-authored-by: Rotimi Best <[email protected]>
* fix: address PR review on instructor setter, color scheme, auto-enroll
- Use existing setter with batched metadata.instructor updates in InstructorForm
- Allowlist public org-site paths so /lms and /courses/:id keep theme selector
- Auto-enroll logged-in users via Better Auth session; remove sessionStorage flow
- Retry enrollment after email verification without duplicate Join Course clicks
Co-authored-by: Rotimi Best <[email protected]>
* fix: allow enrollment without verification; gate dashboard only
- Remove API email verification checks from enroll and auto-enroll flows
- Move VerifyEmailModal to (app) layout so public org-site pages stay usable
- Unverified users can enroll but hit the verification modal in LMS/dashboard
Co-authored-by: Rotimi Best <[email protected]>
* fix: address PR feedback on verify modal, routes, instructor form
- Restore currentOrg.id guard on VerifyEmailModal open condition
- InstructorForm imports currentOrg directly instead of prop drilling
- Move public org-site light-mode route patterns to routes constants
- Reset enrollment attempt key on enroll failure to allow retry
Co-authored-by: Rotimi Best <[email protected]>
---------
Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: Rotimi Best <[email protected]>
When no custom favicon is set, fall back to the organization logo on
public org-site pages instead of the default ClassroomIO icon.
For OG images, only apply the theme-color background behind the logo
when rendering initials; real logo images now render without the tint.
Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: Rotimi Best <[email protected]>
* feat(org): add dynamic open graph images for school sites
Generate branded 1200x630 OG images for org public sites via GET /org/og/:siteName.png.
Paying cloud orgs include their landing-page tagline; free plans show a Powered by
ClassroomIO watermark. Dashboard org-site meta tags now point at the dynamic image.
Co-authored-by: Rotimi Best <[email protected]>
* feat(org): enable custom favicon upload and org-site rendering
Allow org admins to upload a favicon from domain settings, persist it via
PUT /organization, and render it on public org sites with logo fallback.
Co-authored-by: Rotimi Best <[email protected]>
* fix(org): scope custom favicon to public org-site routes only
Co-authored-by: Rotimi Best <[email protected]>
* refactor(api): inline org-site OG rendering, remove @cio/og-images package
Move template and render helpers into apps/api/src/utils/org-site-og.ts.
Certificates stay in a shared package because they support multiple templates;
org OG is a single API-only concern.
Co-authored-by: Rotimi Best <[email protected]>
* chore: remove packages/og-images directory
Co-authored-by: Rotimi Best <[email protected]>
* refactor(api): mount org OG image under /org-site/og
Align with existing org-site public routes (/org-site/course) instead of a
top-level /org prefix.
Co-authored-by: Rotimi Best <[email protected]>
* refactor(plans): consolidate isOrgOnFreePlan into options-based utility
Move orgId guard and self-hosted/plan checks into @cio/utils/plans.
Dashboard and API callers pass plans, isSelfHosted, and optional orgId
directly; remove the dashboard store wrapper.
Co-authored-by: Rotimi Best <[email protected]>
* fix(org): require orgId for free-plan check and translate favicon strings
Treat missing orgId as not on free plan so nav gating matches prior
behavior when currentOrg is unset. Pass organization.id from OG service.
Add favicon settings translations for all non-English locales.
Co-authored-by: Rotimi Best <[email protected]>
* refactor(dashboard): move meta tag helpers to metaTags.server.ts
Keep PRIVATE_SERVER_URL resolution in a server-only module so dynamic
private env imports cannot leak into universal or client bundles.
Co-authored-by: Rotimi Best <[email protected]>
---------
Co-authored-by: Cursor Agent <[email protected]>
Co-authored-by: Rotimi Best <[email protected]>
- Add Types section to the Filter sidebar with all 4 course types
- Add full-text search input centered in the hero (title + description)
- Add Pricing section (Free / Paid) based on course cost
- Compact hero prop across all 10 themes to halve vertical padding
- Children snippet support in all hero themes for the search input
- All filters are URL-driven (?types=, ?search=, ?tags=, ?pricing=)
- Full-stack: validation schema, DB queries, service, API route, server load, UI
A subdomain like xxazzz.myclassroomio.com set isOrgSite=true with a null
org, and the course page fell back to resolving the org from the course
itself — so courses rendered under subdomains that don't exist. Guard
the (org-site) layout so an unresolved org site 404s instead.
- Add paginated recommended courses to LMS dashboard (top 3, list
view)
and explore page (12 per page); exclude courses blocking new
students
- Polish course preview modal: remove padding, render requirements as
HTML, round banner image, secondary close button
- Redirect to course start page after successful enrollment instead of
/lms;
keep spinner active until navigation completes
- Auto-enable "allow new students" when a course is published
- Move Explore after My Learning in LMS sidebar nav
- Move Certificates after Content in course sidebar
- Add hiddenColumns prop to CourseListRow; show "Learn more" button
instead of actions menu in LMS explore context
New features:
- HLS video encoding/streaming (hls routes, hls-encoder) and a media
transcripts pipeline (transcripts route + media-transcript queries)
- AI assistant chat vs. run model: agent-course-generation worker/queue,
run-monitor pane, chat-changes card, content ask-ai bar
- Per-theme course-page previews across org landing-page themes (bold,
classic, corporate, editorial, etc.) with a shared course token/component set
- View-as-student mode and login-link / link-invite invite flows
Refactor:
- Extract packages/core consolidating config (env, storage, dashboard-url),
services and utils previously duplicated under apps/api/src/config
- Remove legacy agent service modules (chat-tools, chat-context,
context-window, document, fetch-url, lesson-content)
- Relax Cross-Origin-Resource-Policy to allow cross-origin media embedding
- Add format script to tenant-router so turbo run format covers it