chore: ai, public course and other ui improvements
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
# Org Landing Page Theme Picker + Unified Content Editor
|
||||
|
||||
## Status
|
||||
|
||||
- Done (shipped)
|
||||
|
||||
## Context
|
||||
The org has 3 landing page theme templates (Minimal, Bold, Classic) in `packages/ui/` that all accept `OrgLandingPageProps` uniformly. However:
|
||||
1. The templates are **only used in Storybook** — the actual public landing page (`landing-page.svelte`) renders its own custom legacy HTML
|
||||
2. The stored `OrgLandingPageJson` is a complex format (header, aboutUs, faq, contact, mailinglist, etc.) that **doesn't align** with what the templates need
|
||||
3. There is no theme selection UI
|
||||
4. There is no edit flow like the course landing page (split-screen with live preview)
|
||||
|
||||
This plan: simplifies the stored format to match `OrgLandingPageProps`, replaces the legacy public landing page renderer with the theme components, adds a theme picker in settings, and adds a split-screen content editor modeled after the course landing page editor.
|
||||
|
||||
---
|
||||
|
||||
## Simplified Data Model
|
||||
|
||||
Replace `OrgLandingPageJson` (the DB-stored type) with a structure that maps 1:1 to `OrgLandingPageProps`:
|
||||
|
||||
```ts
|
||||
// apps/dashboard/src/lib/utils/types/org.ts
|
||||
export interface OrgLandingPageJson {
|
||||
theme: 'minimal' | 'bold' | 'classic';
|
||||
hero: {
|
||||
heading: string;
|
||||
subheading: string;
|
||||
primaryAction: { label: string; href: string };
|
||||
secondaryAction?: { label: string; href: string };
|
||||
image?: string;
|
||||
};
|
||||
navItems: Array<{ label: string; href: string }>;
|
||||
footerLinks: Array<{ label: string; href: string }>;
|
||||
footerText: string;
|
||||
}
|
||||
```
|
||||
|
||||
Sections removed from old format: `header` (with banner/background/show toggles), `aboutUs`, `courses`, `faq`, `contact`, `mailinglist`, `customLinks`, `footer` (social links only).
|
||||
|
||||
Fields from org data (not stored in `landingpage`): `orgName` → `org.name`, `logoUrl` → `org.avatarUrl`, `courses` → `orgApi.publicCourses`.
|
||||
|
||||
---
|
||||
|
||||
## Changes
|
||||
|
||||
### 1. Update `OrgLandingPageJson` type
|
||||
**File:** `apps/dashboard/src/lib/utils/types/org.ts`
|
||||
|
||||
Replace the full type with the simplified version above.
|
||||
|
||||
### 2. Update settings store with new defaults
|
||||
**File:** `apps/dashboard/src/lib/features/settings/utils/store.ts`
|
||||
|
||||
Replace `landingPageSettings` writable defaults with the new schema:
|
||||
```ts
|
||||
{
|
||||
theme: 'minimal',
|
||||
hero: {
|
||||
heading: 'Master the Skills of Tomorrow',
|
||||
subheading: 'Join thousands of learners and accelerate your career.',
|
||||
primaryAction: { label: 'Explore Courses', href: '#courses' },
|
||||
secondaryAction: { label: 'Learn More', href: '#about' },
|
||||
image: ''
|
||||
},
|
||||
navItems: [
|
||||
{ label: 'Courses', href: '#courses' },
|
||||
{ label: 'About', href: '#about' }
|
||||
],
|
||||
footerLinks: [
|
||||
{ label: 'Terms', href: '#terms' },
|
||||
{ label: 'Privacy', href: '#privacy' }
|
||||
],
|
||||
footerText: '© 2026 All rights reserved.'
|
||||
}
|
||||
```
|
||||
|
||||
Also update (or delete and inline) the old store at:
|
||||
`apps/dashboard/src/lib/features/org/components/settings/landing-page-store.ts`
|
||||
(currently used by the legacy landing-page.svelte — this store will go away when we replace the renderer).
|
||||
|
||||
### 3. Redesign `landingpage.svelte` settings page — theme picker only
|
||||
**File:** `apps/dashboard/src/lib/features/settings/pages/landingpage.svelte`
|
||||
|
||||
Remove all existing form fields. The page now shows only:
|
||||
- **Theme picker** (`Field.Set` "Choose a Theme"): 3 cards in `grid grid-cols-3 gap-4` using `RadioGroup.Root bind:value={$landingPageSettings.theme}`. Each card:
|
||||
- Inline SVG thumbnail (distinct per theme — see SVG specs below)
|
||||
- Theme name label
|
||||
- Radio selection state: `border-primary ring-2 ring-primary/20` vs `border-border`
|
||||
- `onValueChange={() => (hasUnsavedChanges = true)}`
|
||||
- **Save button** already in the route page header — saves only `theme` (plus full JSON as fallback)
|
||||
- **"Edit Content"** button below the theme picker (`Button variant="outline"`) → navigates to `/org/[slug]/settings/landingpage/edit`
|
||||
- `UnsavedChanges` component stays
|
||||
|
||||
SVG thumbnail specs:
|
||||
- **Minimal**: white bg, light gray nav bar top, 2 text lines (heading/subheading), 2 course card outlines in a 2-col grid, thin footer line
|
||||
- **Bold**: dark `#111` bg top-half, large bold heading text block, course cards with image-left layout, dark inverted footer
|
||||
- **Classic**: dark navy `#1e3a5f` header bar, white body, centered heading, 3-col course card grid, minimal footer
|
||||
|
||||
Subscriber fallback for old data (users who previously saved the old format):
|
||||
```ts
|
||||
if (!landingpage.theme || !landingpage.hero) {
|
||||
// Set minimal defaults — discard old incompatible structure
|
||||
$landingPageSettings = { ...defaultLandingPageSettings };
|
||||
} else {
|
||||
$landingPageSettings = landingpage;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. New split-screen content editor route
|
||||
**File (new):** `apps/dashboard/src/routes/org/[slug]/settings/landingpage/edit/+page.svelte`
|
||||
|
||||
Modeled after `apps/dashboard/src/routes/courses/[id]/landingpage/+page.svelte`.
|
||||
|
||||
Layout:
|
||||
```svelte
|
||||
<!-- Fixed full-screen overlay, fly transition -->
|
||||
<div class="fixed inset-0 z-250 h-screen w-screen">
|
||||
<Sidebar.Provider>
|
||||
<Sidebar.Root side="left" collapsible="icon">
|
||||
<OrgLandingPageEditor bind:settings={landingPageSettings} {org} onSave={handleSave} onClose={handleClose} />
|
||||
</Sidebar.Root>
|
||||
<Sidebar.Inset class="relative h-screen overflow-y-auto">
|
||||
<!-- Live theme preview -->
|
||||
{#if $landingPageSettings.theme === 'bold'}
|
||||
<BoldLandingPage {...previewProps} />
|
||||
{:else if $landingPageSettings.theme === 'classic'}
|
||||
<ClassicLandingPage {...previewProps} />
|
||||
{:else}
|
||||
<MinimalLandingPage {...previewProps} />
|
||||
{/if}
|
||||
</Sidebar.Inset>
|
||||
</Sidebar.Provider>
|
||||
</div>
|
||||
```
|
||||
|
||||
`previewProps` is derived from `$landingPageSettings` + `$currentOrg` + `orgApi.publicCourses` (mapped to `OrgLandingPageProps`).
|
||||
|
||||
`handleClose()` → `goto('/org/[slug]/settings/landingpage')`.
|
||||
|
||||
### 5. New editor sidebar component
|
||||
**File (new):** `apps/dashboard/src/lib/features/settings/pages/landingpage-editor.svelte`
|
||||
|
||||
Mirrors the section-based structure of the course landing page `editor.svelte`.
|
||||
|
||||
Sections (left sidebar menu → click to edit):
|
||||
1. **Hero** — heading, subheading, primaryAction (label + href), secondaryAction (optional label + href toggle), image (upload widget, used by Bold/Classic)
|
||||
2. **Navigation** — list of nav items (label + href), add/remove rows
|
||||
3. **Footer** — footer text, list of footer links (label + href), add/remove rows
|
||||
|
||||
Header: "Save" button + close button (back to settings).
|
||||
|
||||
Section form pattern: inline `Input` / `Textarea` / `UploadWidget` fields, two-way bound to `settings` prop, changes reflect live in the preview panel.
|
||||
|
||||
### 6. Replace legacy public landing page renderer
|
||||
**File:** `apps/dashboard/src/lib/features/org/components/landing-page/landing-page.svelte`
|
||||
|
||||
Replace the full custom HTML renderer with:
|
||||
- Import `MinimalLandingPage`, `BoldLandingPage`, `ClassicLandingPage` from `@cio/ui/custom/org-landing-page`
|
||||
- Load `org.landingpage` into local state with defaults fallback
|
||||
- Build `previewProps: OrgLandingPageProps` = `{ orgName: org.name, logoUrl: org.avatarUrl, ...landingpage, courses: orgApi.publicCourses }`
|
||||
- Render the correct theme component based on `landingpage.theme`
|
||||
- Remove all legacy section rendering (aboutUs, faq, contact, mailinglist, etc.)
|
||||
- Keep `orgApi.getPublicCoursesBySiteName(orgSiteName)` call for course loading
|
||||
- Keep `<svelte:head>` and `<PoweredBy />`
|
||||
|
||||
---
|
||||
|
||||
## Critical Files
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `apps/dashboard/src/lib/utils/types/org.ts` | Replace `OrgLandingPageJson` type |
|
||||
| `apps/dashboard/src/lib/features/settings/utils/store.ts` | New defaults matching new type |
|
||||
| `apps/dashboard/src/lib/features/org/components/settings/landing-page-store.ts` | Delete or update (old format no longer needed) |
|
||||
| `apps/dashboard/src/lib/features/settings/pages/landingpage.svelte` | Theme picker + Edit Content button only |
|
||||
| `apps/dashboard/src/routes/org/[slug]/settings/landingpage/+page.svelte` | Minor adjustments if needed |
|
||||
| `apps/dashboard/src/lib/features/org/components/landing-page/landing-page.svelte` | Replace renderer with theme components |
|
||||
| `apps/dashboard/src/routes/org/[slug]/settings/landingpage/edit/+page.svelte` | **New** — split-screen editor route |
|
||||
| `apps/dashboard/src/lib/features/settings/pages/landingpage-editor.svelte` | **New** — editor sidebar component |
|
||||
|
||||
## Reused Patterns
|
||||
- `Sidebar.Provider/Root/Inset` from `@cio/ui/base/sidebar` — same as course landing page editor route
|
||||
- `RadioGroup.Root/Item` — already imported in `landingpage.svelte`
|
||||
- `Field.Set/Field.Legend/Field.Separator` — already imported in `landingpage.svelte`
|
||||
- `UploadWidget` — already imported in `landingpage.svelte`
|
||||
- `orgApi.update($currentOrg.id, { landingpage: ... })` — same save pattern
|
||||
- `UnsavedChanges` component — already in `landingpage.svelte`
|
||||
- `goto()` for close/navigate — same as course editor
|
||||
|
||||
## Verification
|
||||
1. **Theme picker**: `/org/[slug]/settings/landingpage` shows 3 cards with distinct SVG previews; clicking a card highlights it; saving persists the selection; reload reflects saved theme
|
||||
2. **Edit Content**: clicking "Edit Content" navigates to `/org/[slug]/settings/landingpage/edit`; the editor opens as a full-screen overlay with the selected theme visible on the right; editing fields updates the preview live
|
||||
3. **Public landing page**: visiting the org's public URL renders the selected theme (Minimal/Bold/Classic) using actual org courses; switching theme in settings and saving changes what public visitors see
|
||||
4. **Backward compat**: orgs with old `OrgLandingPageJson` format get reset to minimal defaults gracefully (no crash)
|
||||
@@ -0,0 +1,92 @@
|
||||
# Org Landing Page Editor Parity Plan
|
||||
|
||||
## Scope Lock
|
||||
|
||||
1. Keep the existing org landing page feature set, but refactor the editor and preview shell to match the course landing page structure.
|
||||
2. Preserve the query-parameter handoff for theme preview so content can be edited against a selected theme without publishing it.
|
||||
3. Do not add section descriptions in the org editor if the course editor does not use them.
|
||||
4. The editor preview pane must scroll like the course landing page preview pane, not like the current org implementation.
|
||||
5. The sidebar shell must use the same composition and visual rhythm as the course editor.
|
||||
6. Keep the editor sections limited to the existing org landing page content areas, including embed support.
|
||||
|
||||
## Delivery Phases
|
||||
|
||||
1. Phase A: Align the org editor route shell with the course landing page editor shell.
|
||||
2. Phase B: Split the org editor into small section components.
|
||||
3. Phase C: Fix scroll behavior, sidebar layout, and header/action placement.
|
||||
4. Phase D: Clean up remaining copy, state handling, and regression risks.
|
||||
|
||||
## Ticket Breakdown
|
||||
|
||||
| ID | Area | Task | Key Files | Depends On | Done When |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| OLP-1 | Route Shell | Rebuild the org editor route around the same `Sidebar.Provider` / `Sidebar.Root` / `Sidebar.Inset` structure used by the course editor | `apps/dashboard/src/routes/org/[slug]/settings/landingpage/edit/+page.svelte` | None | Full-screen editor behaves like the course editor and preview is rendered in the inset pane |
|
||||
| OLP-2 | Sidebar Width | Match the course editor sidebar width and spacing defaults | `apps/dashboard/src/routes/org/[slug]/settings/landingpage/edit/+page.svelte` | OLP-1 | Sidebar no longer looks visually broken or oversized |
|
||||
| OLP-3 | Component Split | Break the monolithic org editor into focused section components | `apps/dashboard/src/lib/features/settings/pages/landingpage-editor.svelte` + new section components | OLP-1 | Editor shell stays thin and section logic is isolated |
|
||||
| OLP-4 | Hero Section | Extract hero editing into its own form component | `apps/dashboard/src/lib/features/settings/pages/landingpage-editor/*` | OLP-3 | Hero controls render independently and update preview state live |
|
||||
| OLP-5 | Navigation Section | Extract navigation editing into its own form component | `apps/dashboard/src/lib/features/settings/pages/landingpage-editor/*` | OLP-3 | Nav items can be added, removed, and edited without a long conditional tree |
|
||||
| OLP-6 | Embed Section | Extract embed editing into its own form component | `apps/dashboard/src/lib/features/settings/pages/landingpage-editor/*` | OLP-3 | Embed fields render cleanly and iframe preview settings remain supported |
|
||||
| OLP-7 | Footer Section | Extract footer editing into its own form component | `apps/dashboard/src/lib/features/settings/pages/landingpage-editor/*` | OLP-3 | Footer text and links edit cleanly in a dedicated section |
|
||||
| OLP-8 | Menu Copy | Remove section descriptions from the org editor section list and section header | `apps/dashboard/src/lib/features/settings/pages/landingpage-editor.svelte` | OLP-3 | Sidebar matches the simpler course editor tone and density |
|
||||
| OLP-9 | Scroll Behavior | Make preview scrolling match the course editor behavior | `apps/dashboard/src/routes/org/[slug]/settings/landingpage/edit/+page.svelte`, org landing page theme components | OLP-1 | The preview pane scrolls independently and the sidebar stays stable |
|
||||
| OLP-10 | Theme Query Param | Keep theme preview driven by `?theme=` without publishing the theme on content edits | `apps/dashboard/src/lib/features/settings/pages/landingpage.svelte`, `apps/dashboard/src/routes/org/[slug]/settings/landingpage/edit/+page.svelte` | None | Clicking Edit Content previews the selected theme while leaving the live theme unchanged until an explicit settings save |
|
||||
| OLP-11 | Header Actions | Keep save/close behavior aligned with the course editor style | `apps/dashboard/src/lib/features/settings/pages/landingpage-editor.svelte` | OLP-1 | Save remains explicit and the editor header remains consistent |
|
||||
| OLP-12 | Regression Check | Verify build and key interaction flows after the refactor | workspace build commands | OLP-1 to OLP-11 | Dashboard build passes and the editor flow is stable |
|
||||
|
||||
## Structure Targets
|
||||
|
||||
### Route Shell
|
||||
|
||||
The org edit route should follow the same outer structure as the course landing page editor:
|
||||
|
||||
1. Full-screen fixed overlay.
|
||||
2. `Sidebar.Provider` as the top-level layout container.
|
||||
3. `Sidebar.Root` for the left editor panel.
|
||||
4. `Sidebar.Inset` for the live preview pane.
|
||||
5. One trigger button in the preview pane, consistent with the course editor.
|
||||
|
||||
### Editor Component Shape
|
||||
|
||||
The org editor should be a thin orchestration layer that:
|
||||
|
||||
1. Tracks selected section state.
|
||||
2. Renders a compact section list.
|
||||
3. Delegates form rendering to section-specific components.
|
||||
4. Handles save and close actions.
|
||||
5. Binds dirty state to the parent settings page.
|
||||
|
||||
### Section Components
|
||||
|
||||
Keep the editable sections aligned with the current org landing page model:
|
||||
|
||||
1. Hero
|
||||
2. Navigation
|
||||
3. Embed
|
||||
4. Footer
|
||||
|
||||
## UX Rules
|
||||
|
||||
1. No section descriptions in the org editor if the course editor does not show them.
|
||||
2. No long `if`/`else` chains in the main editor component when a section registry or split components can express the same behavior.
|
||||
3. The preview pane should be the only scrollable surface inside the editor shell.
|
||||
4. Save remains explicit. Theme selection alone should not publish the selected theme when the user is only editing content.
|
||||
5. The query-param theme preview should be preserved when navigating into and out of the content editor.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. The org landing page editor matches the course landing page editor composition closely enough that the two routes feel like siblings, not separate implementations.
|
||||
2. The org sidebar no longer appears broken or mis-sized.
|
||||
3. The preview pane scrolls correctly and does not trap or duplicate scrolling behavior.
|
||||
4. The org editor sections are split into small, readable components.
|
||||
5. Section descriptions are removed from the org editor UI.
|
||||
6. Opening the editor with a selected theme preview does not publish that theme until the user explicitly saves the settings page.
|
||||
7. `pnpm --filter @cio/dashboard build` passes after the refactor.
|
||||
|
||||
## Verification Commands
|
||||
|
||||
1. `pnpm --filter @cio/dashboard build`
|
||||
2. Manually verify `/org/[slug]/settings/landingpage`
|
||||
3. Manually verify `/org/[slug]/settings/landingpage/edit?theme=minimal`
|
||||
4. Manually verify `/org/[slug]/settings/landingpage/edit?theme=bold`
|
||||
5. Manually verify `/org/[slug]/settings/landingpage/edit?theme=classic`
|
||||
|
||||
Reference in New Issue
Block a user