diff --git a/ARCHITECHTURE.md b/ARCHITECHTURE.md index 893fe1f1..f8960a0e 100644 --- a/ARCHITECHTURE.md +++ b/ARCHITECHTURE.md @@ -1495,7 +1495,7 @@ Replace Supabase calls with API class method: **After (API Route):** ```typescript +``` + +#### Before (LMS): +```typescript +import { generateLmsBreadcrumbs } from './lms-breadcrumb'; +import { currentCommunityQuestion } from '$features/community/utils/store'; + +const breadcrumbTitle = $derived($currentCommunityQuestion.title); +const breadcrumbs = generateLmsBreadcrumbs( + page.url.pathname, + page.url.search, + navItems, + breadcrumbTitle ? { breadcrumb: breadcrumbTitle } : undefined +); +``` + +#### After (LMS - Reactive Store-Based): +```svelte + +``` + +### Step 3: Support Multi-Level Nesting (Up to 5 Layers) + +The refactored system supports up to 5 layers of nesting through recursive traversal. Here's how it works: + +#### How Deep Nesting Works + +1. **Recursive Processing**: The `generateNestedBreadcrumbs` function processes path segments recursively, incrementing a `depth` counter +2. **Depth Limit**: Default `maxDepth` is 10, but can be configured (supports up to 5 layers by default) +3. **Segment-by-Segment Matching**: Each segment is matched against nested route definitions +4. **Dynamic Title Resolution**: Dynamic segments resolve titles via `titleResolver` or `dynamicTitleResolver` + +#### Example: Courses → Course → Lesson → Assignment → Submission (5 Layers) + +**Navigation Config** (in `org-navigation.ts`): +```typescript +{ + titleKey: 'org_navigation.courses', + path: '/courses', + nestedRoutes: [ + { + path: '[id]', + titleKey: 'Course', + supportsDynamicSegment: true, + // Note: titleResolver can be sync (store-based) or async (API-based) + nestedRoutes: [ + { + path: 'lessons', + titleKey: 'Lessons', + nestedRoutes: [ + { + path: '[lessonId]', + titleKey: 'Lesson', + supportsDynamicSegment: true, + nestedRoutes: [ + { + path: 'assignments', + titleKey: 'Assignments', + nestedRoutes: [ + { + path: '[assignmentId]', + titleKey: 'Assignment', + supportsDynamicSegment: true, + nestedRoutes: [ + { + path: 'submissions', + titleKey: 'Submissions', + nestedRoutes: [ + { + path: '[submissionId]', + titleKey: 'Submission', + supportsDynamicSegment: true + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] +} +``` + +**Breadcrumb Component** (in `app-breadcrumbs.svelte`): +```svelte + +``` + +**Alternative: Per-Route Store Resolvers** (More Granular): +```svelte + +``` + +**URL**: `/org/myorg/courses/123/lessons/456/assignments/789/submissions/101` + +**Result**: `Courses / React Fundamentals / Lessons / Components / Assignments / Build Todo App / Submissions / Submission #101` + +**Key Benefit**: When stores update (e.g., `assignmentStore.title` changes), the breadcrumb automatically updates reactively without any API calls! + +#### Key Points for 5-Layer Nesting: + +1. **Each Level Must Define Nested Routes**: For nesting to work, each level must have `nestedRoutes` defined +2. **Dynamic Segments Need `supportsDynamicSegment: true`**: Mark dynamic segments explicitly +3. **Store-Based Resolvers Are Reactive**: Use stores instead of async requests for automatic updates +4. **Static Routes Are Checked First**: Static nested routes (like `lessons`, `assignments`) are matched before dynamic segments +5. **Recursive Depth Tracking**: The function tracks depth and stops at `maxDepth` (default: 10, supports 5 layers easily) + +#### Store-Based vs Async Resolvers + +**✅ Recommended: Store-Based (Reactive)** +```typescript +// In breadcrumb component +const config = $derived(createOrgBreadcrumbConfigWithStore( + $currentOrgPath, + () => $assignmentStore.title // Reads reactively from store +)); + +// Breadcrumbs update automatically when store changes! +const breadcrumbs = $derived(generateBreadcrumbsSync(...)); +``` + +**⚠️ Alternative: Async Resolvers (API Calls)** +```typescript +// In navigation config +titleResolver: async (params) => { + const assignment = await fetchAssignment(params.param0); + return assignment.title; +} +``` + +**Why Use Stores?** +- ✅ **No API calls** - data already loaded in component +- ✅ **Automatic updates** - breadcrumb updates when store changes +- ✅ **Better performance** - no loading states or delays +- ✅ **Reactive** - works seamlessly with Svelte's reactivity system + +#### Parameter Extraction for Deep Nesting + +The `extractParamsFromPath` function extracts all path segments as `param0`, `param1`, `param2`, etc.: + +```typescript +// For URL: /courses/123/lessons/456/assignments/789 +// params = { +// param0: '123', // courseId +// param1: '456', // lessonId +// param2: '789' // assignmentId +// } +``` + +**Note**: When using stores, you typically don't need these params - just read from the store directly! + +### Step 4: Add Route-Specific Overrides + +```typescript +{ + titleKey: 'org_navigation.courses', + path: '/courses', + breadcrumbLabel: 'My Courses', // Override default title + breadcrumbResolver: (params) => { + // Custom logic for this route + return `Courses (${params.count || ''})`; + } +} +``` + +### Step 5: Use Async Resolvers When Needed + +For routes that need async data fetching: + +```typescript +import { generateBreadcrumbs } from './breadcrumb-core'; // Note: async version + +const breadcrumbs = await generateBreadcrumbs( + page.url.pathname, + page.url.search, + navItems, + config +); +``` + +## Benefits of the Refactored System + +### ✅ 1. No Code Duplication +- Single unified generator for all contexts +- Context-specific configs handle differences + +### ✅ 2. Multi-Level Nesting Support +- Recursive generation handles any depth +- Configurable max depth prevents infinite loops + +### ✅ 3. Flexible Path Prefixes +- Function-based prefix extraction +- Supports dynamic prefixes like `/org/[slug]` + +### ✅ 4. Consistent Dynamic Title Resolution +- Unified interface for all resolvers +- Supports both sync and async resolution + +### ✅ 5. Route-Specific Customization +- Override labels per route +- Custom resolvers for complex cases + +### ✅ 6. Optional Caching +- Performance optimization +- Configurable cache TTL + +### ✅ 7. Type Safety +- Full TypeScript support +- Compile-time error checking + +## Example: Complex Multi-Level Route (5 Layers) + +**URL**: `/org/myorg/courses/123/lessons/456/assignments/789/submissions/101` + +**Config**: +```typescript +{ + path: '/courses', + nestedRoutes: [ + { + path: '[id]', + supportsDynamicSegment: true, + titleResolver: async (params) => { + const course = await getCourse(params.param0); + return course.title; + }, + nestedRoutes: [ + { + path: 'lessons', + titleKey: 'Lessons', + nestedRoutes: [ + { + path: '[lessonId]', + supportsDynamicSegment: true, + titleResolver: async (params) => { + const lesson = await getLesson(params.param0, params.param1); + return lesson.title; + }, + nestedRoutes: [ + { + path: 'assignments', + titleKey: 'Assignments', + nestedRoutes: [ + { + path: '[assignmentId]', + supportsDynamicSegment: true, + titleResolver: async (params) => { + const assignment = await getAssignment( + params.param0, // courseId + params.param1, // lessonId + params.param2 // assignmentId + ); + return assignment.title; + }, + nestedRoutes: [ + { + path: 'submissions', + titleKey: 'Submissions', + nestedRoutes: [ + { + path: '[submissionId]', + supportsDynamicSegment: true, + titleResolver: async (params) => { + const submission = await getSubmission( + params.param0, // courseId + params.param1, // lessonId + params.param2, // assignmentId + params.param3 // submissionId + ); + return `Submission #${submission.id}`; + } + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] + } + ] +} +``` + +**Result**: `Courses / React Fundamentals / Lessons / Components / Assignments / Build Todo App / Submissions / Submission #101` + +### How the Recursion Works for 5 Layers: + +1. **Initial Call** (`depth=0`): Processes `/courses` → finds nav item → adds "Courses" +2. **First Recursion** (`depth=1`): Processes `123` → dynamic segment → resolves course title → adds "React Fundamentals" +3. **Second Recursion** (`depth=2`): Processes `lessons` → static nested route → adds "Lessons" +4. **Third Recursion** (`depth=3`): Processes `456` → dynamic segment → resolves lesson title → adds "Components" +5. **Fourth Recursion** (`depth=4`): Processes `assignments` → static nested route → adds "Assignments" +6. **Fifth Recursion** (`depth=5`): Processes `789` → dynamic segment → resolves assignment title → adds "Build Todo App" +7. **Sixth Recursion** (`depth=6`): Processes `submissions` → static nested route → adds "Submissions" +8. **Seventh Recursion** (`depth=7`): Processes `101` → dynamic segment → resolves submission → adds "Submission #101" + +**Depth Limit**: Default `maxDepth` is 10, so this 5-layer example (which uses depth 7) works perfectly. + +## How 5 Layers of Nesting Works + +### Depth Calculation + +The recursive function processes **each path segment** separately, incrementing depth on each recursive call. For a 5-layer breadcrumb path: + +**Example Path**: `/courses/123/lessons/456/assignments/789/submissions/101` + +**Breadcrumb Result**: `Courses / React Fundamentals / Lessons / Components / Assignments / Build Todo App / Submissions / Submission #101` + +**Depth Breakdown**: +- `depth=0`: Top-level nav item (`/courses`) → "Courses" +- `depth=1`: First dynamic segment (`123`) → "React Fundamentals" +- `depth=2`: Static nested route (`lessons`) → "Lessons" +- `depth=3`: Second dynamic segment (`456`) → "Components" +- `depth=4`: Static nested route (`assignments`) → "Assignments" +- `depth=5`: Third dynamic segment (`789`) → "Build Todo App" +- `depth=6`: Static nested route (`submissions`) → "Submissions" +- `depth=7`: Fourth dynamic segment (`101`) → "Submission #101" + +**Total Depth**: 7 levels (includes static route labels) + +**Default `maxDepth`**: 10, which easily supports 5 layers of actual content nesting. + +### Recursive Processing Flow + +```typescript +generateNestedBreadcrumbsSync(segments, navItem, currentPath, ..., depth=0) + ├─ Process currentSegment (segments[0]) + ├─ Check: Is it a static nested route? + │ ├─ YES: Add breadcrumb → Recurse with remaining segments (depth+1) + │ └─ NO: Continue to dynamic check + ├─ Check: Is it a dynamic segment? + │ ├─ YES: Resolve title → Add breadcrumb → Recurse with remaining segments (depth+1) + │ └─ NO: Return (no more breadcrumbs) + └─ Stop if: depth >= maxDepth OR segments.length === 0 +``` + +### Key Implementation Details + +1. **Segment-by-Segment Processing**: Each recursive call processes ONE segment and passes remaining segments to the next call +2. **Static Routes First**: Static nested routes (like `lessons`, `assignments`) are checked before dynamic segments +3. **Nested Route Matching**: When a static route is found, its `nestedRoutes` become the `navItem.nestedRoutes` for the next recursion +4. **Dynamic Segment Handling**: When a dynamic segment is found, it resolves the title and continues with remaining segments if `nestedRoutes` exist +5. **Depth Safety**: The `maxDepth` check prevents infinite recursion (default: 10, configurable) + +### Why This Supports 5 Layers + +- **Default `maxDepth` of 10** provides plenty of headroom for 5 layers +- **Recursive structure** naturally handles any depth +- **Each layer increments depth by 1**, so 5 layers = depth 5-7 (depending on static route labels) +- **Configurable `maxDepth`** allows adjustment if needed + +## Performance Considerations + +1. **Caching**: Enable for routes with expensive resolvers +2. **Async Resolvers**: Use sparingly - prefer sync resolvers when possible +3. **Max Depth**: Default is 10 (supports 5 layers easily), but can be customized: + ```svelte + + ``` +4. **Cache Invalidation**: Clear cache when navigation config changes +5. **Recursive Calls**: Each nested level adds one recursive call - 5 layers = ~7-10 calls total (very efficient) + +## Testing + +```typescript +import { generateBreadcrumbsSync, clearBreadcrumbCache } from './breadcrumb-core'; + +// Clear cache before tests +beforeEach(() => { + clearBreadcrumbCache(); +}); + +// Test breadcrumb generation +test('generates breadcrumbs for nested route', () => { + const breadcrumbs = generateBreadcrumbsSync( + '/org/test/courses/123/lessons/456', + '', + navItems, + config + ); + + expect(breadcrumbs).toHaveLength(4); + expect(breadcrumbs[0].label).toBe('Courses'); +}); +``` + +## Backward Compatibility + +The old functions (`generateBreadcrumbs`, `generateLmsBreadcrumbs`) can be kept as wrappers: + +```typescript +// breadcrumb.ts (legacy wrapper - using store-based approach) +import { generateBreadcrumbsSync } from './breadcrumb-core'; +import { createOrgBreadcrumbConfigWithStore } from './breadcrumb-configs'; +import { courseStore } from '$features/course/utils/store'; + +export function generateBreadcrumbs( + pathname: string, + searchParams: string, + navItems: NavItem[], + currentOrgPath: string, + getStoreValue?: () => string | null | undefined +) { + const config = createOrgBreadcrumbConfigWithStore( + currentOrgPath, + getStoreValue || (() => $courseStore.title || null) + ); + return generateBreadcrumbsSync(pathname, searchParams, navItems, config); +} +``` + +This allows gradual migration without breaking existing code. + +## Reactive Store-Based Title Resolution + +### Why Use Stores Instead of Async Requests? + +**✅ Benefits:** +- **No API calls** - Data is already loaded in your component +- **Automatic updates** - Breadcrumbs update reactively when stores change +- **Better performance** - No loading states, delays, or network requests +- **Simpler code** - Just read from stores, no async/await needed +- **Reactive by default** - Works seamlessly with Svelte's reactivity system + +### How It Works + +1. **Store Setup**: Your component/page loads data into a store +2. **Reactive Config**: Create breadcrumb config with a function that reads from stores +3. **Automatic Updates**: When store changes, breadcrumb automatically updates + +### Example: Community Post Title + +**Store** (`$features/community/utils/store.ts`): +```typescript +export const currentCommunityQuestion = writable<{ title: string | null }>({ + title: null +}); +``` + +**Page Component** (`+page.svelte`): +```svelte + +``` + +**Breadcrumb Component** (`lms-breadcrumbs.svelte`): +```svelte + +``` + +### Pattern for Multiple Stores + +For routes with multiple dynamic segments, use path-based logic: + +```svelte + +``` + +### Important Notes + +1. **Disable Caching**: When using stores, set `enableCache: false` in config (or use `createOrgBreadcrumbConfigWithStore` which does this automatically) +2. **Use `$derived`**: Always wrap config and breadcrumbs in `$derived` for reactivity +3. **Store Updates**: When you update a store, breadcrumbs automatically update - no manual refresh needed! +4. **No Async Needed**: Store-based resolvers are synchronous - just return the store value + diff --git a/apps/dashboard/ai/PAGE_ROUTE_GUIDE.md b/apps/dashboard/ai/PAGE_ROUTE_GUIDE.md new file mode 100644 index 00000000..b298ede5 --- /dev/null +++ b/apps/dashboard/ai/PAGE_ROUTE_GUIDE.md @@ -0,0 +1,846 @@ +# Page and Route Creation Guide + +This guide explains how to create pages and routes in the dashboard application, following the established patterns and architecture. + +## Table of Contents + +1. [Route Structure Overview](#route-structure-overview) +2. [Creating Basic Pages](#creating-basic-pages) +3. [Creating Settings Pages](#creating-settings-pages) +4. [Creating Feature Pages](#creating-feature-pages) +5. [Layout Patterns](#layout-patterns) +6. [Form Patterns (Field.Group)](#form-patterns-fieldgroup) +7. [Navigation Setup](#navigation-setup) +8. [Breadcrumb Setup](#breadcrumb-setup) + +--- + +## Route Structure Overview + +The dashboard has two main route contexts: + +### 1. Organization Routes (`/org/[slug]/...`) +- Uses `OrgSidebar` and `AppHeader` +- Path pattern: `/org/[slug]/[feature]` +- Example: `/org/acme/courses`, `/org/acme/settings` + +### 2. LMS Routes (`/lms/...`) +- Uses `LMSSidebar` and `LmsHeader` +- Path pattern: `/lms/[feature]` +- Example: `/lms/community`, `/lms/settings` + +--- + +## Creating Basic Pages + +### Step 1: Create the Route File + +**Location**: `apps/dashboard/src/routes/[context]/[feature]/+page.svelte` + +**Pattern**: +```svelte + + + + Feature Name - ClassroomIO + + + + + + {$t('feature.title')} + + + + + + + {#snippet child()} + + {/snippet} + + +``` + +### Step 2: Create the Feature Page Component + +**Location**: `apps/dashboard/src/lib/features/[feature]/pages/[feature].svelte` + +**Pattern**: +```svelte + + + + + + + + + +
+ {#if isLoading} + + {:else if !data.length} + + + + {:else} + {#each data as item} + + {/each} + {/if} +
+``` + +### Step 3: Export the Page + +**Location**: `apps/dashboard/src/lib/features/[feature]/pages/index.ts` + +```typescript +export { default as FeaturePage } from './feature.svelte'; +``` + +--- + +## Creating Settings Pages + +Settings pages follow a specific pattern with layouts and subroutes. + +### Step 1: Create Settings Layout + +**Location**: `apps/dashboard/src/routes/[context]/settings/+layout.svelte` + +```svelte + + + + {@render children?.()} + +``` + +### Step 2: Create Default Settings Page + +**Location**: `apps/dashboard/src/routes/[context]/settings/+page.svelte` + +```svelte + + + + Settings - ClassroomIO + + + + + {$t('settings.heading')} + + + + + + + {#snippet child()} + + {/snippet} + +``` + +### Step 3: Create Settings Subroute + +**Location**: `apps/dashboard/src/routes/[context]/settings/[subroute]/+page.svelte` + +```svelte + + + + Subroute Name - ClassroomIO + + + + + {$t('settings.subroute.heading')} + + + + {#snippet child()} + + {/snippet} + +``` + +**Note**: Settings pages use subroutes instead of tabs. Each subroute has its own `+page.svelte` file. + +--- + +## Creating Feature Pages + +### Feature Page Structure + +Feature pages contain all business logic and should be placed in `lib/features/[feature]/pages/`. + +**Key Principles**: +- ✅ All business logic goes in the feature page +- ✅ Use `Page.BodyHeader` for filters, search, and view toggles +- ✅ Use `Empty` component for empty states +- ✅ Use `Search` component for search inputs +- ✅ Handle loading states with skeleton/loader components +- ❌ Don't use `Page.Header` or `Page.Action` in feature pages (these are in route files) + +### Example Feature Page + +```svelte + + + + + + + + + + All + + + + + +
+ {#if isLoading} + + + + {:else if !discussions.length} + + + + {:else} + {#each discussions as discussion} + + {/each} + {/if} +
+``` + +--- + +## Layout Patterns + +### Organization Layout + +**Location**: `apps/dashboard/src/routes/org/[slug]/+layout.svelte` + +```svelte + + + + + + + + +
+ {@render children?.()} +
+
+
+``` + +### LMS Layout + +**Location**: `apps/dashboard/src/routes/lms/+layout.svelte` + +```svelte + + + + + + + + +
+ {@render children?.()} +
+
+
+``` + +**Key Differences**: +- Org routes use `AppHeader` (includes AppSetup) +- LMS routes use `LmsHeader` (no AppSetup) +- Org routes use `OrgSidebar` +- LMS routes use `LMSSidebar` + +--- + +## Form Patterns (Field.Group) + +**IMPORTANT**: Always use `Field.Group` pattern for forms. The old `Row/Grid/Column` pattern is deprecated and removed. + +### Field.Group Pattern + +**Location**: `apps/dashboard/src/lib/features/[feature]/pages/[feature].svelte` + +```svelte + + + + + + + {$t('form.section_title')} + + + {$t('form.name')} + (hasUnsavedChanges = true)} /> + + + {$t('form.email')} + (hasUnsavedChanges = true)} /> + + + (hasUnsavedChanges = true)} /> + {$t('form.enabled')} + + + + + + + + {$t('form.another_section')} + + (hasUnsavedChanges = true)} + /> + + + +``` + +### Field Components + +- `` - Main container (use `class="max-w-md! w-full px-2"`) +- `` - Section container +- `` - Section title +- `` - Individual field wrapper +- `` - Field label +- `` - Field description/help text +- `` - Field error message +- `` - Visual separator between sections +- `orientation="horizontal"` - For switches/checkboxes with labels side-by-side + +### Pattern Rules + +- ✅ DO: Use `Field.Group` as the main container +- ✅ DO: Use `Field.Set` for logical sections +- ✅ DO: Use `Field.Separator` between sections +- ✅ DO: Use `Field.Legend` for section titles +- ✅ DO: Wrap form inputs in `Field.Field` +- ✅ DO: Use `Field.Label` for input labels +- ✅ DO: Use `orientation="horizontal"` for switches/checkboxes +- ❌ DON'T: Use `Row`, `Grid`, or `Column` components (deprecated) +- ❌ DON'T: Mix Field.Group with old layout components + +--- + +## Navigation Setup + +### Adding Items to Organization Navigation + +**Location**: `apps/dashboard/src/lib/features/ui/navigation/org-navigation.ts` + +```typescript +const baseNavConfig: NavItemConfig[] = [ + // ... existing items + { + titleKey: 'org_navigation.new_feature', + path: '/new-feature', + icon: NewFeatureIcon, + matchPattern: '^/org/[^/]+/new-feature(/.*)?$', + // Optional: requires admin access + requiresAdmin: true, + // Optional: nested routes + nestedRoutes: [ + { + path: 'subroute', + titleKey: 'Subroute Name' + } + ], + // Optional: supports dynamic segments + supportsDynamicSegment: true + } +]; +``` + +### Adding Items to LMS Navigation + +**Location**: `apps/dashboard/src/lib/features/ui/navigation/lms-navigation.ts` + +```typescript +const baseNavConfig: NavItemConfig[] = [ + // ... existing items + { + titleKey: 'lms_navigation.new_feature', + path: '/new-feature', + icon: NewFeatureIcon, + matchPattern: '^/lms/new-feature(/.*)?$', + // Optional: conditional display + show: (currentOrg) => currentOrg?.customization?.feature?.enabled === true, + // Optional: nested routes + nestedRoutes: [ + { + path: 'subroute', + titleKey: 'Subroute Name' + } + ], + // Optional: supports dynamic segments + supportsDynamicSegment: true + } +]; +``` + +### Navigation Item Properties + +- `titleKey` - Translation key for the navigation label +- `path` - Route path (relative to context) +- `icon` - Lucide icon component +- `matchPattern` - Regex pattern for active state matching +- `requiresAdmin` - Only show for admin users (org navigation only) +- `show` - Function to conditionally show/hide item +- `nestedRoutes` - Static nested routes (like `community/ask`) +- `supportsDynamicSegment` - Supports dynamic routes (like `community/[slug]`) +- `useHashUrl` - Use '#' for collapsible parent (settings menu) +- `items` - Nested items for collapsible menus (settings sub-items) + +--- + +## Breadcrumb Setup + +Breadcrumbs are automatically generated from navigation configuration. + +### For Organization Routes + +Breadcrumbs use `generateBreadcrumbs()` from `breadcrumb.ts` which reads from `org-navigation.ts`. + +### For LMS Routes + +Breadcrumbs use `generateLmsBreadcrumbs()` from `lms-breadcrumb.ts` which reads from `lms-navigation.ts`. + +### Adding Dynamic Breadcrumbs + +For dynamic routes (like `community/[slug]`), add breadcrumb data in the route's `+page.ts`: + +```typescript +// routes/org/[slug]/community/[slug]/+page.ts +export async function load({ params }) { + // Fetch question data + const question = await fetchQuestion(params.slug); + + return { + breadcrumb: question.title // This will appear in breadcrumbs + }; +} +``` + +--- + +## Complete Examples + +### Example 1: Simple Feature Page + +**Route**: `routes/org/[slug]/courses/+page.svelte` +```svelte + + + + Courses - ClassroomIO + + + + + + {$t('courses.title')} + + + + {#snippet child()} + + {/snippet} + + +``` + +### Example 2: Settings Page with Save Action + +**Route**: `routes/org/[slug]/settings/customize-lms/+page.svelte` +```svelte + + + + Customize LMS - ClassroomIO + + + + + + {$t('components.settings.customize_lms.title')} + + + + + + + {#snippet child()} + + {/snippet} + + +``` + +### Example 3: Form Page Component + +**Feature Page**: `lib/features/settings/pages/profile.svelte` +```svelte + + + + + + + {$t('settings.profile.profile_picture.heading')} + + (hasUnsavedChanges = true)} + /> + + + + + + + {$t('settings.profile.personal_information.heading')} + + + {$t('settings.profile.personal_information.full_name')} + (hasUnsavedChanges = true)} /> + {#if profileApi.errors.fullname} + {$t(profileApi.errors.fullname)} + {/if} + + + + + +``` + +--- + +## Checklist for New Pages + +When creating a new page, follow this checklist: + +### Route Setup +- [ ] Create route file at `routes/[context]/[feature]/+page.svelte` +- [ ] Add `Page.Root`, `Page.Header`, `Page.Body` structure +- [ ] Add `` with page title +- [ ] Import and render feature page component + +### Feature Page +- [ ] Create feature page at `lib/features/[feature]/pages/[feature].svelte` +- [ ] Export from `lib/features/[feature]/pages/index.ts` +- [ ] Use `Page.BodyHeader` for filters/search +- [ ] Use `Empty` component for empty states +- [ ] Handle loading states + +### Forms (if applicable) +- [ ] Use `Field.Group` pattern (NOT Row/Grid/Column) +- [ ] Use `Field.Set` for sections +- [ ] Use `Field.Separator` between sections +- [ ] Use `Field.Legend` for section titles +- [ ] Wrap inputs in `Field.Field` +- [ ] Add `UnsavedChanges` component if needed + +### Navigation (if needed) +- [ ] Add item to `org-navigation.ts` or `lms-navigation.ts` +- [ ] Set appropriate `matchPattern` for active state +- [ ] Add icon component +- [ ] Configure nested routes if needed +- [ ] Configure dynamic segments if needed + +### Settings Pages (if applicable) +- [ ] Create `+layout.svelte` with `Page.Root` wrapper +- [ ] Create default `+page.svelte` for main settings +- [ ] Create subroute `+page.svelte` files for each subroute +- [ ] Export page components from `$features/settings/pages` + +--- + +## Common Patterns + +### Page Width Classes + +- `class="w-full"` - Full width +- `class="w-full md:max-w-3xl lg:mx-auto"` - Centered, max width on larger screens (settings pages) +- `class="w-full md:max-w-4xl lg:mx-auto"` - Slightly wider (customize-lms) +- `class="mx-auto w-full max-w-6xl"` - Wide content area (main layouts) + +### Page Actions + +Actions in `Page.Action` typically include: +- Save/Update buttons +- Create/Add buttons +- Export buttons +- Feature-specific action buttons + +### Loading States + +Use skeleton loaders or spinner components: +```svelte +{#if isLoading} + + + +{:else} + +{/if} +``` + +### Empty States + +Always use the `Empty` component: +```svelte + + + +``` + +--- + +## Best Practices + +1. **Separation of Concerns**: Route files handle routing structure, feature pages handle business logic +2. **Reusability**: Feature pages can be imported and used in different contexts +3. **Consistency**: All pages follow the same structure for maintainability +4. **Design System**: Use established components (`Page.*`, `Field.*`, `Empty`, `Search`) +5. **State Management**: Keep state local to feature pages or use feature-specific stores +6. **Translation Keys**: Always use translation keys from `$t()` function +7. **Type Safety**: Use TypeScript types for props and state +8. **Accessibility**: Use semantic HTML and proper ARIA labels + +--- + +## Migration from Old Patterns + +If you encounter old patterns, migrate them: + +### Old: Row/Grid/Column Pattern +```svelte + + + + + Title + + + + + + +``` + +### New: Field.Group Pattern +```svelte + + + + Title + + Label + + + + +``` + +--- + +## Additional Resources + +- See `ROUTE_PAGE_SETUP.md` for more details on feature page structure +- See existing pages in `routes/org/[slug]/` and `routes/lms/` for examples +- See `lib/features/settings/pages/` for form examples +- See `lib/features/community/pages/` for list page examples + diff --git a/apps/dashboard/ROUTE_PAGE_SETUP.md b/apps/dashboard/ai/ROUTE_PAGE_SETUP.md similarity index 95% rename from apps/dashboard/ROUTE_PAGE_SETUP.md rename to apps/dashboard/ai/ROUTE_PAGE_SETUP.md index ef808659..8baeb99b 100644 --- a/apps/dashboard/ROUTE_PAGE_SETUP.md +++ b/apps/dashboard/ai/ROUTE_PAGE_SETUP.md @@ -45,8 +45,8 @@ The route file should be minimal and only contain: ```svelte @@ -75,8 +75,8 @@ The route file should be minimal and only contain: ### Route File Rules - ✅ DO: Keep it minimal - only Page structure -- ✅ DO: Import feature page from `$lib/features/[feature]/pages` -- ✅ DO: Import action buttons/components from `$lib/features/[feature]/components` +- ✅ DO: Import feature page from `$features/[feature]/pages` +- ✅ DO: Import action buttons/components from `$features/[feature]/components` - ✅ DO: Use `Page.Root`, `Page.Header`, `Page.HeaderContent`, `Page.Title`, `Page.Action`, `Page.Body` - ❌ DON'T: Put business logic in route files - ❌ DON'T: Put state management in route files @@ -110,7 +110,6 @@ The feature page contains all the business logic, state management, and UI compo let isLoading = $state(false); let searchValue = $state(''); let selectedId = $state(''); - @@ -194,8 +193,8 @@ export { default as FeaturePage } from './feature.svelte'; ```svelte diff --git a/apps/dashboard/src/app.css b/apps/dashboard/src/app.css index 2ae336a6..8f9b57bf 100644 --- a/apps/dashboard/src/app.css +++ b/apps/dashboard/src/app.css @@ -99,24 +99,14 @@ code { color: var(--main-primary-color); } -svg.lucide-icon:not(.custom) { +:not([data-slot='button']) > svg.lucide-icon:not(.custom) { color: #666666; } -.dark svg.lucide-icon:not(.custom) { +.dark :not([data-slot='button']) > svg.lucide-icon:not(.custom) { color: #fff; } -.org-root { - height: calc(100vh - 48px); -} - -.org-slot { - min-width: calc(100vw - 250px); - height: calc(100vh - 48px); - overflow-y: auto; -} - .shake { animation: shake 0.85s; } @@ -168,980 +158,382 @@ svg.lucide-icon:not(.custom) { } } -/** - Rose Theme -*/ -.theme-rose .bg-primary-50 { - background-color: #fff1f2 !important; -} - -.theme-rose .text-primary-50 { - color: #fff1f2 !important; -} - -.theme-rose .border-primary-50 { - border-color: #fff1f2 !important; -} - -.theme-rose .bg-primary-100 { - background-color: #ffe4e7 !important; -} - -.theme-rose .text-primary-100 { - color: #ffe4e7 !important; -} - -.theme-rose .border-primary-100 { - border-color: #ffe4e7 !important; -} - -.theme-rose .bg-primary-200 { - background-color: #fecdd4 !important; -} - -.theme-rose .text-primary-200 { - color: #fecdd4 !important; -} - -.theme-rose .border-primary-200 { - border-color: #fecdd4 !important; -} - -.theme-rose .bg-primary-300 { - background-color: #fda4b2 !important; -} - -.theme-rose .text-primary-300 { - color: #fda4b2 !important; -} - -.theme-rose .border-primary-300 { - border-color: #fda4b2 !important; -} - -.theme-rose .bg-primary-400 { - background-color: #fb7189 !important; -} - -.theme-rose .text-primary-400 { - color: #fb7189 !important; -} - -.theme-rose .border-primary-400 { - border-color: #fb7189 !important; -} - -.theme-rose .bg-primary-500 { - background-color: #f43f63 !important; -} - -.theme-rose .text-primary-500 { - color: #f43f63 !important; -} - -.theme-rose .border-primary-500 { - border-color: #f43f63 !important; -} - -.theme-rose .bg-primary-600 { - background-color: #e21d4e !important; -} - -.theme-rose .text-primary-600 { - color: #e21d4e !important; -} - -.theme-rose .border-primary-600 { - border-color: #e21d4e !important; -} - -.theme-rose .bg-primary-700 { - background-color: #be1241 !important; -} - -.theme-rose .text-primary-700 { - color: #be1241 !important; -} - -.theme-rose .border-primary-700 { - border-color: #be1241 !important; -} - -.theme-rose .bg-primary-800 { - background-color: #9f123d !important; -} - -.theme-rose .text-primary-800 { - color: #9f123d !important; -} - -.theme-rose .border-primary-800 { - border-color: #9f123d !important; -} - -.theme-rose .bg-primary-900 { - background-color: #88133a !important; -} - -.theme-rose .text-primary-900 { - color: #88133a !important; -} - -.theme-rose .border-primary-900 { - border-color: #88133a !important; -} - -/** - Blue Theme -*/ -.bg-primary-50 { - background-color: #eff6ff !important; -} - -.text-primary-50 { - color: #eff6ff !important; -} - -.border-primary-50 { - border-color: #eff6ff !important; -} - -.bg-primary-100 { - background-color: #dbeafe !important; -} - -.text-primary-100 { - color: #dbeafe !important; -} - -.border-primary-100 { - border-color: #dbeafe !important; -} - -.bg-primary-200 { - background-color: #bfdafe !important; -} - -.text-primary-200 { - color: #bfdafe !important; -} - -.border-primary-200 { - border-color: #bfdafe !important; -} - -.bg-primary-300 { - background-color: #92c4fe !important; -} - -.text-primary-300 { - color: #92c4fe !important; -} - -.border-primary-300 { - border-color: #92c4fe !important; -} - -.bg-primary-400 { - background-color: #5fa3fb !important; -} - -.text-primary-400 { - color: #5fa3fb !important; -} - -.border-primary-400 { - border-color: #5fa3fb !important; -} - -.bg-primary-500 { - background-color: #3a7ff7 !important; -} - -.text-primary-500 { - color: #3a7ff7 !important; -} - -.border-primary-500 { - border-color: #3a7ff7 !important; -} - -.bg-primary-600 { - background-color: #2460ec !important; -} - -.text-primary-600 { - color: #2460ec !important; -} - -.border-primary-600 { - border-color: #2460ec !important; -} - -.bg-primary-700 { - background-color: #1d4ee2 !important; -} - -.text-primary-700 { - color: #1d4ee2 !important; -} - -.border-primary-700 { - border-color: #1d4ee2 !important; -} - -.bg-primary-800 { - background-color: #1d3eb0 !important; -} - -.text-primary-800 { - color: #1d3eb0 !important; -} - -.border-primary-800 { - border-color: #1d3eb0 !important; -} - -.bg-primary-900 { - background-color: #1d398b !important; -} - -.text-primary-900 { - color: #1d398b !important; -} - -.border-primary-900 { - border-color: #1d398b !important; -} - -/** - Green Theme -*/ -.theme-green .bg-primary-50 { - background-color: #effef0 !important; -} - -.theme-green .text-primary-50 { - color: #effef0 !important; -} - -.theme-green .border-primary-50 { - border-color: #effef0 !important; -} - -.theme-green .bg-primary-100 { - background-color: #d9ffdc !important; -} - -.theme-green .text-primary-100 { - color: #d9ffdc !important; -} - -.theme-green .border-primary-100 { - border-color: #d9ffdc !important; -} - -.theme-green .bg-primary-200 { - background-color: #b6fcbc !important; -} - -.theme-green .text-primary-200 { - color: #b6fcbc !important; -} - -.theme-green .border-primary-200 { - border-color: #b6fcbc !important; -} - -.theme-green .bg-primary-300 { - background-color: #7df88a !important; -} - -.theme-green .text-primary-300 { - color: #7df88a !important; -} - -.theme-green .border-primary-300 { - border-color: #7df88a !important; -} - -.theme-green .bg-primary-400 { - background-color: #3deb51 !important; -} - -.theme-green .text-primary-400 { - color: #3deb51 !important; -} - -.theme-green .border-primary-400 { - border-color: #3deb51 !important; -} - -.theme-green .bg-primary-500 { - background-color: #12bf26 !important; -} - -.theme-green .text-primary-500 { - color: #12bf26 !important; -} - -.theme-green .border-primary-500 { - border-color: #12bf26 !important; -} - -.theme-green .bg-primary-600 { - background-color: #0aaf1d !important; -} - -.theme-green .text-primary-600 { - color: #0aaf1d !important; -} - -.theme-green .border-primary-600 { - border-color: #0aaf1d !important; -} - -.theme-green .bg-primary-700 { - background-color: #0c891b !important; -} - -.theme-green .text-primary-700 { - color: #0c891b !important; -} - -.theme-green .border-primary-700 { - border-color: #0c891b !important; -} - -.theme-green .bg-primary-800 { - background-color: #0f6c1b !important; -} - -.theme-green .text-primary-800 { - color: #0f6c1b !important; -} - -.theme-green .border-primary-800 { - border-color: #0f6c1b !important; -} - -.theme-green .bg-primary-900 { - background-color: #0f581a !important; -} - -.theme-green .text-primary-900 { - color: #0f581a !important; -} - -.theme-green .border-primary-900 { - border-color: #0f581a !important; -} - -/** - Orange Theme -*/ -.theme-orange .bg-primary-50 { - background-color: #fff8ec !important; -} - -.theme-orange .text-primary-50 { - color: #fff8ec !important; -} - -.theme-orange .border-primary-50 { - border-color: #fff8ec !important; -} - -.theme-orange .bg-primary-100 { - background-color: #fff0d3 !important; -} - -.theme-orange .text-primary-100 { - color: #fff0d3 !important; -} - -.theme-orange .border-primary-100 { - border-color: #fff0d3 !important; -} - -.theme-orange .bg-primary-200 { - background-color: #ffdca5 !important; -} - -.theme-orange .text-primary-200 { - color: #ffdca5 !important; -} - -.theme-orange .border-primary-200 { - border-color: #ffdca5 !important; -} - -.theme-orange .bg-primary-300 { - background-color: #ffc26d !important; -} - -.theme-orange .text-primary-300 { - color: #ffc26d !important; -} - -.theme-orange .border-primary-300 { - border-color: #ffc26d !important; -} - -.theme-orange .bg-primary-400 { - background-color: #ff9d32 !important; -} - -.theme-orange .text-primary-400 { - color: #ff9d32 !important; -} - -.theme-orange .border-primary-400 { - border-color: #ff9d32 !important; -} - -.theme-orange .bg-primary-500 { - background-color: #ff7f0a !important; -} - -.theme-orange .text-primary-500 { - color: #ff7f0a !important; -} - -.theme-orange .border-primary-500 { - border-color: #ff7f0a !important; -} - -.theme-orange .bg-primary-600 { - background-color: #ff6600 !important; -} - -.theme-orange .text-primary-600 { - color: #ff6600 !important; -} - -.theme-orange .border-primary-600 { - border-color: #ff6600 !important; -} - -.theme-orange .bg-primary-700 { - background-color: #cc4902 !important; -} - -.theme-orange .text-primary-700 { - color: #cc4902 !important; -} - -.theme-orange .border-primary-700 { - border-color: #cc4902 !important; -} - -.theme-orange .bg-primary-800 { - background-color: #a1390b !important; -} - -.theme-orange .text-primary-800 { - color: #a1390b !important; -} - -.theme-orange .border-primary-800 { - border-color: #a1390b !important; -} - -.theme-orange .bg-primary-900 { - background-color: #82310c !important; -} - -.theme-orange .text-primary-900 { - color: #82310c !important; -} - -.theme-orange .border-primary-900 { - border-color: #82310c !important; -} - -/** - Violet Theme -*/ -.theme-violet .bg-primary-50 { - background-color: #fff2fe !important; -} - -.theme-violet .text-primary-50 { - color: #fff2fe !important; -} - -.theme-violet .border-primary-50 { - border-color: #fff2fe !important; -} - -.theme-violet .bg-primary-100 { - background-color: #ffe3fe !important; -} - -.theme-violet .text-primary-100 { - color: #ffe3fe !important; -} - -.theme-violet .border-primary-100 { - border-color: #ffe3fe !important; -} - -.theme-violet .bg-primary-200 { - background-color: #ffc6fe !important; -} - -.theme-violet .text-primary-200 { - color: #ffc6fe !important; -} - -.theme-violet .border-primary-200 { - border-color: #ffc6fe !important; -} - -.theme-violet .bg-primary-300 { - background-color: #ff99f7 !important; -} - -.theme-violet .text-primary-300 { - color: #ff99f7 !important; -} - -.theme-violet .border-primary-300 { - border-color: #ff99f7 !important; -} - -.theme-violet .bg-primary-400 { - background-color: #ff5df2 !important; -} - -.theme-violet .text-primary-400 { - color: #ff5df2 !important; -} - -.theme-violet .border-primary-400 { - border-color: #ff5df2 !important; -} - -.theme-violet .bg-primary-500 { - background-color: #ff21f5 !important; -} - -.theme-violet .text-primary-500 { - color: #ff21f5 !important; -} - -.theme-violet .border-primary-500 { - border-color: #ff21f5 !important; -} - -.theme-violet .bg-primary-600 { - background-color: #fb00ff !important; -} - -.theme-violet .text-primary-600 { - color: #fb00ff !important; -} - -.theme-violet .border-primary-600 { - border-color: #fb00ff !important; -} - -.theme-violet .bg-primary-700 { - background-color: #cf00ce !important; -} - -.theme-violet .text-primary-700 { - color: #cf00ce !important; -} - -.theme-violet .border-primary-700 { - border-color: #cf00ce !important; -} - -.theme-violet .bg-primary-800 { - background-color: #a900a6 !important; -} - -.theme-violet .text-primary-800 { - color: #a900a6 !important; -} - -.theme-violet .border-primary-800 { - border-color: #a900a6 !important; -} - -.theme-violet .bg-primary-900 { - background-color: #890684 !important; -} - -.theme-violet .text-primary-900 { - color: #890684 !important; -} - -.theme-violet .border-primary-900 { - border-color: #890684 !important; -} - -/* Tailwind v4 Custom Animations and Keyframes */ -@theme { - --animate-NEWS_FEED: shakeAnnouncement 0.5s ease-in-out; - --animate-SECTION: flipBook 0.6s ease-in-out; - --animate-LESSONS: scrollUnroll 0.8s ease-out; - --animate-ATTENDANCE: attendanceCount 0.8s ease-in-out; - --animate-SUBMISSIONS: taskComplete 0.6s ease-in-out; - --animate-MARKS: gradeGlow 1s ease-in-out; - --animate-PEOPLE: peopleWave 0.8s ease-in-out; - --animate-ANALYTICS: dataScan 1s ease-in-out; - --animate-LANDING_PAGE: screenGlow 1s ease-in-out; - --animate-library-expand: libraryExpand 0.7s ease-out; - --animate-template-morph: templateMorph 0.6s ease-in-out; - --animate-ripple: communityRipple 0.9s ease-out; - --animate-roll: quizRoll 0.8s ease-in-out; - --animate-gather: audienceGather 0.7s ease-in-out; - --animate-setup-configure: setupConfigure 1s ease-in-out; - --animate-border: border 4s linear infinite; -} - -@keyframes shakeAnnouncement { - 0%, - 100% { - transform: translateX(0); - } - - 10%, - 30%, - 50%, - 70%, - 90% { - transform: translateX(-2px); - } - - 20%, - 40%, - 60%, - 80% { - transform: translateX(2px); - } -} - -@keyframes flipBook { - 0% { - transform: rotateY(0deg); - } - - 50% { - transform: rotateY(90deg); - } - - 100% { - transform: rotateY(0deg); - } -} - -@keyframes scrollUnroll { - 0% { - transform: scaleY(0) translateY(-50%); - opacity: 0.5; - } - - 50% { - transform: scaleY(0.8) translateY(-25%); - opacity: 0.8; - } - - 100% { - transform: scaleY(1) translateY(0); - opacity: 1; - } -} - -@keyframes attendanceCount { - 0% { - transform: scale(1); - opacity: 0.8; - } - - 20% { - transform: scale(1.1); - opacity: 1; - } - - 40% { - transform: scale(1.05); - opacity: 0.9; - } - - 60% { - transform: scale(1.15); - opacity: 1; - } - - 80% { - transform: scale(1.02); - opacity: 0.95; - } - - 100% { - transform: scale(1); - opacity: 1; - } -} - -@keyframes taskComplete { - 0% { - transform: scale(1) rotate(0deg); - } - - 25% { - transform: scale(1.1) rotate(-5deg); - } - - 50% { - transform: scale(1.2) rotate(5deg); - } - - 75% { - transform: scale(1.1) rotate(-2deg); - } - - 100% { - transform: scale(1) rotate(0deg); - } -} - -@keyframes gradeGlow { - 0%, - 100% { - transform: scale(1); - filter: brightness(1) drop-shadow(0 0 0 rgba(34, 197, 94, 0)); - } - - 50% { - transform: scale(1.15); - filter: brightness(1.3) drop-shadow(0 0 8px rgba(34, 197, 94, 0.6)); - } -} - -@keyframes peopleWave { - 0%, - 100% { - transform: rotate(0deg) scale(1); - } - - 25% { - transform: rotate(-5deg) scale(1.05); - } - - 50% { - transform: rotate(5deg) scale(1.1); - } - - 75% { - transform: rotate(-3deg) scale(1.05); - } -} - -@keyframes dataScan { - 0% { - transform: scale(1); - filter: brightness(1) drop-shadow(0 0 0 rgba(99, 102, 241, 0)); - } - - 25% { - transform: scale(1.05); - filter: brightness(1.1) drop-shadow(0 0 4px rgba(99, 102, 241, 0.4)); - } - - 50% { - transform: scale(1.1); - filter: brightness(1.2) drop-shadow(0 0 8px rgba(99, 102, 241, 0.6)); - } - - 75% { - transform: scale(1.15) rotate(2deg); - filter: brightness(1.3) drop-shadow(0 0 12px rgba(99, 102, 241, 0.8)); - } - - 100% { - transform: scale(1); - filter: brightness(1) drop-shadow(0 0 0 rgba(99, 102, 241, 0)); - } -} - -@keyframes screenGlow { - 0%, - 100% { - transform: scale(1); - filter: brightness(1) drop-shadow(0 0 0 rgba(59, 130, 246, 0)); - } - - 50% { - transform: scale(1.1); - filter: brightness(1.2) drop-shadow(0 0 6px rgba(59, 130, 246, 0.5)); - } -} - -@keyframes libraryExpand { - 0% { - transform: translateY(-100%) scale(0.8); - opacity: 0; - } - - 30% { - transform: translateY(-20%) scale(0.9); - opacity: 0.7; - } - - 60% { - transform: translateY(5%) scale(1.05); - opacity: 1; - } - - 80% { - transform: translateY(-2%) scale(1.02); - opacity: 1; - } - - 100% { - transform: translateY(0%) scale(1); - opacity: 1; - } -} - -@keyframes templateMorph { - 0% { - transform: scale(1) rotate(0deg); - } - - 25% { - transform: scale(1.05) rotate(-2deg) skewX(2deg); - } - - 50% { - transform: scale(1.1) rotate(2deg) skewX(-2deg); - } - - 75% { - transform: scale(1.05) rotate(-1deg) skewX(1deg); - } - - 100% { - transform: scale(1) rotate(0deg) skewX(0deg); - } -} - -@keyframes communityRipple { - 0% { - transform: translateX(-100%) scale(0.8); - opacity: 0; - } - - 30% { - transform: translateX(-20%) scale(0.9); - opacity: 0.7; - } - - 60% { - transform: translateX(5%) scale(1.05); - opacity: 1; - } - - 80% { - transform: translateX(-2%) scale(1.02); - opacity: 1; - } - - 100% { - transform: translateX(0%) scale(1); - opacity: 1; - } -} - -@keyframes quizRoll { - 0% { - transform: scale(1) rotate(0deg); - } - - 20% { - transform: scale(1.05) rotate(72deg); - } - - 40% { - transform: scale(1.1) rotate(144deg); - } - - 60% { - transform: scale(1.15) rotate(216deg); - } - - 80% { - transform: scale(1.1) rotate(288deg); - } - - 100% { - transform: scale(1) rotate(360deg); - } -} - -@keyframes audienceGather { - 0% { - transform: scale(1); - filter: brightness(1) drop-shadow(0 0 0 rgba(147, 51, 234, 0)); - } - - 25% { - transform: scale(0.95) scaleX(1.1); - filter: brightness(1.05) drop-shadow(0 0 2px rgba(147, 51, 234, 0.3)); - } - - 50% { - transform: scale(1.1) scaleX(0.9); - filter: brightness(1.1) drop-shadow(0 0 4px rgba(147, 51, 234, 0.5)); - } - - 75% { - transform: scale(1.05) scaleX(1.05); - filter: brightness(1.05) drop-shadow(0 0 2px rgba(147, 51, 234, 0.3)); - } - - 100% { - transform: scale(1) scaleX(1); - filter: brightness(1) drop-shadow(0 0 0 rgba(147, 51, 234, 0)); - } -} - -@keyframes setupConfigure { - 0% { - transform: scale(1) rotate(0deg); - } - - 15% { - transform: scale(1.02) rotate(15deg); - } - - 30% { - transform: scale(1.04) rotate(-10deg); - } - - 45% { - transform: scale(1.06) rotate(20deg); - } - - 60% { - transform: scale(1.08) rotate(-15deg); - } - - 75% { - transform: scale(1.06) rotate(10deg); - } - - 90% { - transform: scale(1.02) rotate(-5deg); - } - - 100% { - transform: scale(1) rotate(0deg); +body[data-theme='default'] { + --chart-1: var(--color-blue-300); + --chart-2: var(--color-blue-500); + --chart-3: var(--color-blue-600); + --chart-4: var(--color-blue-700); + --chart-5: var(--color-blue-800); +} + +body[data-theme='blue'] { + --primary: var(--color-blue-700); + --primary-foreground: var(--color-blue-50); + /* --ring: var(--color-blue-100); */ + --sidebar-primary: var(--color-blue-600); + --sidebar-primary-foreground: var(--color-blue-50); + --sidebar-accent: var(--color-blue-50); + --sidebar-accent-foreground: var(--color-blue-700); + /* --sidebar-ring: var(--color-blue-400); */ + --chart-1: var(--color-blue-300); + --chart-2: var(--color-blue-500); + --chart-3: var(--color-blue-600); + --chart-4: var(--color-blue-700); + --chart-5: var(--color-blue-800); + + @variant dark { + --primary: var(--color-blue-700); + --primary-foreground: var(--color-blue-50); + /* --ring: var(--color-blue-900); */ + --sidebar-primary: var(--color-blue-500); + --sidebar-primary-foreground: var(--color-blue-50); + --sidebar-accent: oklch(0.268 0.007 34.298); + --sidebar-accent-foreground: var(--color-blue-500); + /* --sidebar-ring: var(--color-blue-900); */ + } +} + +body[data-theme='green'] { + --primary: var(--color-lime-600); + --primary-foreground: var(--color-lime-50); + --ring: var(--color-lime-400); + --chart-1: var(--color-green-300); + --chart-2: var(--color-green-500); + --chart-3: var(--color-green-600); + --chart-4: var(--color-green-700); + --chart-5: var(--color-green-800); + --sidebar-primary: var(--color-lime-600); + --sidebar-primary-foreground: var(--color-lime-50); + --sidebar-accent: var(--color-lime-50); + --sidebar-accent-foreground: var(--color-lime-700); + --sidebar-ring: var(--color-lime-400); + + @variant dark { + --primary: var(--color-lime-600); + --primary-foreground: var(--color-lime-50); + --ring: var(--color-lime-900); + --sidebar-primary: var(--color-lime-500); + --sidebar-primary-foreground: var(--color-lime-50); + --sidebar-accent: oklch(0.268 0.007 34.298); + --sidebar-accent-foreground: var(--color-lime-500); + --sidebar-ring: var(--color-lime-900); + } +} + +body[data-theme='amber'] { + --primary: var(--color-amber-600); + --primary-foreground: var(--color-amber-50); + --ring: var(--color-amber-400); + --chart-1: var(--color-amber-300); + --chart-2: var(--color-amber-500); + --chart-3: var(--color-amber-600); + --chart-4: var(--color-amber-700); + --chart-5: var(--color-amber-800); + --sidebar-primary: var(--color-amber-600); + --sidebar-primary-foreground: var(--color-amber-50); + --sidebar-accent: var(--color-amber-50); + --sidebar-accent-foreground: var(--color-amber-700); + --sidebar-ring: var(--color-amber-400); + + @variant dark { + --primary: var(--color-amber-500); + --primary-foreground: var(--color-amber-50); + --ring: var(--color-amber-900); + --sidebar-primary: var(--color-amber-500); + --sidebar-primary-foreground: var(--color-amber-50); + --sidebar-accent: oklch(0.268 0.007 34.298); + --sidebar-accent-foreground: var(--color-amber-500); + --sidebar-ring: var(--color-amber-900); + } +} + +body[data-theme='rose'] { + --primary: var(--color-rose-600); + --primary-foreground: var(--color-rose-50); + --ring: var(--color-rose-400); + --chart-1: var(--color-rose-300); + --chart-2: var(--color-rose-500); + --chart-3: var(--color-rose-600); + --chart-4: var(--color-rose-700); + --chart-5: var(--color-rose-800); + --sidebar-primary: var(--color-rose-600); + --sidebar-primary-foreground: var(--color-rose-50); + --sidebar-accent: var(--color-rose-50); + --sidebar-accent-foreground: var(--color-rose-700); + --sidebar-ring: var(--color-rose-400); + + @variant dark { + --primary: var(--color-rose-500); + --primary-foreground: var(--color-rose-50); + --ring: var(--color-rose-900); + --sidebar-primary: var(--color-rose-500); + --sidebar-primary-foreground: var(--color-rose-50); + --sidebar-accent: oklch(0.268 0.007 34.298); + --sidebar-accent-foreground: var(--color-rose-500); + --sidebar-ring: var(--color-rose-900); + } +} + +body[data-theme='purple'] { + --primary: var(--color-purple-600); + --primary-foreground: var(--color-purple-50); + --ring: var(--color-purple-400); + --chart-1: var(--color-purple-300); + --chart-2: var(--color-purple-500); + --chart-3: var(--color-purple-600); + --chart-4: var(--color-purple-700); + --chart-5: var(--color-purple-800); + --sidebar-primary: var(--color-purple-600); + --sidebar-primary-foreground: var(--color-purple-50); + --sidebar-accent: var(--color-purple-50); + --sidebar-accent-foreground: var(--color-purple-700); + --sidebar-ring: var(--color-purple-400); + + @variant dark { + --primary: var(--color-purple-500); + --primary-foreground: var(--color-purple-50); + --ring: var(--color-purple-900); + --sidebar-primary: var(--color-purple-500); + --sidebar-primary-foreground: var(--color-purple-50); + --sidebar-accent: oklch(0.268 0.007 34.298); + --sidebar-accent-foreground: var(--color-purple-500); + --sidebar-ring: var(--color-purple-900); + } +} + +body[data-theme='orange'] { + --primary: var(--color-orange-600); + --primary-foreground: var(--color-orange-50); + --ring: var(--color-orange-400); + --chart-1: var(--color-orange-300); + --chart-2: var(--color-orange-500); + --chart-3: var(--color-orange-600); + --chart-4: var(--color-orange-700); + --chart-5: var(--color-orange-800); + --sidebar-primary: var(--color-orange-600); + --sidebar-primary-foreground: var(--color-orange-50); + --sidebar-accent: var(--color-orange-50); + --sidebar-accent-foreground: var(--color-orange-700); + --sidebar-ring: var(--color-orange-400); + + @variant dark { + --primary: var(--color-orange-500); + --primary-foreground: var(--color-orange-50); + --ring: var(--color-orange-900); + --sidebar-primary: var(--color-orange-500); + --sidebar-primary-foreground: var(--color-orange-50); + --sidebar-accent: oklch(0.268 0.007 34.298); + --sidebar-accent-foreground: var(--color-orange-500); + --sidebar-ring: var(--color-orange-900); + } +} + +body[data-theme='teal'] { + --primary: var(--color-teal-600); + --primary-foreground: var(--color-teal-50); + --chart-1: var(--color-teal-300); + --chart-2: var(--color-teal-500); + --chart-3: var(--color-teal-600); + --chart-4: var(--color-teal-700); + --chart-5: var(--color-teal-800); + --sidebar-primary: var(--color-teal-600); + --sidebar-primary-foreground: var(--color-teal-50); + --sidebar-accent: var(--color-teal-50); + --sidebar-accent-foreground: var(--color-teal-700); + --sidebar-ring: var(--color-teal-400); + + @variant dark { + --primary: var(--color-teal-500); + --primary-foreground: var(--color-teal-50); + --sidebar-primary: var(--color-teal-500); + --sidebar-primary-foreground: var(--color-teal-50); + --sidebar-accent: oklch(0.268 0.007 34.298); + --sidebar-accent-foreground: var(--color-teal-500); + --sidebar-ring: var(--color-teal-900); + } +} + +body[data-theme='mono'] { + --font-sans: var(--font-mono); + --primary: var(--color-stone-600); + --primary-foreground: var(--color-stone-50); + --chart-1: var(--color-stone-300); + --chart-2: var(--color-stone-500); + --chart-3: var(--color-stone-600); + --chart-4: var(--color-stone-700); + --chart-5: var(--color-stone-800); + --sidebar-primary: var(--color-stone-600); + --sidebar-primary-foreground: var(--color-stone-50); + --sidebar-accent: var(--color-stone-50); + --sidebar-accent-foreground: var(--color-stone-700); + --sidebar-ring: var(--color-stone-400); + + @variant dark { + --primary: var(--color-stone-500); + --primary-foreground: var(--color-stone-50); + --sidebar-primary: var(--color-stone-500); + --sidebar-primary-foreground: var(--color-stone-50); + --sidebar-accent: oklch(0.268 0.007 34.298); + --sidebar-accent-foreground: var(--color-stone-500); + --sidebar-ring: var(--color-stone-900); + } + + @media (min-width: 1024px) { + --font-sans: var(--font-mono); + --radius: 0.45em; + --text-lg: 1rem; + --text-xl: 1.1rem; + --text-2xl: 1.2rem; + --text-3xl: 1.3rem; + --text-4xl: 1.4rem; + --text-5xl: 1.5rem; + --text-6xl: 1.6rem; + --text-7xl: 1.7rem; + --text-8xl: 1.8rem; + --text-base: 0.85rem; + --text-sm: 0.8rem; + --spacing: 0.222222rem; + } + + .rounded-xs, + .rounded-sm, + .rounded-md, + .rounded-lg, + .rounded-xl { + border-radius: 0; + } + + .shadow-xs, + .shadow-sm, + .shadow-md, + .shadow-lg, + .shadow-xl { + box-shadow: none; + } + + [data-slot='toggle-group'], + [data-slot='toggle-group-item'] { + @apply !rounded-none !shadow-none; + } +} + +body[data-theme='scaled'] { + --chart-1: var(--color-blue-300); + --chart-2: var(--color-blue-500); + --chart-3: var(--color-blue-600); + --chart-4: var(--color-blue-700); + --chart-5: var(--color-blue-800); + + @media (min-width: 1024px) { + --radius: 0.45em; + --text-lg: 1rem; + --text-xl: 1.1rem; + --text-2xl: 1.2rem; + --text-3xl: 1.3rem; + --text-4xl: 1.4rem; + --text-5xl: 1.5rem; + --text-6xl: 1.6rem; + --text-7xl: 1.7rem; + --text-8xl: 1.8rem; + --text-base: 0.85rem; + --text-sm: 0.8rem; + --spacing: 0.2rem; + } + + [data-slot='select-trigger'], + [data-slot='toggle-group-item'] { + --spacing: 0.2rem; + } + + [data-slot='card'] { + border-radius: var(--radius); + padding-block: calc(var(--spacing) * 4); + gap: calc(var(--spacing) * 2); + } + + [data-slot='card'].pb-0 { + padding-bottom: 0; + } +} + +body[data-theme='red'] { + --primary: var(--color-red-600); + --primary-foreground: var(--color-red-50); + --ring: var(--color-red-400); + --chart-1: var(--color-red-300); + --chart-2: var(--color-red-500); + --chart-3: var(--color-red-600); + --chart-4: var(--color-red-700); + --chart-5: var(--color-red-800); + --sidebar-primary: var(--color-red-600); + --sidebar-primary-foreground: var(--color-red-50); + --sidebar-accent: var(--color-red-50); + --sidebar-accent-foreground: var(--color-red-700); + --sidebar-ring: var(--color-red-400); + + @variant dark { + --primary: var(--color-red-500); + --primary-foreground: var(--color-red-50); + --ring: var(--color-red-900); + --sidebar-primary: var(--color-red-500); + --sidebar-primary-foreground: var(--color-red-50); + --sidebar-accent: oklch(0.268 0.007 34.298); + --sidebar-accent-foreground: var(--color-red-500); + --sidebar-ring: var(--color-red-900); + } +} + +body[data-theme='yellow'] { + --primary: var(--color-yellow-400); + --primary-foreground: var(--color-yellow-900); + --ring: var(--color-yellow-400); + --chart-1: var(--color-yellow-300); + --chart-2: var(--color-yellow-500); + --chart-3: var(--color-yellow-600); + --chart-4: var(--color-yellow-700); + --chart-5: var(--color-yellow-800); + --sidebar-primary: var(--color-yellow-600); + --sidebar-primary-foreground: var(--color-yellow-50); + --sidebar-accent: var(--color-yellow-50); + --sidebar-accent-foreground: var(--color-yellow-700); + --sidebar-ring: var(--color-yellow-400); + + @variant dark { + --primary: var(--color-yellow-500); + --primary-foreground: var(--color-yellow-900); + --ring: var(--color-yellow-900); + --sidebar-primary: var(--color-yellow-500); + --sidebar-primary-foreground: var(--color-yellow-50); + --sidebar-accent: oklch(0.268 0.007 34.298); + --sidebar-accent-foreground: var(--color-yellow-500); + --sidebar-ring: var(--color-yellow-900); + } +} + +body[data-theme='violet'] { + --primary: var(--color-violet-600); + --primary-foreground: var(--color-violet-50); + --ring: var(--color-violet-400); + --chart-1: var(--color-violet-300); + --chart-2: var(--color-violet-500); + --chart-3: var(--color-violet-600); + --chart-4: var(--color-violet-700); + --chart-5: var(--color-violet-800); + --sidebar-primary: var(--color-violet-600); + --sidebar-primary-foreground: var(--color-violet-50); + --sidebar-accent: var(--color-violet-50); + --sidebar-accent-foreground: var(--color-violet-700); + --sidebar-ring: var(--color-violet-400); + + @variant dark { + --primary: var(--color-violet-500); + --primary-foreground: var(--color-violet-50); + --ring: var(--color-violet-900); + --sidebar-primary: var(--color-violet-500); + --sidebar-primary-foreground: var(--color-violet-50); + --sidebar-accent: oklch(0.268 0.007 34.298); + --sidebar-accent-foreground: var(--color-violet-500); + --sidebar-ring: var(--color-violet-900); } } @@ -1166,9 +558,9 @@ svg.lucide-icon:not(.custom) { conic-gradient( from var(--border-angle), rgb(148 163 184 / 0.3) 75%, - rgb(37 99 235) 82%, - rgb(96 165 250) 88%, - rgb(37 99 235) 92%, + var(--primary) 82%, + var(--primary-foreground) 88%, + var(--primary) 92%, rgb(148 163 184 / 0.3) 100% ) border-box; @@ -1185,9 +577,9 @@ svg.lucide-icon:not(.custom) { conic-gradient( from var(--border-angle), rgb(71 85 105 / 0.48) 75%, - rgb(37 99 235) 82%, - rgb(96 165 250) 88%, - rgb(37 99 235) 92%, + var(--primary) 82%, + var(--primary-foreground) 88%, + var(--primary) 92%, rgb(71 85 105 / 0.48) 100% ) border-box; @@ -1287,7 +679,6 @@ svg.lucide-icon:not(.custom) { */ @keyframes flame-sequence { 0% { - stroke: var(--main-primary-color); opacity: 1; transform: translate(-0.5px, 0.9px) scale(1.02); } diff --git a/apps/dashboard/src/app.html b/apps/dashboard/src/app.html index 0ade6572..7b7e40c2 100644 --- a/apps/dashboard/src/app.html +++ b/apps/dashboard/src/app.html @@ -25,7 +25,7 @@ -
%sveltekit.body%
+
%sveltekit.body%
\ No newline at end of file diff --git a/apps/dashboard/src/lib/components/AI/AIButton/CustomPromptBtn.svelte b/apps/dashboard/src/lib/components/AI/AIButton/CustomPromptBtn.svelte index 6768a2dd..427dfe3d 100644 --- a/apps/dashboard/src/lib/components/AI/AIButton/CustomPromptBtn.svelte +++ b/apps/dashboard/src/lib/components/AI/AIButton/CustomPromptBtn.svelte @@ -71,7 +71,7 @@
(openPopover = !openPopover)} > diff --git a/apps/dashboard/src/lib/components/Analytics/index.ts b/apps/dashboard/src/lib/components/Analytics/index.ts deleted file mode 100644 index fa6c643f..00000000 --- a/apps/dashboard/src/lib/components/Analytics/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { default as ActivityCard } from './ActivityCard.svelte'; -export { default as HeroProfileCard } from './HeroProfileCard.svelte'; -export { default as LoadingPage } from './LoadingPage.svelte'; diff --git a/apps/dashboard/src/lib/components/Box/index.svelte b/apps/dashboard/src/lib/components/Box/index.svelte deleted file mode 100644 index 321ce82f..00000000 --- a/apps/dashboard/src/lib/components/Box/index.svelte +++ /dev/null @@ -1,14 +0,0 @@ - - -
- {@render children?.()} -
diff --git a/apps/dashboard/src/lib/components/Buttons/Close/index.svelte b/apps/dashboard/src/lib/components/Buttons/Close/index.svelte deleted file mode 100644 index 44ab8032..00000000 --- a/apps/dashboard/src/lib/components/Buttons/Close/index.svelte +++ /dev/null @@ -1,30 +0,0 @@ - - - - {#if isClosing} - - {:else} - - {/if} - diff --git a/apps/dashboard/src/lib/components/Buttons/Close/index.ts b/apps/dashboard/src/lib/components/Buttons/Close/index.ts deleted file mode 100644 index 55ccef7e..00000000 --- a/apps/dashboard/src/lib/components/Buttons/Close/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as CloseButton } from './index.svelte'; diff --git a/apps/dashboard/src/lib/components/Buttons/Delete/index.svelte b/apps/dashboard/src/lib/components/Buttons/Delete/index.svelte deleted file mode 100644 index 1876ee4b..00000000 --- a/apps/dashboard/src/lib/components/Buttons/Delete/index.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - - - diff --git a/apps/dashboard/src/lib/components/Buttons/Send/index.svelte b/apps/dashboard/src/lib/components/Buttons/Send/index.svelte deleted file mode 100644 index aac4d0d7..00000000 --- a/apps/dashboard/src/lib/components/Buttons/Send/index.svelte +++ /dev/null @@ -1,10 +0,0 @@ - - - - - diff --git a/apps/dashboard/src/lib/components/Chip/Plan.svelte b/apps/dashboard/src/lib/components/Chip/Plan.svelte deleted file mode 100644 index 4b9fc77e..00000000 --- a/apps/dashboard/src/lib/components/Chip/Plan.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - -

- {name} -

diff --git a/apps/dashboard/src/lib/components/Chip/Text.svelte b/apps/dashboard/src/lib/components/Chip/Text.svelte deleted file mode 100644 index c64ff1c3..00000000 --- a/apps/dashboard/src/lib/components/Chip/Text.svelte +++ /dev/null @@ -1,24 +0,0 @@ - - -

- {value} -

- - diff --git a/apps/dashboard/src/lib/components/Chip/index.svelte b/apps/dashboard/src/lib/components/Chip/index.svelte deleted file mode 100644 index 0c1af842..00000000 --- a/apps/dashboard/src/lib/components/Chip/index.svelte +++ /dev/null @@ -1,31 +0,0 @@ - - -

{value}

- - diff --git a/apps/dashboard/src/lib/components/Course/components/Attendance/index.svelte b/apps/dashboard/src/lib/components/Course/components/Attendance/index.svelte deleted file mode 100644 index 7c2d53d7..00000000 --- a/apps/dashboard/src/lib/components/Course/components/Attendance/index.svelte +++ /dev/null @@ -1,6 +0,0 @@ - - - -All attendance will be here with table diff --git a/apps/dashboard/src/lib/components/Course/components/Ceritficate/Design.svelte b/apps/dashboard/src/lib/components/Course/components/Ceritficate/Design.svelte index f9825b5d..b3ce9b40 100644 --- a/apps/dashboard/src/lib/components/Course/components/Ceritficate/Design.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Ceritficate/Design.svelte @@ -7,18 +7,17 @@ import { course } from '$lib/components/Course/store'; import { t } from '$lib/utils/functions/translations'; - import { snackbar } from '$lib/components/Snackbar/store'; + import { snackbar } from '$features/ui/snackbar/store'; import { updateCourse } from '$lib/utils/services/courses'; import { currentOrg, isFreePlan } from '$lib/utils/store/org'; - import { VARIANTS } from '$lib/components/PrimaryButton/constants'; import { saveCertificateValidation } from '$lib/utils/functions/validator'; + import { Button } from '@cio/ui/base/button'; import Plain from './templates/Plain.svelte'; import Professional from './templates/Professional.svelte'; - import TextArea from '$lib/components/Form/TextArea.svelte'; - import { UpgradeBanner } from '$lib/features/ui'; + import { TextareaField } from '@cio/ui/custom/textarea-field'; + import { UpgradeBanner } from '$features/ui'; import BlueBadgePattern from './templates/BlueBadgePattern.svelte'; - import PrimaryButton from '$lib/components/PrimaryButton/index.svelte'; import PurpleBadgePattern from './templates/PurpleBadgePattern.svelte'; import BlueProfessionalBadge from './templates/BlueProfessionalBadge.svelte'; import PurpleProfessionalBadge from './templates/PurpleProfessionalBadge.svelte'; @@ -122,18 +121,15 @@ {$t('course.navItem.certificates.and_upload')}

- goto(`/org/${$currentOrg.siteName}/settings`)} - /> +

{$t('course.navItem.certificates.description')}

- - {#if !!errorMessage} -

- {errorMessage} -

- {:else if helperMessage} -

- {helperMessage} -

- {/if} - {@render iconbutton?.()} - - - diff --git a/apps/dashboard/src/lib/components/Form/TextField.svelte b/apps/dashboard/src/lib/components/Form/TextField.svelte deleted file mode 100644 index 45b2e1f8..00000000 --- a/apps/dashboard/src/lib/components/Form/TextField.svelte +++ /dev/null @@ -1,156 +0,0 @@ - - - - - diff --git a/apps/dashboard/src/lib/components/HashTags/index.svelte b/apps/dashboard/src/lib/components/HashTags/index.svelte deleted file mode 100644 index f173ea5c..00000000 --- a/apps/dashboard/src/lib/components/HashTags/index.svelte +++ /dev/null @@ -1,9 +0,0 @@ - - -
- {#each tags as tag} - #{tag.toLowerCase()} - {/each} -
diff --git a/apps/dashboard/src/lib/components/Hoverable/index.svelte b/apps/dashboard/src/lib/components/Hoverable/index.svelte deleted file mode 100644 index 1ae47faa..00000000 --- a/apps/dashboard/src/lib/components/Hoverable/index.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - -
- {@render children?.()} -
diff --git a/apps/dashboard/src/lib/components/IconButton/index.svelte b/apps/dashboard/src/lib/components/IconButton/index.svelte deleted file mode 100644 index 62889921..00000000 --- a/apps/dashboard/src/lib/components/IconButton/index.svelte +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - - diff --git a/apps/dashboard/src/lib/components/IconButton/index.ts b/apps/dashboard/src/lib/components/IconButton/index.ts deleted file mode 100644 index 1f8e189d..00000000 --- a/apps/dashboard/src/lib/components/IconButton/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as IconButton } from './index.svelte'; diff --git a/apps/dashboard/src/lib/components/Icons/AudienceEmptyIcon.svelte b/apps/dashboard/src/lib/components/Icons/AudienceEmptyIcon.svelte deleted file mode 100644 index 791241ce..00000000 --- a/apps/dashboard/src/lib/components/Icons/AudienceEmptyIcon.svelte +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/apps/dashboard/src/lib/components/Icons/CourseIcon.svelte b/apps/dashboard/src/lib/components/Icons/CourseIcon.svelte deleted file mode 100644 index 04376fb6..00000000 --- a/apps/dashboard/src/lib/components/Icons/CourseIcon.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - - - diff --git a/apps/dashboard/src/lib/components/Icons/ExerciseIcon.svelte b/apps/dashboard/src/lib/components/Icons/ExerciseIcon.svelte deleted file mode 100644 index 3eabf5b9..00000000 --- a/apps/dashboard/src/lib/components/Icons/ExerciseIcon.svelte +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - diff --git a/apps/dashboard/src/lib/components/Icons/PasswordEye.svelte b/apps/dashboard/src/lib/components/Icons/PasswordEye.svelte deleted file mode 100644 index 61191b20..00000000 --- a/apps/dashboard/src/lib/components/Icons/PasswordEye.svelte +++ /dev/null @@ -1,35 +0,0 @@ - - -{#if isClosed} - - - -{:else} - - - - -{/if} diff --git a/apps/dashboard/src/lib/components/Icons/ReviewsIcon.svelte b/apps/dashboard/src/lib/components/Icons/ReviewsIcon.svelte deleted file mode 100644 index f562afd5..00000000 --- a/apps/dashboard/src/lib/components/Icons/ReviewsIcon.svelte +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - diff --git a/apps/dashboard/src/lib/components/Icons/StepDoneIcon.svelte b/apps/dashboard/src/lib/components/Icons/StepDoneIcon.svelte deleted file mode 100644 index 57293bab..00000000 --- a/apps/dashboard/src/lib/components/Icons/StepDoneIcon.svelte +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/apps/dashboard/src/lib/components/LMS/SideBar.svelte b/apps/dashboard/src/lib/components/LMS/SideBar.svelte deleted file mode 100644 index f094595d..00000000 --- a/apps/dashboard/src/lib/components/LMS/SideBar.svelte +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - LMS Navigation - - {#each sideLinks as item (item.link)} - - - - - {item.name} - - - - {/each} - - - - - {#if user} - - - - {/if} - - - - - - - - diff --git a/apps/dashboard/src/lib/components/LMS/components/Profile.svelte b/apps/dashboard/src/lib/components/LMS/components/Profile.svelte deleted file mode 100644 index c9f9ca9b..00000000 --- a/apps/dashboard/src/lib/components/LMS/components/Profile.svelte +++ /dev/null @@ -1,119 +0,0 @@ - - - - - -

{$t('settings.profile.profile_picture.heading')}

-
- - - -
- - -

{$t('settings.profile.personal_information.heading')}

-
- - - - - - - -
- - - -
diff --git a/apps/dashboard/src/lib/components/MarkdownEditor/index.svelte b/apps/dashboard/src/lib/components/MarkdownEditor/index.svelte deleted file mode 100644 index 4a938144..00000000 --- a/apps/dashboard/src/lib/components/MarkdownEditor/index.svelte +++ /dev/null @@ -1,54 +0,0 @@ - - -
-
- - -
- -
- {#if isWriteMode} - - {:else} -
- {@render preview?.()} -
- {/if} -
-
- - diff --git a/apps/dashboard/src/lib/components/Modal/DeleteModal.svelte b/apps/dashboard/src/lib/components/Modal/DeleteModal.svelte deleted file mode 100644 index ad710dc1..00000000 --- a/apps/dashboard/src/lib/components/Modal/DeleteModal.svelte +++ /dev/null @@ -1,46 +0,0 @@ - - - !isLoading && (open = false)} - bind:open - width="w-96" - containerClass="p-6" - headerClass="p-2" - modalHeading={$t('delete_modal.label')} -> -
-

- {$t('delete_modal.content')} -

- -
- (open = false)} - {isLoading} - /> - -
-
-
diff --git a/apps/dashboard/src/lib/components/Modal/index.svelte b/apps/dashboard/src/lib/components/Modal/index.svelte deleted file mode 100644 index 964ff370..00000000 --- a/apps/dashboard/src/lib/components/Modal/index.svelte +++ /dev/null @@ -1,92 +0,0 @@ - - -{#if open} - -{/if} - - diff --git a/apps/dashboard/src/lib/components/Modal/index.ts b/apps/dashboard/src/lib/components/Modal/index.ts deleted file mode 100644 index 916d43d5..00000000 --- a/apps/dashboard/src/lib/components/Modal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as Modal } from './index.svelte'; diff --git a/apps/dashboard/src/lib/components/Navigation/CustomLinks.svelte b/apps/dashboard/src/lib/components/Navigation/CustomLinks.svelte index 6d0fd493..75368cf2 100644 --- a/apps/dashboard/src/lib/components/Navigation/CustomLinks.svelte +++ b/apps/dashboard/src/lib/components/Navigation/CustomLinks.svelte @@ -17,18 +17,17 @@ {#if customLinks} diff --git a/apps/dashboard/src/lib/components/Navigation/app.svelte b/apps/dashboard/src/lib/components/Navigation/app.svelte index 17a669d9..d83f650c 100644 --- a/apps/dashboard/src/lib/components/Navigation/app.svelte +++ b/apps/dashboard/src/lib/components/Navigation/app.svelte @@ -3,7 +3,7 @@ import BellIcon from '@lucide/svelte/icons/bell'; import ArrowLeftIcon from '@lucide/svelte/icons/arrow-left'; - import { IconButton } from '$lib/components/IconButton'; + import { IconButton } from '@cio/ui/custom/icon-button'; import { globalStore } from '$lib/utils/store/app'; import { currentOrgPath } from '$lib/utils/store/org'; import { t } from '$lib/utils/functions/translations'; @@ -26,7 +26,7 @@
{#if isCoursePage} diff --git a/apps/dashboard/src/lib/components/Navigation/index.svelte b/apps/dashboard/src/lib/components/Navigation/index.svelte index 5b244226..703e2c13 100644 --- a/apps/dashboard/src/lib/components/Navigation/index.svelte +++ b/apps/dashboard/src/lib/components/Navigation/index.svelte @@ -45,12 +45,12 @@
    - + {#if isOrgSite && showLinks}