From b31ccf30373b42019b780beabbe589e3023f4e1e Mon Sep 17 00:00:00 2001 From: Rotimi Best Date: Thu, 25 Dec 2025 09:01:51 +0000 Subject: [PATCH] feat: lint codebase and run precommit hook --- .prettierignore | 10 +- apps/api/.prettierignore | 1 + apps/api/ROUTE_NAMING_BEST_PRACTICES.md | 96 +- apps/api/package.json | 2 +- apps/api/scripts/README.md | 4 +- apps/api/scripts/upload-openapi-spec.ts | 6 +- apps/api/src/middlewares/rate-limiter.ts | 1 - apps/api/src/utils/redis/limiter.ts | 10 +- apps/api/static/course.html | 19 +- apps/api/static/lesson.html | 23 +- apps/course-app/.prettierignore | 4 - apps/course-app/.prettierrc | 15 - apps/course-app/components.json | 26 +- apps/course-app/package.json | 3 +- apps/course-app/postcss.config.js | 8 +- apps/course-app/src/app.d.ts | 14 +- apps/course-app/src/app.html | 87 +- apps/course-app/src/demo.spec.ts | 6 +- .../src/lib/components/ui/button/index.ts | 74 +- .../src/lib/components/ui/dialog/index.ts | 58 +- .../src/lib/components/ui/input/index.ts | 42 +- .../src/lib/components/ui/label/index.ts | 8 +- .../src/lib/components/ui/separator/index.ts | 8 +- .../src/lib/components/ui/sonner/index.ts | 2 +- apps/course-app/static/site.webmanifest | 12 +- apps/course-app/vite.config.ts | 18 +- apps/dashboard/README.md | 2 - apps/dashboard/ai/BREADCRUMBS_REFACTOR.md | 171 +- apps/dashboard/ai/PAGE_ROUTE_GUIDE.md | 48 +- apps/dashboard/src/app.html | 48 +- .../Analytics/AnalyticsSkeleton.svelte | 14 +- .../components/Ceritficate/Design.svelte | 2 +- .../Ceritficate/IssueCertificateModal.svelte | 78 +- .../Lesson/ActivateSectionsModal.svelte | 40 +- .../Lesson/DeleteLessonConfirmation.svelte | 30 +- .../Lesson/Exercise/DeleteConfirmation.svelte | 48 +- .../Lesson/Exercise/MarkExerciseModal.svelte | 288 +- .../Lesson/Exercise/OrderModal.svelte | 56 +- .../Lesson/Exercises/NewExerciseModal.svelte | 274 +- .../Document/AddDocumentToLesson.svelte | 4 +- .../Lesson/Materials/Video/UploadVideo.svelte | 2 +- .../NewsFeed/DeleteFeedConfirmation.svelte | 21 +- .../components/NewsFeed/NewFeedModal.svelte | 45 +- .../People/DeleteConfirmation.svelte | 34 +- .../community/components/delete-modal.svelte | 39 +- .../src/lib/features/community/utils/store.ts | 1 - .../components/copy-course-modal.svelte | 58 +- .../features/course/pages/attendance.svelte | 1 - .../lib/features/course/pages/lessons.svelte | 1 - .../features/lms/components/learning.svelte | 1 - .../onboarding/utils/dom-protection.ts | 385 +- .../add-org-modal/add-org-modal.svelte | 45 +- .../org/components/image-renderer.svelte | 1 - .../landing-page/page-header.svelte | 1 - .../landing-page/page-loader.svelte | 1 - .../org/components/new-org-modal-store.ts | 1 - .../org/components/quiz/delete-modal.svelte | 44 +- .../org/components/quiz/new-quiz-modal.svelte | 37 +- .../org/components/quiz/quiz-question.svelte | 5 +- .../components/settings/landing-page-store.ts | 1 - .../ui/analytics/activity-card.svelte | 1 - .../ui/analytics/hero-profile-card.svelte | 1 - .../features/ui/analytics/loading-page.svelte | 1 - .../src/lib/features/ui/delete-modal.svelte | 31 +- .../features/ui/icons/arrow-top-right.svelte | 1 - .../ui/icons/circle-check-icon.svelte | 1 - .../ui/icons/courses-empty-icon.svelte | 1 - .../features/ui/icons/email-sent-icon.svelte | 1 - .../ui/icons/error-icon-dark-mode.svelte | 1 - .../lib/features/ui/icons/error-icon.svelte | 1 - .../ui/icons/google-icon-colored.svelte | 1 - .../ui/icons/user-profile-icon.svelte | 1 - .../features/ui/navigation/breadcrumb-core.ts | 86 +- .../features/ui/navigation/lms-breadcrumb.ts | 1 - .../ui/navigation/lms-breadcrumbs.svelte | 3 +- .../features/ui/navigation/lms-header.svelte | 1 - .../src/lib/features/ui/senja-embed.svelte | 1 - .../ui/sidebar/lms-sidebar/nav-main.svelte | 1 - .../ui/upload-widget/upload-widget.svelte | 148 +- .../dashboard/src/lib/features/ui/vote.svelte | 1 - .../src/lib/utils/functions/fileValidation.ts | 8 +- .../src/lib/utils/functions/permissions.ts | 6 +- .../src/lib/utils/translations/da.json | 2 +- .../src/lib/utils/translations/de.json | 2 +- .../src/lib/utils/translations/es.json | 2 +- .../src/lib/utils/translations/fr.json | 2 +- .../src/lib/utils/translations/hi.json | 2 +- .../src/lib/utils/translations/pl.json | 2 +- .../src/lib/utils/translations/pt.json | 2 +- .../src/lib/utils/translations/ru.json | 2 +- .../src/lib/utils/translations/vi.json | 2 +- .../src/lib/utils/validation/USAGE.md | 41 +- .../(auth)/verify-email-error/+page.svelte | 58 +- apps/dashboard/src/routes/+layout.svelte | 3 +- .../api/admin/cleanup-tokens/+server.ts | 4 +- .../api/admin/security-monitor/+server.ts | 10 +- .../routes/api/courses/analytics/+server.ts | 10 +- .../src/routes/api/courses/data/+server.ts | 13 +- .../routes/api/courses/exercises/+server.ts | 10 +- .../src/routes/api/courses/marks/+server.ts | 6 +- .../src/routes/api/polar/webhook/+server.ts | 12 +- .../src/routes/courses/[id]/+layout.svelte | 8 +- .../src/routes/invite/s/[hash]/+page.svelte | 8 +- .../src/routes/lms/settings/+layout.svelte | 1 - .../lms/settings/integrations/+page.svelte | 1 - apps/website/.prettierrc | 14 - apps/website/FOLDER_STRUCTURE.md | 12 +- apps/website/package.json | 1 + .../src/lib/components/blog-list-item.svelte | 20 +- .../components/core-feature-section.svelte | 18 +- .../src/lib/components/core-features.svelte | 39 +- .../src/lib/components/custom-img.svelte | 7 +- apps/website/src/lib/components/faq.svelte | 12 +- apps/website/src/lib/components/footer.svelte | 19 +- apps/website/src/lib/components/hero.svelte | 19 +- apps/website/src/lib/components/modal.svelte | 6 +- .../src/lib/components/more-features.svelte | 46 +- .../src/lib/components/navigation.svelte | 22 +- .../src/lib/components/not-found.svelte | 19 +- .../src/lib/components/page-signup-cta.svelte | 15 +- .../lib/components/student-dashboard.svelte | 18 +- .../website/src/lib/components/success.svelte | 11 +- .../src/lib/components/testimonial.svelte | 20 +- .../src/lib/components/timeline.svelte | 22 +- .../src/lib/components/upload-widget.svelte | 11 +- .../lib/features/contact/feedback-form.svelte | 12 +- .../src/lib/features/contact/help-form.svelte | 12 +- .../lib/features/tools/progress/avatar.svelte | 12 +- .../features/tools/progress/background.svelte | 12 +- .../lib/features/tools/progress/mood.svelte | 6 +- .../lib/features/tools/progress/report.svelte | 49 +- .../lib/features/tools/stopwatch/timer.svelte | 15 +- .../features/tools/tic-tac-toe/board.svelte | 2 +- .../lib/features/tools/tic-tac-toe/board.ts | 6 +- .../tools/tic-tac-toe/winning-line.svelte | 8 +- apps/website/src/lib/utils/constants/tools.ts | 6 +- apps/website/src/mdsvex.svelte | 10 +- .../routes/(marketing)/bootcamps/+page.svelte | 94 +- .../customer-education/+page.svelte | 101 +- .../employee-training/+page.svelte | 90 +- .../(redirects)/devanddesign/+page.server.ts | 5 +- apps/website/src/routes/api/blog/+server.ts | 4 +- .../website/src/routes/api/contact/+server.ts | 51 +- apps/website/src/routes/blog/+page.svelte | 12 +- apps/website/src/routes/contact/+page.svelte | 12 +- .../src/routes/oss-friends/+page.svelte | 29 +- apps/website/src/routes/oss-friends/+page.ts | 6 +- apps/website/src/routes/pricing/+page.svelte | 17 +- apps/website/src/routes/roadmap/+page.svelte | 16 +- apps/website/src/routes/teach/+page.svelte | 19 +- apps/website/src/routes/tools/+layout.svelte | 12 +- apps/website/src/routes/tools/+page.svelte | 28 +- .../src/routes/tools/pomodoro/+page.svelte | 22 +- .../src/routes/tools/tic-tac-toe/+page.svelte | 4 +- apps/website/static/manifest.json | 46 +- lefthook.yml | 12 + package.json | 11 +- packages/storybook/HOW_TO_CREATE_STORIES.md | 42 +- packages/storybook/package.json | 1 + .../storybook/src/atoms/accordion/fields.ts | 10 +- .../storybook/src/atoms/checkbox/fields.ts | 9 +- .../storybook/src/atoms/collapsible/fields.ts | 7 +- .../storybook/src/atoms/command/fields.ts | 9 +- .../src/atoms/item/item.stories.svelte | 10 +- .../storybook/src/atoms/menubar/fields.ts | 8 +- .../storybook/src/atoms/pagination/fields.ts | 8 +- .../storybook/src/atoms/radio-group/fields.ts | 10 +- packages/storybook/src/atoms/switch/fields.ts | 9 +- packages/storybook/src/atoms/tabs/fields.ts | 8 +- .../storybook/src/atoms/tooltip/fields.ts | 7 +- .../src/molecules/hover-card/fields.ts | 1 - .../src/molecules/icon-button/fields.ts | 1 - .../src/molecules/input-group/fields.ts | 1 - .../storybook/src/molecules/page/fields.ts | 1 - .../password/password.stories.svelte | 1 - packages/ui/src/base/button-group/index.ts | 20 +- packages/ui/src/base/card/index.ts | 44 +- .../ui/src/base/drawer/drawer-close.svelte | 4 +- .../ui/src/base/drawer/drawer-trigger.svelte | 4 +- packages/ui/src/base/drawer/drawer.svelte | 14 +- packages/ui/src/base/drawer/index.ts | 68 +- packages/ui/src/base/empty/index.ts | 38 +- packages/ui/src/base/hover-card/index.ts | 15 +- packages/ui/src/base/input-group/index.ts | 38 +- packages/ui/src/base/input/index.ts | 8 +- packages/ui/src/base/item/index.ts | 62 +- packages/ui/src/base/kbd/index.ts | 14 +- packages/ui/src/base/navigation-menu/index.ts | 50 +- packages/ui/src/base/page/page-action.svelte | 8 +- .../ui/src/base/page/page-subtitle.svelte | 1 - .../src/base/password/password-util.svelte.ts | 185 +- packages/ui/src/base/password/types.ts | 43 +- packages/ui/src/base/separator/index.ts | 8 +- packages/ui/src/base/textarea/index.ts | 8 +- packages/ui/src/base/toggle/index.ts | 13 +- packages/ui/src/custom/domain-input/index.ts | 1 - .../src/custom/icon-button/icon-button.svelte | 8 +- packages/ui/src/custom/icon-button/index.ts | 1 - packages/ui/src/custom/image-cropper/utils.ts | 106 +- packages/ui/src/custom/input-field/index.ts | 1 - packages/ui/src/custom/password/index.ts | 1 - .../ui/src/custom/password/password.svelte | 3 +- packages/ui/src/custom/search/index.ts | 1 - .../ui/src/custom/underline-tabs/index.ts | 18 +- .../underline-tabs-utils.svelte.ts | 106 +- packages/ui/src/hooks/use-clipboard.svelte.ts | 94 +- packages/ui/src/output.css | 3808 ++++++++++++++++- packages/utils/package.json | 3 +- .../utils/src/functions/fileValidation.ts | 8 +- packages/utils/src/functions/index.ts | 2 +- packages/utils/src/validation/README.md | 92 +- packages/utils/src/validation/auth/forgot.ts | 4 +- packages/utils/src/validation/auth/reset.ts | 6 +- pnpm-lock.yaml | 43 +- 214 files changed, 6191 insertions(+), 2880 deletions(-) create mode 100644 apps/api/.prettierignore delete mode 100644 apps/course-app/.prettierignore delete mode 100644 apps/course-app/.prettierrc delete mode 100644 apps/website/.prettierrc create mode 100644 lefthook.yml diff --git a/.prettierignore b/.prettierignore index 9e3bea4a..a5574698 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,4 +5,12 @@ build _template supabase static -dist \ No newline at end of file +dist +docker + +**/course-app +apps/docs +apps/api/static + +pnpm-lock.yaml +*.md \ No newline at end of file diff --git a/apps/api/.prettierignore b/apps/api/.prettierignore new file mode 100644 index 00000000..7b4d4ba2 --- /dev/null +++ b/apps/api/.prettierignore @@ -0,0 +1 @@ +static diff --git a/apps/api/ROUTE_NAMING_BEST_PRACTICES.md b/apps/api/ROUTE_NAMING_BEST_PRACTICES.md index f1cffe40..13b9d689 100644 --- a/apps/api/ROUTE_NAMING_BEST_PRACTICES.md +++ b/apps/api/ROUTE_NAMING_BEST_PRACTICES.md @@ -56,9 +56,9 @@ GET /lessons (which course?) ```typescript // ✅ OK - Top-level resources -GET /account -PUT /account/user -GET /account/subscription +GET / account; +PUT / account / user; +GET / account / subscription; ``` --- @@ -67,13 +67,13 @@ GET /account/subscription Use HTTP methods correctly for their intended purpose: -| Method | Purpose | Idempotent | Response Body | -|--------|---------|------------|---------------| -| `GET` | Retrieve resource(s) | ✅ Yes | Always | -| `POST` | Create new resource | ❌ No | Created resource | -| `PUT` | Replace entire resource | ✅ Yes | Updated resource | -| `PATCH` | Partial update | ✅ Yes | Updated resource | -| `DELETE` | Remove resource | ✅ Yes | Usually empty (204) | +| Method | Purpose | Idempotent | Response Body | +| -------- | ----------------------- | ---------- | ------------------- | +| `GET` | Retrieve resource(s) | ✅ Yes | Always | +| `POST` | Create new resource | ❌ No | Created resource | +| `PUT` | Replace entire resource | ✅ Yes | Updated resource | +| `PATCH` | Partial update | ✅ Yes | Updated resource | +| `DELETE` | Remove resource | ✅ Yes | Usually empty (204) | ```typescript // ✅ GOOD - Correct method usage @@ -112,11 +112,13 @@ POST /lessons?courseId=123 // Relationship not obvious ``` **When to nest:** + - ✅ Child resource only exists within parent context - ✅ Child resource ID is unique within parent (e.g., `lessonId` within `courseId`) - ✅ Operations typically scoped to parent **When NOT to nest:** + - ❌ Resource can exist independently - ❌ Resource ID is globally unique - ❌ Operations span multiple parents @@ -154,6 +156,7 @@ GET /get-courses // Redundant verb **Pattern:** `POST /resource/:id/action` **When to use:** + - ✅ State transitions (publish, unpublish, activate, cancel) - ✅ Side effects (clone, duplicate, export) - ✅ Complex operations that don't fit CRUD @@ -181,14 +184,14 @@ GET /courses/search/javascript **Common query parameter patterns:** -| Purpose | Parameter | Example | -|---------|-----------|---------| -| Filtering | `status`, `type`, `role` | `?status=active&type=course` | -| Search | `q`, `search` | `?q=javascript` | -| Pagination | `page`, `limit`, `offset` | `?page=1&limit=20` | -| Sorting | `sort`, `order` | `?sort=created_at&order=desc` | -| Date range | `startDate`, `endDate` | `?startDate=2024-01-01` | -| Boolean flags | `include`, `expand` | `?include=lessons&expand=metadata` | +| Purpose | Parameter | Example | +| ------------- | ------------------------- | ---------------------------------- | +| Filtering | `status`, `type`, `role` | `?status=active&type=course` | +| Search | `q`, `search` | `?q=javascript` | +| Pagination | `page`, `limit`, `offset` | `?page=1&limit=20` | +| Sorting | `sort`, `order` | `?sort=created_at&order=desc` | +| Date range | `startDate`, `endDate` | `?startDate=2024-01-01` | +| Boolean flags | `include`, `expand` | `?include=lessons&expand=metadata` | --- @@ -303,9 +306,9 @@ import { organizationRouter } from '@api/routes/organization'; import { accountRouter } from '@api/routes/account'; export const app = new Hono() - .route('/course', courseRouter) // /course/* + .route('/course', courseRouter) // /course/* .route('/organization', organizationRouter) // /organization/* - .route('/account', accountRouter); // /account/* + .route('/account', accountRouter); // /account/* ``` --- @@ -321,37 +324,37 @@ export const courseRouter = new Hono() .get('/', authMiddleware, zValidator('query', ZListCourses), async (c) => { // GET /course?orgId=123&status=published&page=1&limit=20 }) - + // Get single course .get('/:courseId', authMiddleware, zValidator('param', ZGetCourse), async (c) => { // GET /course/:courseId }) - + // Create course .post('/', authMiddleware, zValidator('json', ZCreateCourse), async (c) => { // POST /course }) - + // Update course (full) .put('/:courseId', authMiddleware, zValidator('json', ZUpdateCourse), async (c) => { // PUT /course/:courseId }) - + // Update course (partial) .patch('/:courseId', authMiddleware, zValidator('json', ZPatchCourse), async (c) => { // PATCH /course/:courseId }) - + // Delete course .delete('/:courseId', authMiddleware, zValidator('param', ZDeleteCourse), async (c) => { // DELETE /course/:courseId }) - + // Actions .post('/:courseId/clone', authMiddleware, zValidator('json', ZCloneCourse), async (c) => { // POST /course/:courseId/clone }) - + .post('/:courseId/publish', authMiddleware, zValidator('param', ZPublishCourse), async (c) => { // POST /course/:courseId/publish }); @@ -366,22 +369,22 @@ export const lessonRouter = new Hono() .get('/', authMiddleware, zValidator('query', ZListLessons), async (c) => { // GET /course/:courseId/lesson?page=1&limit=20 }) - + // Get single lesson .get('/:lessonId', authMiddleware, zValidator('param', ZGetLesson), async (c) => { // GET /course/:courseId/lesson/:lessonId }) - + // Create lesson .post('/', authMiddleware, zValidator('json', ZCreateLesson), async (c) => { // POST /course/:courseId/lesson }) - + // Update lesson .put('/:lessonId', authMiddleware, zValidator('json', ZUpdateLesson), async (c) => { // PUT /course/:courseId/lesson/:lessonId }) - + // Delete lesson .delete('/:lessonId', authMiddleware, zValidator('param', ZDeleteLesson), async (c) => { // DELETE /course/:courseId/lesson/:lessonId @@ -391,7 +394,7 @@ export const lessonRouter = new Hono() // apps/api/src/routes/course/course.ts export const courseRouter = new Hono() // ... course routes ... - .route('/:courseId/lesson', lessonRouter); // Nested under course + .route('/:courseId/lesson', lessonRouter); // Nested under course ``` ### Example 3: Organization with Team @@ -403,31 +406,31 @@ export const organizationRouter = new Hono() .get('/', authMiddleware, zValidator('query', ZListOrgs), async (c) => { // GET /organization?siteName=acme }) - + // Get organization .get('/:orgId', authMiddleware, zValidator('param', ZGetOrg), async (c) => { // GET /organization/:orgId }) - + // Create organization .post('/', authMiddleware, zValidator('json', ZCreateOrg), async (c) => { // POST /organization }) - + // Update organization .put('/:orgId', authMiddleware, zValidator('json', ZUpdateOrg), async (c) => { // PUT /organization/:orgId }) - + // Team management (nested) .get('/:orgId/team', authMiddleware, zValidator('param', ZGetTeam), async (c) => { // GET /organization/:orgId/team }) - + .post('/:orgId/team/invite', authMiddleware, zValidator('json', ZInviteTeam), async (c) => { // POST /organization/:orgId/team/invite }) - + .delete('/:orgId/team/:memberId', authMiddleware, zValidator('param', ZRemoveMember), async (c) => { // DELETE /organization/:orgId/team/:memberId }); @@ -457,16 +460,16 @@ DELETE /courses/:id ```typescript // ❌ BAD -GET /course -POST /courses -GET /organization -POST /organizations +GET / course; +POST / courses; +GET / organization; +POST / organizations; // ✅ GOOD -GET /courses -POST /courses -GET /organizations -POST /organizations +GET / courses; +POST / courses; +GET / organizations; +POST / organizations; ``` ### ❌ Don't: Use query parameters for required identifiers @@ -540,4 +543,3 @@ When creating a new route, ensure: 8. **Organized by domain** in code structure Following these conventions ensures your API is **predictable**, **maintainable**, and **developer-friendly**. - diff --git a/apps/api/package.json b/apps/api/package.json index d543d109..5468ccea 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -17,7 +17,7 @@ "test": "vitest NODE_ENV=test", "test:coverage": "vitest run --coverage", "lint": "eslint . --ext .ts", - "format": "prettier --write \"src/**/*.ts\"", + "format": "prettier --write .", "upload:openapi": "tsx scripts/upload-openapi-spec.ts" }, "repository": { diff --git a/apps/api/scripts/README.md b/apps/api/scripts/README.md index ccd54105..41bb8caa 100644 --- a/apps/api/scripts/README.md +++ b/apps/api/scripts/README.md @@ -11,6 +11,7 @@ pnpm run upload:openapi ``` ### Direct script usage + ```bash tsx scripts/upload-openapi-spec.ts ``` @@ -49,6 +50,7 @@ This script is integrated with a GitHub Action that automatically generates and ### Automatic Workflow The GitHub Action runs on: + - **Push to main** - Automatically uploads to R2 - **Pull Requests** - Generates spec for review (no upload) - **Manual dispatch** - Allows manual triggering with custom parameters @@ -57,7 +59,7 @@ The GitHub Action runs on: 1. **Required secrets:** - `CLOUDFLARE_ACCESS_KEY` - - `CLOUDFLARE_SECRET_ACCESS_KEY` + - `CLOUDFLARE_SECRET_ACCESS_KEY` - `CLOUDFLARE_ACCOUNT_ID` - `PUBLIC_SUPABASE_URL` - `PUBLIC_SUPABASE_ANON_KEY` diff --git a/apps/api/scripts/upload-openapi-spec.ts b/apps/api/scripts/upload-openapi-spec.ts index 36280a78..4d04f6cd 100644 --- a/apps/api/scripts/upload-openapi-spec.ts +++ b/apps/api/scripts/upload-openapi-spec.ts @@ -24,11 +24,7 @@ class OpenAPISpecGenerator { } private initializeS3Client() { - if ( - env.CLOUDFLARE_ACCESS_KEY && - env.CLOUDFLARE_SECRET_ACCESS_KEY && - env.CLOUDFLARE_ACCOUNT_ID - ) { + if (env.CLOUDFLARE_ACCESS_KEY && env.CLOUDFLARE_SECRET_ACCESS_KEY && env.CLOUDFLARE_ACCOUNT_ID) { this.s3Client = new S3Client({ region: 'auto', endpoint: `https://${env.CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com`, diff --git a/apps/api/src/middlewares/rate-limiter.ts b/apps/api/src/middlewares/rate-limiter.ts index 7732b111..d7a4e02b 100644 --- a/apps/api/src/middlewares/rate-limiter.ts +++ b/apps/api/src/middlewares/rate-limiter.ts @@ -36,7 +36,6 @@ export const createRateLimiter = (options: RateLimiterOptions = {}): MiddlewareH } try { - // Generate rate limit key const key = opts.keyGenerator(c); diff --git a/apps/api/src/utils/redis/limiter.ts b/apps/api/src/utils/redis/limiter.ts index fcc0c804..bb5a2b3d 100644 --- a/apps/api/src/utils/redis/limiter.ts +++ b/apps/api/src/utils/redis/limiter.ts @@ -13,19 +13,13 @@ export class RedisRateLimiter { private windowMs: number; private maxRequests: number; - constructor( - redis: Redis, - windowMs: number = DEFAULT_WINDOW_MS, - maxRequests: number = DEFAULT_MAX_REQUESTS - ) { + constructor(redis: Redis, windowMs: number = DEFAULT_WINDOW_MS, maxRequests: number = DEFAULT_MAX_REQUESTS) { this.redis = redis; this.windowMs = windowMs; this.maxRequests = maxRequests; } - async isAllowed( - key: string - ): Promise<{ allowed: boolean; remaining: number; resetTime: number }> { + async isAllowed(key: string): Promise<{ allowed: boolean; remaining: number; resetTime: number }> { const now = Date.now(); const windowStart = now - this.windowMs; const keyWithPrefix = `${RATE_LIMIT_KEY_PREFIX}${key}`; diff --git a/apps/api/static/course.html b/apps/api/static/course.html index ca9560e8..2325b73e 100644 --- a/apps/api/static/course.html +++ b/apps/api/static/course.html @@ -154,20 +154,23 @@

Extra Resources

-

Slide Link:

Slide Link:

Open Here

Video Link:

diff --git a/apps/api/static/lesson.html b/apps/api/static/lesson.html index b31ff14c..3e5901e5 100644 --- a/apps/api/static/lesson.html +++ b/apps/api/static/lesson.html @@ -158,20 +158,25 @@

Extra Resources

-

Slide Link:

Open Here +

Slide Link:

+ + Open Here +

Video Link:

diff --git a/apps/course-app/.prettierignore b/apps/course-app/.prettierignore deleted file mode 100644 index ab78a95d..00000000 --- a/apps/course-app/.prettierignore +++ /dev/null @@ -1,4 +0,0 @@ -# Package Managers -package-lock.json -pnpm-lock.yaml -yarn.lock diff --git a/apps/course-app/.prettierrc b/apps/course-app/.prettierrc deleted file mode 100644 index abaa84f9..00000000 --- a/apps/course-app/.prettierrc +++ /dev/null @@ -1,15 +0,0 @@ -{ - "useTabs": false, - "singleQuote": true, - "trailingComma": "none", - "printWidth": 100, - "plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"], - "overrides": [ - { - "files": "*.svelte", - "options": { - "parser": "svelte" - } - } - ] -} diff --git a/apps/course-app/components.json b/apps/course-app/components.json index b2c229a7..1740726b 100644 --- a/apps/course-app/components.json +++ b/apps/course-app/components.json @@ -1,14 +1,14 @@ { - "$schema": "https://shadcn-svelte.com/schema.json", - "style": "new-york", - "tailwind": { - "config": "tailwind.config.ts", - "css": "src/app.css", - "baseColor": "zinc" - }, - "aliases": { - "components": "$lib/components", - "utils": "$lib/utils" - }, - "typescript": true -} \ No newline at end of file + "$schema": "https://shadcn-svelte.com/schema.json", + "style": "new-york", + "tailwind": { + "config": "tailwind.config.ts", + "css": "src/app.css", + "baseColor": "zinc" + }, + "aliases": { + "components": "$lib/components", + "utils": "$lib/utils" + }, + "typescript": true +} diff --git a/apps/course-app/package.json b/apps/course-app/package.json index 2d7b6e11..27641728 100644 --- a/apps/course-app/package.json +++ b/apps/course-app/package.json @@ -9,7 +9,8 @@ "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "format": "prettier --write .", + "format": "echo 'disabled'", + "format:run": "prettier --write .", "lint": "prettier --check .", "test:unit": "vitest", "test": "npm run test:unit -- --run" diff --git a/apps/course-app/postcss.config.js b/apps/course-app/postcss.config.js index 0f772168..ba807304 100644 --- a/apps/course-app/postcss.config.js +++ b/apps/course-app/postcss.config.js @@ -1,6 +1,6 @@ export default { - plugins: { - tailwindcss: {}, - autoprefixer: {} - } + plugins: { + tailwindcss: {}, + autoprefixer: {} + } }; diff --git a/apps/course-app/src/app.d.ts b/apps/course-app/src/app.d.ts index da08e6da..520c4217 100644 --- a/apps/course-app/src/app.d.ts +++ b/apps/course-app/src/app.d.ts @@ -1,13 +1,13 @@ // See https://svelte.dev/docs/kit/types#app.d.ts // for information about these interfaces declare global { - namespace App { - // interface Error {} - // interface Locals {} - // interface PageData {} - // interface PageState {} - // interface Platform {} - } + namespace App { + // interface Error {} + // interface Locals {} + // interface PageData {} + // interface PageState {} + // interface Platform {} + } } export {}; diff --git a/apps/course-app/src/app.html b/apps/course-app/src/app.html index b6be97d7..039a3e72 100644 --- a/apps/course-app/src/app.html +++ b/apps/course-app/src/app.html @@ -1,47 +1,58 @@ + + + + - - - - + Courseapp by ClassroomIO – Create your SAAS Academy in Minutes + + + + + - Courseapp by ClassroomIO – Create your SAAS Academy in Minutes - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + + - - - - - - + %sveltekit.head% - %sveltekit.head% + + + - - - - - -
%sveltekit.body%
- - - \ No newline at end of file + +
%sveltekit.body%
+ + diff --git a/apps/course-app/src/demo.spec.ts b/apps/course-app/src/demo.spec.ts index e07cbbd7..964d2872 100644 --- a/apps/course-app/src/demo.spec.ts +++ b/apps/course-app/src/demo.spec.ts @@ -1,7 +1,7 @@ import { describe, it, expect } from 'vitest'; describe('sum test', () => { - it('adds 1 + 2 to equal 3', () => { - expect(1 + 2).toBe(3); - }); + it('adds 1 + 2 to equal 3', () => { + expect(1 + 2).toBe(3); + }); }); diff --git a/apps/course-app/src/lib/components/ui/button/index.ts b/apps/course-app/src/lib/components/ui/button/index.ts index f0f46124..605af131 100644 --- a/apps/course-app/src/lib/components/ui/button/index.ts +++ b/apps/course-app/src/lib/components/ui/button/index.ts @@ -1,50 +1,48 @@ -import type { Button as ButtonPrimitive } from "bits-ui"; -import { type VariantProps, tv } from "tailwind-variants"; -import Root from "./button.svelte"; +import type { Button as ButtonPrimitive } from 'bits-ui'; +import { type VariantProps, tv } from 'tailwind-variants'; +import Root from './button.svelte'; const buttonVariants = tv({ - base: "focus-visible:ring-ring inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50", - variants: { - variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90 shadow", - destructive: - "bg-destructive text-destructive-foreground hover:bg-destructive/90 shadow-sm", - outline: - "border-input bg-background hover:bg-accent hover:text-accent-foreground border shadow-sm", - secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-sm", - ghost: "hover:bg-accent hover:text-accent-foreground", - link: "text-primary underline-offset-4 hover:underline", - }, - size: { - default: "h-9 px-4 py-2", - sm: "h-8 rounded-md px-3 text-xs", - lg: "h-10 rounded-md px-8", - icon: "h-9 w-9", - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, + base: 'focus-visible:ring-ring inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50', + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary/90 shadow', + destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90 shadow-sm', + outline: 'border-input bg-background hover:bg-accent hover:text-accent-foreground border shadow-sm', + secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 shadow-sm', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline' + }, + size: { + default: 'h-9 px-4 py-2', + sm: 'h-8 rounded-md px-3 text-xs', + lg: 'h-10 rounded-md px-8', + icon: 'h-9 w-9' + } + }, + defaultVariants: { + variant: 'default', + size: 'default' + } }); -type Variant = VariantProps["variant"]; -type Size = VariantProps["size"]; +type Variant = VariantProps['variant']; +type Size = VariantProps['size']; type Props = ButtonPrimitive.Props & { - variant?: Variant; - size?: Size; + variant?: Variant; + size?: Size; }; type Events = ButtonPrimitive.Events; export { - Root, - type Props, - type Events, - // - Root as Button, - type Props as ButtonProps, - type Events as ButtonEvents, - buttonVariants, + Root, + type Props, + type Events, + // + Root as Button, + type Props as ButtonProps, + type Events as ButtonEvents, + buttonVariants }; diff --git a/apps/course-app/src/lib/components/ui/dialog/index.ts b/apps/course-app/src/lib/components/ui/dialog/index.ts index b17ba5e6..950252f2 100644 --- a/apps/course-app/src/lib/components/ui/dialog/index.ts +++ b/apps/course-app/src/lib/components/ui/dialog/index.ts @@ -1,37 +1,37 @@ -import { Dialog as DialogPrimitive } from "bits-ui"; +import { Dialog as DialogPrimitive } from 'bits-ui'; -import Title from "./dialog-title.svelte"; -import Portal from "./dialog-portal.svelte"; -import Footer from "./dialog-footer.svelte"; -import Header from "./dialog-header.svelte"; -import Overlay from "./dialog-overlay.svelte"; -import Content from "./dialog-content.svelte"; -import Description from "./dialog-description.svelte"; +import Title from './dialog-title.svelte'; +import Portal from './dialog-portal.svelte'; +import Footer from './dialog-footer.svelte'; +import Header from './dialog-header.svelte'; +import Overlay from './dialog-overlay.svelte'; +import Content from './dialog-content.svelte'; +import Description from './dialog-description.svelte'; const Root = DialogPrimitive.Root; const Trigger = DialogPrimitive.Trigger; const Close = DialogPrimitive.Close; export { - Root, - Title, - Portal, - Footer, - Header, - Trigger, - Overlay, - Content, - Description, - Close, - // - Root as Dialog, - Title as DialogTitle, - Portal as DialogPortal, - Footer as DialogFooter, - Header as DialogHeader, - Trigger as DialogTrigger, - Overlay as DialogOverlay, - Content as DialogContent, - Description as DialogDescription, - Close as DialogClose, + Root, + Title, + Portal, + Footer, + Header, + Trigger, + Overlay, + Content, + Description, + Close, + // + Root as Dialog, + Title as DialogTitle, + Portal as DialogPortal, + Footer as DialogFooter, + Header as DialogHeader, + Trigger as DialogTrigger, + Overlay as DialogOverlay, + Content as DialogContent, + Description as DialogDescription, + Close as DialogClose }; diff --git a/apps/course-app/src/lib/components/ui/input/index.ts b/apps/course-app/src/lib/components/ui/input/index.ts index 75e3bc25..f7063b39 100644 --- a/apps/course-app/src/lib/components/ui/input/index.ts +++ b/apps/course-app/src/lib/components/ui/input/index.ts @@ -1,29 +1,29 @@ -import Root from "./input.svelte"; +import Root from './input.svelte'; export type FormInputEvent = T & { - currentTarget: EventTarget & HTMLInputElement; + currentTarget: EventTarget & HTMLInputElement; }; export type InputEvents = { - blur: FormInputEvent; - change: FormInputEvent; - click: FormInputEvent; - focus: FormInputEvent; - focusin: FormInputEvent; - focusout: FormInputEvent; - keydown: FormInputEvent; - keypress: FormInputEvent; - keyup: FormInputEvent; - mouseover: FormInputEvent; - mouseenter: FormInputEvent; - mouseleave: FormInputEvent; - mousemove: FormInputEvent; - paste: FormInputEvent; - input: FormInputEvent; - wheel: FormInputEvent; + blur: FormInputEvent; + change: FormInputEvent; + click: FormInputEvent; + focus: FormInputEvent; + focusin: FormInputEvent; + focusout: FormInputEvent; + keydown: FormInputEvent; + keypress: FormInputEvent; + keyup: FormInputEvent; + mouseover: FormInputEvent; + mouseenter: FormInputEvent; + mouseleave: FormInputEvent; + mousemove: FormInputEvent; + paste: FormInputEvent; + input: FormInputEvent; + wheel: FormInputEvent; }; export { - Root, - // - Root as Input, + Root, + // + Root as Input }; diff --git a/apps/course-app/src/lib/components/ui/label/index.ts b/apps/course-app/src/lib/components/ui/label/index.ts index 8bfca0b3..d1907754 100644 --- a/apps/course-app/src/lib/components/ui/label/index.ts +++ b/apps/course-app/src/lib/components/ui/label/index.ts @@ -1,7 +1,7 @@ -import Root from "./label.svelte"; +import Root from './label.svelte'; export { - Root, - // - Root as Label, + Root, + // + Root as Label }; diff --git a/apps/course-app/src/lib/components/ui/separator/index.ts b/apps/course-app/src/lib/components/ui/separator/index.ts index 82442d2c..2402e13e 100644 --- a/apps/course-app/src/lib/components/ui/separator/index.ts +++ b/apps/course-app/src/lib/components/ui/separator/index.ts @@ -1,7 +1,7 @@ -import Root from "./separator.svelte"; +import Root from './separator.svelte'; export { - Root, - // - Root as Separator, + Root, + // + Root as Separator }; diff --git a/apps/course-app/src/lib/components/ui/sonner/index.ts b/apps/course-app/src/lib/components/ui/sonner/index.ts index 1ad9f4a2..fcaf06bf 100644 --- a/apps/course-app/src/lib/components/ui/sonner/index.ts +++ b/apps/course-app/src/lib/components/ui/sonner/index.ts @@ -1 +1 @@ -export { default as Toaster } from "./sonner.svelte"; +export { default as Toaster } from './sonner.svelte'; diff --git a/apps/course-app/static/site.webmanifest b/apps/course-app/static/site.webmanifest index 45dc8a20..0b08af12 100644 --- a/apps/course-app/static/site.webmanifest +++ b/apps/course-app/static/site.webmanifest @@ -1 +1,11 @@ -{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file +{ + "name": "", + "short_name": "", + "icons": [ + { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, + { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/apps/course-app/vite.config.ts b/apps/course-app/vite.config.ts index c63c90e7..393426cc 100644 --- a/apps/course-app/vite.config.ts +++ b/apps/course-app/vite.config.ts @@ -3,15 +3,15 @@ import { sveltekit } from '@sveltejs/kit/vite'; import path from 'path'; export default defineConfig({ - plugins: [sveltekit()], + plugins: [sveltekit()], - test: { - include: ['src/**/*.{test,spec}.{js,ts}'] - }, + test: { + include: ['src/**/*.{test,spec}.{js,ts}'] + }, - resolve: { - alias: { - $lib: path.resolve('./src/lib') - } - } + resolve: { + alias: { + $lib: path.resolve('./src/lib') + } + } }); diff --git a/apps/dashboard/README.md b/apps/dashboard/README.md index 957b64ae..e97d4251 100644 --- a/apps/dashboard/README.md +++ b/apps/dashboard/README.md @@ -100,11 +100,9 @@ Here is what you need to be able to run Cal.com. You can install nvm from [here](https://github.com/nvm-sh/nvm). 4. Set up your `.env` file - - Duplicate `.env.example` to `.env` 5. Setup Supabase. - - Make sure you've downloaded the [supabase cli](https://github.com/supabase/cli) - Install and Start [docker](https://docs.docker.com/engine/install/) - Go to the project directory in your terminal and start supabase diff --git a/apps/dashboard/ai/BREADCRUMBS_REFACTOR.md b/apps/dashboard/ai/BREADCRUMBS_REFACTOR.md index 943601d6..e3ad3c5d 100644 --- a/apps/dashboard/ai/BREADCRUMBS_REFACTOR.md +++ b/apps/dashboard/ai/BREADCRUMBS_REFACTOR.md @@ -11,13 +11,13 @@ The navigation config types now support additional properties: ```typescript export interface NavItemConfig { // ... existing properties ... - + // NEW: Route-specific breadcrumb label override breadcrumbLabel?: string; - + // NEW: Custom resolver for dynamic breadcrumb titles breadcrumbResolver?: (params: Record) => string | Promise; - + // ENHANCED: Nested routes can now have nested routes (multi-level) nestedRoutes?: NestedRouteConfig[]; } @@ -25,13 +25,13 @@ export interface NavItemConfig { export interface NestedRouteConfig { path: string; titleKey: string; - + // NEW: Custom resolver for this nested route titleResolver?: (params: Record) => string | Promise; - + // NEW: Nested routes can have nested routes nestedRoutes?: NestedRouteConfig[]; - + // NEW: Support dynamic segments within nested routes supportsDynamicSegment?: boolean; } @@ -40,6 +40,7 @@ export interface NestedRouteConfig { ### Step 2: Update Breadcrumb Generation Calls #### Before (Org): + ```typescript import { generateBreadcrumbs } from './breadcrumb'; @@ -53,6 +54,7 @@ const breadcrumbs = generateBreadcrumbs( ``` #### After (Org - Store-Based Reactive): + ```svelte ``` #### Before (LMS): + ```typescript import { generateLmsBreadcrumbs } from './lms-breadcrumb'; import { currentCommunityQuestion } from '$features/community/utils/store'; @@ -93,6 +91,7 @@ const breadcrumbs = generateLmsBreadcrumbs( ``` #### After (LMS - Reactive Store-Based): + ```svelte ``` @@ -131,6 +125,7 @@ The refactored system supports up to 5 layers of nesting through recursive trave #### Example: Courses → Course → Lesson → Assignment → Submission (5 Layers) **Navigation Config** (in `org-navigation.ts`): + ```typescript { titleKey: 'org_navigation.courses', @@ -186,20 +181,20 @@ The refactored system supports up to 5 layers of nesting through recursive trave ``` **Breadcrumb Component** (in `app-breadcrumbs.svelte`): + ```svelte ``` **Alternative: Per-Route Store Resolvers** (More Granular): + ```svelte ``` @@ -288,6 +269,7 @@ The refactored system supports up to 5 layers of nesting through recursive trave #### Store-Based vs Async Resolvers **✅ Recommended: Store-Based (Reactive)** + ```typescript // In breadcrumb component const config = $derived(createOrgBreadcrumbConfigWithStore( @@ -300,15 +282,17 @@ 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 @@ -322,7 +306,7 @@ The `extractParamsFromPath` function extracts all path segments as `param0`, `pa // For URL: /courses/123/lessons/456/assignments/789 // params = { // param0: '123', // courseId -// param1: '456', // lessonId +// param1: '456', // lessonId // param2: '789' // assignmentId // } ``` @@ -350,41 +334,43 @@ 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 -); +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 @@ -393,6 +379,7 @@ const breadcrumbs = await generateBreadcrumbs( **URL**: `/org/myorg/courses/123/lessons/456/assignments/789/submissions/101` **Config**: + ```typescript { path: '/courses', @@ -492,8 +479,9 @@ The recursive function processes **each path segment** separately, incrementing **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=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" @@ -560,13 +548,8 @@ beforeEach(() => { // Test breadcrumb generation test('generates breadcrumbs for nested route', () => { - const breadcrumbs = generateBreadcrumbsSync( - '/org/test/courses/123/lessons/456', - '', - navItems, - config - ); - + const breadcrumbs = generateBreadcrumbsSync('/org/test/courses/123/lessons/456', '', navItems, config); + expect(breadcrumbs).toHaveLength(4); expect(breadcrumbs[0].label).toBe('Courses'); }); @@ -604,6 +587,7 @@ This allows gradual migration without breaking existing code. ### 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 @@ -619,6 +603,7 @@ This allows gradual migration without breaking existing code. ### Example: Community Post Title **Store** (`$features/community/utils/store.ts`): + ```typescript export const currentCommunityQuestion = writable<{ title: string | null }>({ title: null @@ -626,19 +611,20 @@ export const currentCommunityQuestion = writable<{ title: string | null }>({ ``` **Page Component** (`+page.svelte`): + ```svelte ``` @@ -669,11 +656,10 @@ For routes with multiple dynamic segments, use path-based logic: ```svelte ``` @@ -699,4 +685,3 @@ For routes with multiple dynamic segments, use path-based logic: 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 index b298ede5..c5992c4a 100644 --- a/apps/dashboard/ai/PAGE_ROUTE_GUIDE.md +++ b/apps/dashboard/ai/PAGE_ROUTE_GUIDE.md @@ -20,11 +20,13 @@ This guide explains how to create pages and routes in the dashboard application, 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` @@ -38,6 +40,7 @@ The dashboard has two main route contexts: **Location**: `apps/dashboard/src/routes/[context]/[feature]/+page.svelte` **Pattern**: + ```svelte - + - + %sveltekit.head% + - %sveltekit.head% - - - -
%sveltekit.body%
- - - \ No newline at end of file + +
%sveltekit.body%
+ + diff --git a/apps/dashboard/src/lib/components/Course/components/Analytics/AnalyticsSkeleton.svelte b/apps/dashboard/src/lib/components/Course/components/Analytics/AnalyticsSkeleton.svelte index 56a831ba..5b322406 100644 --- a/apps/dashboard/src/lib/components/Course/components/Analytics/AnalyticsSkeleton.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Analytics/AnalyticsSkeleton.svelte @@ -13,8 +13,8 @@
- - + +
@@ -26,10 +26,10 @@
- +
- - + +
@@ -37,7 +37,7 @@
- +
@@ -45,7 +45,7 @@
- +
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 f666cbcf..60a42743 100644 --- a/apps/dashboard/src/lib/components/Course/components/Ceritficate/Design.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Ceritficate/Design.svelte @@ -45,7 +45,7 @@ isSaving = true; try { - // Prevent free plan users from bypassing UI restrictions + // Prevent free plan users from bypassing UI restrictions if ($isFreePlan) { errors.description = 'Certificate customization is only available on paid plans'; throw new Error(errors.description); diff --git a/apps/dashboard/src/lib/components/Course/components/Ceritficate/IssueCertificateModal.svelte b/apps/dashboard/src/lib/components/Course/components/Ceritficate/IssueCertificateModal.svelte index 52231b39..e1e4eb84 100644 --- a/apps/dashboard/src/lib/components/Course/components/Ceritficate/IssueCertificateModal.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Ceritficate/IssueCertificateModal.svelte @@ -28,47 +28,47 @@ Send Certificate
-
-
- - -
-

- If you set this as automatic, certificates will be issued after the learner completes the course. -

-
-

or send a personalised/ custom certificate below:

-
-
- - +
+
+ + +
+

+ If you set this as automatic, certificates will be issued after the learner completes the course. +

+

or send a personalised/ custom certificate below:

+ +
+ + +
- + -
- -
- -
+
+ +
+ + diff --git a/apps/dashboard/src/lib/components/Course/components/Lesson/ActivateSectionsModal.svelte b/apps/dashboard/src/lib/components/Course/components/Lesson/ActivateSectionsModal.svelte index 12a53b59..d33d51f1 100644 --- a/apps/dashboard/src/lib/components/Course/components/Lesson/ActivateSectionsModal.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Lesson/ActivateSectionsModal.svelte @@ -39,34 +39,28 @@ if (!isOpen) handleClose(); }} > - + {$t(`course.navItem.lessons.section_prompt.header`)}
-
-

- {$t('course.navItem.lessons.section_prompt.title')} -

-

- {$t('course.navItem.lessons.section_prompt.description')} -

-
+
+

+ {$t('course.navItem.lessons.section_prompt.title')} +

+

+ {$t('course.navItem.lessons.section_prompt.description')} +

+
-
- - +
+ + +
-
diff --git a/apps/dashboard/src/lib/components/Course/components/Lesson/DeleteLessonConfirmation.svelte b/apps/dashboard/src/lib/components/Course/components/Lesson/DeleteLessonConfirmation.svelte index 1743e01d..17114237 100644 --- a/apps/dashboard/src/lib/components/Course/components/Lesson/DeleteLessonConfirmation.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Lesson/DeleteLessonConfirmation.svelte @@ -22,29 +22,23 @@ if (!isOpen) openDeleteModal = false; }} > - + {$t('delete_modal.label')}
-

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

+

+ {$t('delete_modal.content')}? +

-
- - +
+ + +
-
diff --git a/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/DeleteConfirmation.svelte b/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/DeleteConfirmation.svelte index 335af53e..0d518c66 100644 --- a/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/DeleteConfirmation.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/DeleteConfirmation.svelte @@ -7,37 +7,35 @@ let { onDelete = () => {}, onCancel = () => {} } = $props(); - + {$t('course.navItem.lessons.exercises.all_exercises.delete_confirmation.title')}
-

- {$t('course.navItem.lessons.exercises.all_exercises.delete_confirmation.sure')} -

+

+ {$t('course.navItem.lessons.exercises.all_exercises.delete_confirmation.sure')} +

-
- - +
+ + +
-
diff --git a/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/MarkExerciseModal.svelte b/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/MarkExerciseModal.svelte index f5b57598..19a6a63c 100644 --- a/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/MarkExerciseModal.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/MarkExerciseModal.svelte @@ -236,108 +236,108 @@ if (!isOpen) onClose(); }} > - + {data.title}
- {#if openDeletePrompt} -
-

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

+ {#if openDeletePrompt} +
+

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

-
- - +
+ + +
-
- {:else} - - {/if} -
-
-
-
-

- {$t('course.navItem.submissions.grading_modal.details')} - {#if data.isEarly} - - {$t('course.navItem.submissions.grading_modal.early')} + {/if} +

+
+
+
+

+ {$t('course.navItem.submissions.grading_modal.details')} + {#if data.isEarly} + + {$t('course.navItem.submissions.grading_modal.early')} + {:else} + + {$t('course.navItem.submissions.grading_modal.late')} + + {/if} +

+ + + - {:else} - - {$t('course.navItem.submissions.grading_modal.late')} - - {/if} -

+ +
+ + { + openDeletePrompt = true; + }} + > + {$t('delete_modal.label')} + + +
+
- - - - - - { - openDeletePrompt = true; - }} - > - {$t('delete_modal.label')} - - - -
+
+

+ {$t('course.navItem.submissions.grading_modal.total_grade')}: +

-
-

- {$t('course.navItem.submissions.grading_modal.total_grade')}: -

- - - {total}/{maxPoints} - -
- -
-

- {$t('course.navItem.submissions.grading_modal.student')}: -

- {#if data.student} -
- Student avatar -

- {data.student.fullname} -

-
- {/if} -
- -
-

- {$t('course.navItem.submissions.grading_modal.status')}: -

- { - if (value) { - handleStatusChange(value); - } - }} - > - - {status.text} - - - {#each SELECTABLE_STATUS as statusOption} - - {statusOption.text} - - {/each} - - -
- -
-

- {$t('course.navItem.submissions.grading_modal.add_comment')}: -

- -
- -
- - -
- +
-
diff --git a/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/OrderModal.svelte b/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/OrderModal.svelte index 0d58361c..5beb0d19 100644 --- a/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/OrderModal.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Lesson/Exercise/OrderModal.svelte @@ -71,34 +71,34 @@ {$t('course.navItem.lessons.exercises.all_exercises.order_questions')}
- {#each items as item (item.id)} -
- {#if item.type === 1} - - {:else if item.type === 2} - - {:else} - - {/if} - {` ${item.name}`} -
- {/each} -
+ use:dndzone={{ + items, + flipDurationMs, + dropTargetStyle: { + border: '2px #1d4ed8 solid', + 'border-style': 'dashed' + } + }} + onconsider={handleDndConsider} + onfinalize={handleDndFinalize} + class="w-full" + > + {#each items as item (item.id)} +
+ {#if item.type === 1} + + {:else if item.type === 2} + + {:else} + + {/if} + {` ${item.name}`} +
+ {/each} +
diff --git a/apps/dashboard/src/lib/components/Course/components/Lesson/Exercises/NewExerciseModal.svelte b/apps/dashboard/src/lib/components/Course/components/Lesson/Exercises/NewExerciseModal.svelte index 5b0ef451..4466a8b0 100644 --- a/apps/dashboard/src/lib/components/Course/components/Lesson/Exercises/NewExerciseModal.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Lesson/Exercises/NewExerciseModal.svelte @@ -153,7 +153,7 @@ if (!isOpen) handleCancelAddExercise(); }} > - + {$t('course.navItem.lessons.exercises.new_exercise_modal.heading')} @@ -161,147 +161,147 @@ {/if} {#if step === 0} -
-

- {$t('course.navItem.lessons.exercises.new_exercise_modal.how')}? -

- -
- {#each options as option} - - {/each} -
- -
- -
-
- {:else if step === 1} - {#if type === Type.SCRATCH} -
-
-

- {$t('course.navItem.lessons.exercises.new_exercise_modal.title')} -

- - -
- - -
-
-
- {:else if type === Type.TEMPLATE}
-

- {$t('course.navItem.lessons.exercises.new_exercise_modal.select_template')} +

+ {$t('course.navItem.lessons.exercises.new_exercise_modal.how')}?

-
-
- {#each tags as tag} - handleTagSelection(tag)} - >{tag} - {/each} -
- - {#if exerciseTemplateApi.isLoading} -
- {#each Array(16) as _} -
-
- -
- - -
-
-
- {/each} -
- {:else if exerciseTemplateApi.templates.length > 0} -
- {#each exerciseTemplateApi.templates as template} - - {/each} -
- {/if} - -
- - -
+
+ +
+ +
+

+ {option.title} + {#if option.isDisabled} + + {/if} +

+

{option.subtitle}

+
+ + {/each} +
+ +
+
- - + - {/if} + {/if} {/if}
diff --git a/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/Document/AddDocumentToLesson.svelte b/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/Document/AddDocumentToLesson.svelte index 5b5e1982..ca486c44 100644 --- a/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/Document/AddDocumentToLesson.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/Document/AddDocumentToLesson.svelte @@ -101,14 +101,14 @@ async function uploadDocument() { if (!selectedFile) return; - + // Prevent free plan users from bypassing UI restrictions if ($isFreePlan) { $lessonDocUpload.error = $t('upgrade.required'); snackbar.error($lessonDocUpload.error); return; } - + $lessonDocUpload.isUploading = true; try { diff --git a/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/Video/UploadVideo.svelte b/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/Video/UploadVideo.svelte index 1509b9b4..8cd9fa53 100644 --- a/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/Video/UploadVideo.svelte +++ b/apps/dashboard/src/lib/components/Course/components/Lesson/Materials/Video/UploadVideo.svelte @@ -39,7 +39,7 @@ // Prevent free plan users from bypassing UI restrictions if ($isFreePlan) { formRes = { - type: 'AUTHORIZATION_ERROR', + type: 'AUTHORIZATION_ERROR', status: 403, message: $t('upgrade.required') }; diff --git a/apps/dashboard/src/lib/components/Course/components/NewsFeed/DeleteFeedConfirmation.svelte b/apps/dashboard/src/lib/components/Course/components/NewsFeed/DeleteFeedConfirmation.svelte index 92056d44..9d783a4e 100644 --- a/apps/dashboard/src/lib/components/Course/components/NewsFeed/DeleteFeedConfirmation.svelte +++ b/apps/dashboard/src/lib/components/Course/components/NewsFeed/DeleteFeedConfirmation.svelte @@ -15,27 +15,18 @@ } - + Delete Feed
-

Are you sure you want to delete this feed?

+

Are you sure you want to delete this feed?

-
- - +
+ + +
-
diff --git a/apps/dashboard/src/lib/components/Course/components/NewsFeed/NewFeedModal.svelte b/apps/dashboard/src/lib/components/Course/components/NewsFeed/NewFeedModal.svelte index 5073982c..de8e40c0 100644 --- a/apps/dashboard/src/lib/components/Course/components/NewsFeed/NewFeedModal.svelte +++ b/apps/dashboard/src/lib/components/Course/components/NewsFeed/NewFeedModal.svelte @@ -127,30 +127,27 @@
- { - newPost = text; - }} - editorClass="max-h-[400px]" - placeholder={$t('course.navItem.news_feed.heading_button.placeholder')} - /> - {#if errors.newPost} -

{errors.newPost}

- {/if} -
-
- - + { + newPost = text; + }} + editorClass="max-h-[400px]" + placeholder={$t('course.navItem.news_feed.heading_button.placeholder')} + /> + {#if errors.newPost} +

{errors.newPost}

+ {/if} +
+
+ + +
-
-
+
diff --git a/apps/dashboard/src/lib/components/Course/components/People/DeleteConfirmation.svelte b/apps/dashboard/src/lib/components/Course/components/People/DeleteConfirmation.svelte index 5104cdcf..6201337a 100644 --- a/apps/dashboard/src/lib/components/Course/components/People/DeleteConfirmation.svelte +++ b/apps/dashboard/src/lib/components/Course/components/People/DeleteConfirmation.svelte @@ -12,32 +12,24 @@ } - + - + {$t('course.navItem.people.delete_confirmation.title')}
-

- {$t('course.navItem.people.delete_confirmation.sure')} {email}? -

+

+ {$t('course.navItem.people.delete_confirmation.sure')} {email}? +

-
- - +
+ + +
-
diff --git a/apps/dashboard/src/lib/features/community/components/delete-modal.svelte b/apps/dashboard/src/lib/features/community/components/delete-modal.svelte index c4dab1f1..7fffe651 100644 --- a/apps/dashboard/src/lib/features/community/components/delete-modal.svelte +++ b/apps/dashboard/src/lib/features/community/components/delete-modal.svelte @@ -20,37 +20,28 @@ }: Props = $props(); - + - {$t('community.delete.title')} {isQuestion - ? $t('community.delete.question') - : $t('community.delete.comment')} + {$t('community.delete.title')} + {isQuestion ? $t('community.delete.question') : $t('community.delete.comment')}
-

- {$t('community.delete.sure')} - {isQuestion ? $t('community.delete.question') : $t('community.delete.comment')}? -

+

+ {$t('community.delete.sure')} + {isQuestion ? $t('community.delete.question') : $t('community.delete.comment')}? +

-
- - +
+ + +
-
diff --git a/apps/dashboard/src/lib/features/community/utils/store.ts b/apps/dashboard/src/lib/features/community/utils/store.ts index f19669b6..a9c1dd36 100644 --- a/apps/dashboard/src/lib/features/community/utils/store.ts +++ b/apps/dashboard/src/lib/features/community/utils/store.ts @@ -7,4 +7,3 @@ export interface CurrentCommunityQuestion { export const currentCommunityQuestion = writable({ title: null }); - diff --git a/apps/dashboard/src/lib/features/course/components/copy-course-modal.svelte b/apps/dashboard/src/lib/features/course/components/copy-course-modal.svelte index f7963137..c0bb79cd 100644 --- a/apps/dashboard/src/lib/features/course/components/copy-course-modal.svelte +++ b/apps/dashboard/src/lib/features/course/components/copy-course-modal.svelte @@ -20,43 +20,41 @@ }); - + {$t('courses.copy_course.title')}
- + - + - {#if courseCloneApi.errors.general} -
{courseCloneApi.errors.general}
- {/if} + {#if courseCloneApi.errors.general} +
{courseCloneApi.errors.general}
+ {/if} -
- -
- +
+ +
+
diff --git a/apps/dashboard/src/lib/features/course/pages/attendance.svelte b/apps/dashboard/src/lib/features/course/pages/attendance.svelte index b10368d2..02d404a0 100644 --- a/apps/dashboard/src/lib/features/course/pages/attendance.svelte +++ b/apps/dashboard/src/lib/features/course/pages/attendance.svelte @@ -233,4 +233,3 @@ {/if} - diff --git a/apps/dashboard/src/lib/features/course/pages/lessons.svelte b/apps/dashboard/src/lib/features/course/pages/lessons.svelte index d74045f4..17ace15f 100644 --- a/apps/dashboard/src/lib/features/course/pages/lessons.svelte +++ b/apps/dashboard/src/lib/features/course/pages/lessons.svelte @@ -109,4 +109,3 @@ variant="page" /> {/if} - diff --git a/apps/dashboard/src/lib/features/lms/components/learning.svelte b/apps/dashboard/src/lib/features/lms/components/learning.svelte index fb0baf23..2442a40b 100644 --- a/apps/dashboard/src/lib/features/lms/components/learning.svelte +++ b/apps/dashboard/src/lib/features/lms/components/learning.svelte @@ -55,4 +55,3 @@ {/if} - diff --git a/apps/dashboard/src/lib/features/onboarding/utils/dom-protection.ts b/apps/dashboard/src/lib/features/onboarding/utils/dom-protection.ts index 8d0ebfaa..1d1bf4c2 100644 --- a/apps/dashboard/src/lib/features/onboarding/utils/dom-protection.ts +++ b/apps/dashboard/src/lib/features/onboarding/utils/dom-protection.ts @@ -2,9 +2,9 @@ import type { Readable } from 'svelte/store'; import { browser } from '$app/environment'; export interface DOMProtectionCleanup { - protectionInterval?: ReturnType; - domObserver?: MutationObserver; - cleanup: () => void; + protectionInterval?: ReturnType; + domObserver?: MutationObserver; + cleanup: () => void; } /** @@ -14,192 +14,190 @@ export interface DOMProtectionCleanup { * @param isEmailVerified - A readable store or getter function that returns whether email is verified * @returns Cleanup object with intervals, observers, and a cleanup function */ -export function setupDOMProtection( - isEmailVerified: Readable | (() => boolean | null) -): DOMProtectionCleanup { - if (!browser) { - return { - cleanup: () => {} - }; - } +export function setupDOMProtection(isEmailVerified: Readable | (() => boolean | null)): DOMProtectionCleanup { + if (!browser) { + return { + cleanup: () => {} + }; + } - let protectionInterval: ReturnType | undefined; - let domObserver: MutationObserver | undefined; + let protectionInterval: ReturnType | undefined; + let domObserver: MutationObserver | undefined; - // Helper to get current verification status - const getIsVerified = () => { - if (typeof isEmailVerified === 'function') { - return isEmailVerified() ?? false; - } - // For stores, we need to subscribe temporarily - this is a limitation - // In practice, the component should pass a getter function - let value = false; - const unsubscribe = isEmailVerified.subscribe((v) => { - value = v ?? false; - }); - unsubscribe(); - return value; - }; + // Helper to get current verification status + const getIsVerified = () => { + if (typeof isEmailVerified === 'function') { + return isEmailVerified() ?? false; + } + // For stores, we need to subscribe temporarily - this is a limitation + // In practice, the component should pass a getter function + let value = false; + const unsubscribe = isEmailVerified.subscribe((v) => { + value = v ?? false; + }); + unsubscribe(); + return value; + }; - // Function to disable all interactive elements - function disableInteractions() { - if (getIsVerified()) return; + // Function to disable all interactive elements + function disableInteractions() { + if (getIsVerified()) return; - // Disable all buttons except verification-related ones - const buttons = document.querySelectorAll('button, [role="button"], a[href], input[type="submit"]'); - buttons.forEach((button) => { - const el = button as HTMLElement; + // Disable all buttons except verification-related ones + const buttons = document.querySelectorAll('button, [role="button"], a[href], input[type="submit"]'); + buttons.forEach((button) => { + const el = button as HTMLElement; - // Skip if already protected or is verification-related - if ( - el.dataset.verificationProtected === 'true' || - el.closest('[data-verification-modal]') || - el.textContent?.toLowerCase().includes('verify') || - el.textContent?.toLowerCase().includes('resend') - ) { - return; - } + // Skip if already protected or is verification-related + if ( + el.dataset.verificationProtected === 'true' || + el.closest('[data-verification-modal]') || + el.textContent?.toLowerCase().includes('verify') || + el.textContent?.toLowerCase().includes('resend') + ) { + return; + } - el.dataset.verificationProtected = 'true'; - el.dataset.originalPointerEvents = el.style.pointerEvents || 'auto'; - el.style.pointerEvents = 'none'; - el.style.opacity = '0.5'; - el.style.cursor = 'not-allowed'; - el.setAttribute('title', 'Email verification required'); + el.dataset.verificationProtected = 'true'; + el.dataset.originalPointerEvents = el.style.pointerEvents || 'auto'; + el.style.pointerEvents = 'none'; + el.style.opacity = '0.5'; + el.style.cursor = 'not-allowed'; + el.setAttribute('title', 'Email verification required'); - // Add click prevention - el.addEventListener('click', preventInteraction, true); - el.addEventListener('mousedown', preventInteraction, true); - }); + // Add click prevention + el.addEventListener('click', preventInteraction, true); + el.addEventListener('mousedown', preventInteraction, true); + }); - // Disable all form inputs - const inputs = document.querySelectorAll('input, textarea, select'); - inputs.forEach((input) => { - const el = input as HTMLInputElement; + // Disable all form inputs + const inputs = document.querySelectorAll('input, textarea, select'); + inputs.forEach((input) => { + const el = input as HTMLInputElement; - // Skip if in verification modal - if (el.closest('[data-verification-modal]')) return; + // Skip if in verification modal + if (el.closest('[data-verification-modal]')) return; - if (!el.dataset.verificationProtected) { - el.dataset.verificationProtected = 'true'; - el.dataset.originalDisabled = el.disabled ? 'true' : 'false'; - el.disabled = true; - el.style.opacity = '0.5'; - el.addEventListener('focus', preventInteraction, true); - el.addEventListener('input', preventInteraction, true); - } - }); + if (!el.dataset.verificationProtected) { + el.dataset.verificationProtected = 'true'; + el.dataset.originalDisabled = el.disabled ? 'true' : 'false'; + el.disabled = true; + el.style.opacity = '0.5'; + el.addEventListener('focus', preventInteraction, true); + el.addEventListener('input', preventInteraction, true); + } + }); - // Disable contenteditable elements - const editables = document.querySelectorAll('[contenteditable="true"]'); - editables.forEach((el) => { - const element = el as HTMLElement; - if (!element.dataset.verificationProtected) { - element.dataset.verificationProtected = 'true'; - element.contentEditable = 'false'; - element.style.opacity = '0.5'; - } - }); - } + // Disable contenteditable elements + const editables = document.querySelectorAll('[contenteditable="true"]'); + editables.forEach((el) => { + const element = el as HTMLElement; + if (!element.dataset.verificationProtected) { + element.dataset.verificationProtected = 'true'; + element.contentEditable = 'false'; + element.style.opacity = '0.5'; + } + }); + } - function preventInteraction(event: Event) { - if (!getIsVerified()) { - event.preventDefault(); - event.stopImmediatePropagation(); + function preventInteraction(event: Event) { + if (!getIsVerified()) { + event.preventDefault(); + event.stopImmediatePropagation(); - // Show warning - if (event.target) { - const target = event.target as HTMLElement; - target.setAttribute('title', 'Please verify your email to use this feature'); - } + // Show warning + if (event.target) { + const target = event.target as HTMLElement; + target.setAttribute('title', 'Please verify your email to use this feature'); + } - // Optional: Show alert - console.warn('🔒 Email verification required to interact with this element'); - return false; - } - } + // Optional: Show alert + console.warn('🔒 Email verification required to interact with this element'); + return false; + } + } - // Function to restore interactions when verified - function enableInteractions() { - const protectedElements = document.querySelectorAll('[data-verification-protected="true"]'); - protectedElements.forEach((el) => { - const element = el as HTMLElement; + // Function to restore interactions when verified + function enableInteractions() { + const protectedElements = document.querySelectorAll('[data-verification-protected="true"]'); + protectedElements.forEach((el) => { + const element = el as HTMLElement; - // Restore styles - if (element.dataset.originalPointerEvents) { - element.style.pointerEvents = element.dataset.originalPointerEvents; - } else { - element.style.pointerEvents = ''; - } - element.style.opacity = ''; - element.style.cursor = ''; - element.removeAttribute('title'); + // Restore styles + if (element.dataset.originalPointerEvents) { + element.style.pointerEvents = element.dataset.originalPointerEvents; + } else { + element.style.pointerEvents = ''; + } + element.style.opacity = ''; + element.style.cursor = ''; + element.removeAttribute('title'); - // Restore functionality for inputs - if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA' || element.tagName === 'SELECT') { - const input = element as HTMLInputElement; - input.disabled = input.dataset.originalDisabled === 'true'; - } + // Restore functionality for inputs + if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA' || element.tagName === 'SELECT') { + const input = element as HTMLInputElement; + input.disabled = input.dataset.originalDisabled === 'true'; + } - // Restore contenteditable - if (element.hasAttribute('contenteditable')) { - element.contentEditable = 'true'; - } + // Restore contenteditable + if (element.hasAttribute('contenteditable')) { + element.contentEditable = 'true'; + } - // Remove event listeners - element.removeEventListener('click', preventInteraction, true); - element.removeEventListener('mousedown', preventInteraction, true); - element.removeEventListener('focus', preventInteraction, true); - element.removeEventListener('input', preventInteraction, true); + // Remove event listeners + element.removeEventListener('click', preventInteraction, true); + element.removeEventListener('mousedown', preventInteraction, true); + element.removeEventListener('focus', preventInteraction, true); + element.removeEventListener('input', preventInteraction, true); - // Clean up data attributes - delete element.dataset.verificationProtected; - delete element.dataset.originalPointerEvents; - delete element.dataset.originalDisabled; - }); - } + // Clean up data attributes + delete element.dataset.verificationProtected; + delete element.dataset.originalPointerEvents; + delete element.dataset.originalDisabled; + }); + } - // Set up mutation observer to protect new elements - if (window.MutationObserver) { - domObserver = new MutationObserver((mutations) => { - let shouldReapply = false; - mutations.forEach((mutation) => { - if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { - shouldReapply = true; - } - }); + // Set up mutation observer to protect new elements + if (window.MutationObserver) { + domObserver = new MutationObserver((mutations) => { + let shouldReapply = false; + mutations.forEach((mutation) => { + if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { + shouldReapply = true; + } + }); - if (shouldReapply && !getIsVerified()) { - setTimeout(disableInteractions, 100); - } - }); + if (shouldReapply && !getIsVerified()) { + setTimeout(disableInteractions, 100); + } + }); - domObserver.observe(document.body, { - childList: true, - subtree: true - }); - } + domObserver.observe(document.body, { + childList: true, + subtree: true + }); + } - // Run protection every second - protectionInterval = setInterval(() => { - if (getIsVerified()) { - enableInteractions(); - cleanup(); - } else { - disableInteractions(); - } - }, 1000); + // Run protection every second + protectionInterval = setInterval(() => { + if (getIsVerified()) { + enableInteractions(); + cleanup(); + } else { + disableInteractions(); + } + }, 1000); - // Initial protection - if (!getIsVerified()) { - disableInteractions(); - } + // Initial protection + if (!getIsVerified()) { + disableInteractions(); + } - // Add global CSS to prevent any missed interactions - if (document) { - const style = document.createElement('style'); - style.id = 'email-verification-protection'; - style.textContent = ` + // Add global CSS to prevent any missed interactions + if (document) { + const style = document.createElement('style'); + style.id = 'email-verification-protection'; + style.textContent = ` body:not(.email-verified) button:not([data-verification-modal] button), body:not(.email-verified) a:not([data-verification-modal] a), body:not(.email-verified) input:not([data-verification-modal] input), @@ -210,45 +208,44 @@ export function setupDOMProtection( user-select: none !important; } `; - document.head.appendChild(style); - } + document.head.appendChild(style); + } - function cleanup() { - if (protectionInterval) { - clearInterval(protectionInterval); - protectionInterval = undefined; - } - if (domObserver) { - domObserver.disconnect(); - domObserver = undefined; - } + function cleanup() { + if (protectionInterval) { + clearInterval(protectionInterval); + protectionInterval = undefined; + } + if (domObserver) { + domObserver.disconnect(); + domObserver = undefined; + } - // Clean up protection styles - if (browser) { - const style = document?.getElementById('email-verification-protection'); - if (style) style.remove(); - } - } + // Clean up protection styles + if (browser) { + const style = document?.getElementById('email-verification-protection'); + if (style) style.remove(); + } + } - return { - protectionInterval, - domObserver, - cleanup - }; + return { + protectionInterval, + domObserver, + cleanup + }; } /** * Updates the body class based on email verification status */ export function updateBodyClass(isEmailVerified: boolean | null) { - if (!browser) { - return; - } + if (!browser) { + return; + } - if (isEmailVerified) { - document.body.classList.add('email-verified'); - } else { - document.body.classList.remove('email-verified'); - } + if (isEmailVerified) { + document.body.classList.add('email-verified'); + } else { + document.body.classList.remove('email-verified'); + } } - diff --git a/apps/dashboard/src/lib/features/org/components/add-org-modal/add-org-modal.svelte b/apps/dashboard/src/lib/features/org/components/add-org-modal/add-org-modal.svelte index f8e344f5..de68c6bd 100644 --- a/apps/dashboard/src/lib/features/org/components/add-org-modal/add-org-modal.svelte +++ b/apps/dashboard/src/lib/features/org/components/add-org-modal/add-org-modal.svelte @@ -41,30 +41,29 @@ {$t('add_org.create_org')}
- - - {$t('add_org.name')} - - {#if orgApi.errors.name || orgApi.errors.orgName} - {orgApi.errors.name || orgApi.errors.orgName} - {/if} - + + + {$t('add_org.name')} + + {#if orgApi.errors.name || orgApi.errors.orgName} + {orgApi.errors.name || orgApi.errors.orgName} + {/if} + - - {$t('add_org.org_sitename')} - - {#if orgApi.errors.siteName || orgApi.errors.general} - {orgApi.errors.siteName || orgApi.errors.general} - {/if} - + + {$t('add_org.org_sitename')} + + {#if orgApi.errors.siteName || orgApi.errors.general} + {orgApi.errors.siteName || orgApi.errors.general} + {/if} + - - - - - + + + +
+
- diff --git a/apps/dashboard/src/lib/features/org/components/image-renderer.svelte b/apps/dashboard/src/lib/features/org/components/image-renderer.svelte index e469281a..8ccd3a2a 100644 --- a/apps/dashboard/src/lib/features/org/components/image-renderer.svelte +++ b/apps/dashboard/src/lib/features/org/components/image-renderer.svelte @@ -27,4 +27,3 @@

{$t('courses.course_card.error_message')}

{/await} - diff --git a/apps/dashboard/src/lib/features/org/components/landing-page/page-header.svelte b/apps/dashboard/src/lib/features/org/components/landing-page/page-header.svelte index f6e96798..d1ac6f1f 100644 --- a/apps/dashboard/src/lib/features/org/components/landing-page/page-header.svelte +++ b/apps/dashboard/src/lib/features/org/components/landing-page/page-header.svelte @@ -25,4 +25,3 @@ - diff --git a/apps/dashboard/src/lib/features/org/components/landing-page/page-loader.svelte b/apps/dashboard/src/lib/features/org/components/landing-page/page-loader.svelte index 465ec1db..4b2b47d4 100644 --- a/apps/dashboard/src/lib/features/org/components/landing-page/page-loader.svelte +++ b/apps/dashboard/src/lib/features/org/components/landing-page/page-loader.svelte @@ -24,4 +24,3 @@ - diff --git a/apps/dashboard/src/lib/features/org/components/new-org-modal-store.ts b/apps/dashboard/src/lib/features/org/components/new-org-modal-store.ts index e1420e83..dce85cb1 100644 --- a/apps/dashboard/src/lib/features/org/components/new-org-modal-store.ts +++ b/apps/dashboard/src/lib/features/org/components/new-org-modal-store.ts @@ -3,4 +3,3 @@ import { writable } from 'svelte/store'; export const newOrgModal = writable({ open: false }); - diff --git a/apps/dashboard/src/lib/features/org/components/quiz/delete-modal.svelte b/apps/dashboard/src/lib/features/org/components/quiz/delete-modal.svelte index 43db1a22..0567f609 100644 --- a/apps/dashboard/src/lib/features/org/components/quiz/delete-modal.svelte +++ b/apps/dashboard/src/lib/features/org/components/quiz/delete-modal.svelte @@ -43,29 +43,29 @@
-

- {#if $deleteModal.isQuestion} - {$t('components.quiz.delete_question')} - {:else} - {$t('components.quiz.delete_quiz')} - {/if} - {$t('components.quiz.lose_content')} -

+

+ {#if $deleteModal.isQuestion} + {$t('components.quiz.delete_question')} + {:else} + {$t('components.quiz.delete_quiz')} + {/if} + {$t('components.quiz.lose_content')} +

-
- +
+ +
-
diff --git a/apps/dashboard/src/lib/features/org/components/quiz/new-quiz-modal.svelte b/apps/dashboard/src/lib/features/org/components/quiz/new-quiz-modal.svelte index eef85fe0..c634593b 100644 --- a/apps/dashboard/src/lib/features/org/components/quiz/new-quiz-modal.svelte +++ b/apps/dashboard/src/lib/features/org/components/quiz/new-quiz-modal.svelte @@ -114,28 +114,27 @@ - {$createQuizModal.openEdit ? $t('components.quiz.update') : $t('components.quiz.create')} {$t( - 'components.quiz.a_quiz' - )} + {$createQuizModal.openEdit ? $t('components.quiz.update') : $t('components.quiz.create')} + {$t('components.quiz.a_quiz')}
- + -
- -
- +
+ +
+
diff --git a/apps/dashboard/src/lib/features/org/components/quiz/quiz-question.svelte b/apps/dashboard/src/lib/features/org/components/quiz/quiz-question.svelte index 51c18c91..43318734 100644 --- a/apps/dashboard/src/lib/features/org/components/quiz/quiz-question.svelte +++ b/apps/dashboard/src/lib/features/org/components/quiz/quiz-question.svelte @@ -53,10 +53,7 @@ errorMessage={optionHasError(option.id, currentError.options) && $t('components.quiz.label')} />
- (option.isCorrect = !option.isCorrect)} - class={option.isCorrect ? 'success' : ''} - > + (option.isCorrect = !option.isCorrect)} class={option.isCorrect ? 'success' : ''}>
diff --git a/apps/dashboard/src/lib/features/org/components/settings/landing-page-store.ts b/apps/dashboard/src/lib/features/org/components/settings/landing-page-store.ts index 470f4b11..7f2a7ab2 100644 --- a/apps/dashboard/src/lib/features/org/components/settings/landing-page-store.ts +++ b/apps/dashboard/src/lib/features/org/components/settings/landing-page-store.ts @@ -114,4 +114,3 @@ export const landingPageSettings: Writable = writable({ show: true } }); - diff --git a/apps/dashboard/src/lib/features/ui/analytics/activity-card.svelte b/apps/dashboard/src/lib/features/ui/analytics/activity-card.svelte index d8489865..da6ffe9a 100644 --- a/apps/dashboard/src/lib/features/ui/analytics/activity-card.svelte +++ b/apps/dashboard/src/lib/features/ui/analytics/activity-card.svelte @@ -38,4 +38,3 @@

- diff --git a/apps/dashboard/src/lib/features/ui/analytics/hero-profile-card.svelte b/apps/dashboard/src/lib/features/ui/analytics/hero-profile-card.svelte index 01afc221..e18eb7a8 100644 --- a/apps/dashboard/src/lib/features/ui/analytics/hero-profile-card.svelte +++ b/apps/dashboard/src/lib/features/ui/analytics/hero-profile-card.svelte @@ -44,4 +44,3 @@ - diff --git a/apps/dashboard/src/lib/features/ui/analytics/loading-page.svelte b/apps/dashboard/src/lib/features/ui/analytics/loading-page.svelte index 0850116b..425bde85 100644 --- a/apps/dashboard/src/lib/features/ui/analytics/loading-page.svelte +++ b/apps/dashboard/src/lib/features/ui/analytics/loading-page.svelte @@ -10,4 +10,3 @@ - diff --git a/apps/dashboard/src/lib/features/ui/delete-modal.svelte b/apps/dashboard/src/lib/features/ui/delete-modal.svelte index f4dc91cf..15a275d7 100644 --- a/apps/dashboard/src/lib/features/ui/delete-modal.svelte +++ b/apps/dashboard/src/lib/features/ui/delete-modal.svelte @@ -23,27 +23,18 @@ {$t('delete_modal.label')}
-

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

+

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

-
- - +
+ + +
-
- diff --git a/apps/dashboard/src/lib/features/ui/icons/arrow-top-right.svelte b/apps/dashboard/src/lib/features/ui/icons/arrow-top-right.svelte index 682d8f91..883eb60d 100644 --- a/apps/dashboard/src/lib/features/ui/icons/arrow-top-right.svelte +++ b/apps/dashboard/src/lib/features/ui/icons/arrow-top-right.svelte @@ -15,4 +15,3 @@ - diff --git a/apps/dashboard/src/lib/features/ui/icons/circle-check-icon.svelte b/apps/dashboard/src/lib/features/ui/icons/circle-check-icon.svelte index 4f859b79..3e6000d5 100644 --- a/apps/dashboard/src/lib/features/ui/icons/circle-check-icon.svelte +++ b/apps/dashboard/src/lib/features/ui/icons/circle-check-icon.svelte @@ -51,4 +51,3 @@ fill: none; /* Ensure the checkmark path itself is not filled */ } - diff --git a/apps/dashboard/src/lib/features/ui/icons/courses-empty-icon.svelte b/apps/dashboard/src/lib/features/ui/icons/courses-empty-icon.svelte index 721742eb..37d68245 100644 --- a/apps/dashboard/src/lib/features/ui/icons/courses-empty-icon.svelte +++ b/apps/dashboard/src/lib/features/ui/icons/courses-empty-icon.svelte @@ -41,4 +41,3 @@ stroke-linejoin="bevel" /> - diff --git a/apps/dashboard/src/lib/features/ui/icons/email-sent-icon.svelte b/apps/dashboard/src/lib/features/ui/icons/email-sent-icon.svelte index 0715d45f..f09599ce 100644 --- a/apps/dashboard/src/lib/features/ui/icons/email-sent-icon.svelte +++ b/apps/dashboard/src/lib/features/ui/icons/email-sent-icon.svelte @@ -15,4 +15,3 @@ stroke-width="5" /> - diff --git a/apps/dashboard/src/lib/features/ui/icons/error-icon-dark-mode.svelte b/apps/dashboard/src/lib/features/ui/icons/error-icon-dark-mode.svelte index 6cd51e1a..a03dfc4b 100644 --- a/apps/dashboard/src/lib/features/ui/icons/error-icon-dark-mode.svelte +++ b/apps/dashboard/src/lib/features/ui/icons/error-icon-dark-mode.svelte @@ -183,4 +183,3 @@ fill="#E4E4E4" /> - diff --git a/apps/dashboard/src/lib/features/ui/icons/error-icon.svelte b/apps/dashboard/src/lib/features/ui/icons/error-icon.svelte index 7c291cd7..73680ba9 100644 --- a/apps/dashboard/src/lib/features/ui/icons/error-icon.svelte +++ b/apps/dashboard/src/lib/features/ui/icons/error-icon.svelte @@ -183,4 +183,3 @@ fill="#5C6065" /> - diff --git a/apps/dashboard/src/lib/features/ui/icons/google-icon-colored.svelte b/apps/dashboard/src/lib/features/ui/icons/google-icon-colored.svelte index 435ecb46..99f18468 100644 --- a/apps/dashboard/src/lib/features/ui/icons/google-icon-colored.svelte +++ b/apps/dashboard/src/lib/features/ui/icons/google-icon-colored.svelte @@ -24,4 +24,3 @@ fill="#EB4132" /> - diff --git a/apps/dashboard/src/lib/features/ui/icons/user-profile-icon.svelte b/apps/dashboard/src/lib/features/ui/icons/user-profile-icon.svelte index dacf1ee9..fd558921 100644 --- a/apps/dashboard/src/lib/features/ui/icons/user-profile-icon.svelte +++ b/apps/dashboard/src/lib/features/ui/icons/user-profile-icon.svelte @@ -15,4 +15,3 @@ - diff --git a/apps/dashboard/src/lib/features/ui/navigation/breadcrumb-core.ts b/apps/dashboard/src/lib/features/ui/navigation/breadcrumb-core.ts index 1869f05c..89398e42 100644 --- a/apps/dashboard/src/lib/features/ui/navigation/breadcrumb-core.ts +++ b/apps/dashboard/src/lib/features/ui/navigation/breadcrumb-core.ts @@ -46,12 +46,12 @@ export interface BreadcrumbConfig { * Can be a string or a function that extracts the prefix dynamically */ pathPrefix: string | ((pathname: string) => string); - + /** * Base path for generating hrefs (e.g., '/lms', '/org/myorg') */ basePath: string | ((pathname: string) => string); - + /** * Resolver for dynamic segment titles * Can resolve from page.data, stores, or API calls @@ -62,12 +62,12 @@ export interface BreadcrumbConfig { fullPathname: string, context?: unknown ) => string | Promise | null | undefined; - + /** * Optional: Cache breadcrumbs for better performance */ enableCache?: boolean; - + /** * Optional: Maximum breadcrumb depth (prevents infinite recursion) */ @@ -107,7 +107,7 @@ function getCachedOrGenerate( } clearExpiredCache(); - + const cached = breadcrumbCache.get(cacheKey); if (cached) { return cached; @@ -122,31 +122,25 @@ function getCachedOrGenerate( /** * Extract path prefix from pathname */ -function extractPathPrefix( - pathname: string, - prefixConfig: string | ((pathname: string) => string) -): string { +function extractPathPrefix(pathname: string, prefixConfig: string | ((pathname: string) => string)): string { if (typeof prefixConfig === 'function') { return prefixConfig(pathname); } - + // Handle dynamic prefixes like '/org/[slug]' if (prefixConfig.includes('[')) { const pattern = prefixConfig.replace(/\[.*?\]/g, '[^/]+'); const match = pathname.match(new RegExp(`^${pattern}`)); return match ? match[0] : ''; } - + return pathname.startsWith(prefixConfig) ? prefixConfig : ''; } /** * Get base path for href generation */ -function getBasePath( - pathname: string, - basePathConfig: string | ((pathname: string) => string) -): string { +function getBasePath(pathname: string, basePathConfig: string | ((pathname: string) => string)): string { if (typeof basePathConfig === 'function') { return basePathConfig(pathname); } @@ -228,19 +222,16 @@ async function resolveDynamicTitle( /** * Extract parameters from pathname (e.g., { slug: 'my-post' }) */ -function extractParamsFromPath( - pathname: string, - parentPath: string -): Record { +function extractParamsFromPath(pathname: string, parentPath: string): Record { const params: Record = {}; const relativePath = pathname.replace(parentPath, '').replace(/^\//, ''); const segments = relativePath.split('/').filter(Boolean); - + // Simple extraction - can be enhanced for more complex patterns segments.forEach((segment, index) => { params[`param${index}`] = segment; }); - + return params; } @@ -272,7 +263,7 @@ async function generateNestedBreadcrumbs( // Check for static nested route if (navItem.nestedRoutes) { const nestedRoute = navItem.nestedRoutes.find((route) => route.path === currentSegment); - + if (nestedRoute) { breadcrumbs.push({ label: nestedRoute.titleKey, @@ -300,16 +291,11 @@ async function generateNestedBreadcrumbs( } // Handle dynamic segment - if (navItem.supportsDynamicSegment || - navItem.nestedRoutes?.some((route) => route.supportsDynamicSegment && route.path !== currentSegment)) { - const dynamicTitle = await resolveDynamicTitle( - currentSegment, - currentPath, - fullPathname, - navItem, - config, - context - ); + if ( + navItem.supportsDynamicSegment || + navItem.nestedRoutes?.some((route) => route.supportsDynamicSegment && route.path !== currentSegment) + ) { + const dynamicTitle = await resolveDynamicTitle(currentSegment, currentPath, fullPathname, navItem, config, context); if (dynamicTitle) { breadcrumbs.push({ @@ -323,7 +309,7 @@ async function generateNestedBreadcrumbs( const supportingNestedRoute = navItem.nestedRoutes?.find( (route) => route.supportsDynamicSegment && route.nestedRoutes ); - + if (supportingNestedRoute) { const nestedBreadcrumbs = await generateNestedBreadcrumbs( remainingSegments, @@ -356,7 +342,7 @@ export async function generateBreadcrumbs( context?: unknown ): Promise { const cacheKey = `${pathname}${searchParams}`; - + return getCachedOrGenerate( cacheKey, () => generateBreadcrumbsSync(pathname, searchParams, navItems, config, context), @@ -381,7 +367,7 @@ export function generateBreadcrumbsSync( // Extract path prefix and base path const pathPrefix = extractPathPrefix(pathname, config.pathPrefix); const basePath = getBasePath(pathname, config.basePath); - + // Get relative path after removing prefix const relativePath = pathname.replace(pathPrefix, '').replace(/^\//, '') || ''; const pathSegments = relativePath.split('/').filter(Boolean); @@ -400,10 +386,8 @@ export function generateBreadcrumbsSync( } // Add parent breadcrumb - const parentHref = matchedNavItem.useHashUrl - ? `${basePath}${matchedNavItem.path}` - : matchedNavItem.url; - + const parentHref = matchedNavItem.useHashUrl ? `${basePath}${matchedNavItem.path}` : matchedNavItem.url; + breadcrumbs.push({ label: matchedNavItem.breadcrumbLabel || matchedNavItem.title, href: parentHref @@ -411,10 +395,8 @@ export function generateBreadcrumbsSync( // Handle query params (settings tabs, etc.) if (matchedNavItem.items && searchParams) { - const subItem = matchedNavItem.items.find((sub) => - isActive(pathWithQuery, sub.url, undefined, true) - ); - + const subItem = matchedNavItem.items.find((sub) => isActive(pathWithQuery, sub.url, undefined, true)); + if (subItem) { breadcrumbs.push({ label: subItem.breadcrumbLabel || subItem.title, @@ -428,7 +410,7 @@ export function generateBreadcrumbsSync( if (pathSegments.length > 1) { const remainingSegments = pathSegments.slice(1); const parentPath = `${basePath}${matchedNavItem.path}`; - + // Note: For async resolvers, you'd need to use generateBreadcrumbs instead const nestedBreadcrumbs = generateNestedBreadcrumbsSync( remainingSegments, @@ -440,7 +422,7 @@ export function generateBreadcrumbsSync( config, context ); - + breadcrumbs.push(...nestedBreadcrumbs); } @@ -475,7 +457,7 @@ function generateNestedBreadcrumbsSync( // Check for static nested route if (navItem.nestedRoutes) { const nestedRoute = navItem.nestedRoutes.find((route) => route.path === currentSegment); - + if (nestedRoute) { breadcrumbs.push({ label: nestedRoute.titleKey, @@ -506,14 +488,9 @@ function generateNestedBreadcrumbsSync( if (navItem.supportsDynamicSegment) { // Try to resolve title synchronously (from context if available) let dynamicTitle: string | null = null; - + if (config.dynamicTitleResolver) { - const result = config.dynamicTitleResolver( - currentSegment, - currentPath, - fullPathname, - context - ); + const result = config.dynamicTitleResolver(currentSegment, currentPath, fullPathname, context); // Only use if it's not a promise if (typeof result === 'string') { dynamicTitle = result; @@ -531,7 +508,7 @@ function generateNestedBreadcrumbsSync( const supportingNestedRoute = navItem.nestedRoutes?.find( (route) => route.supportsDynamicSegment && route.nestedRoutes ); - + if (supportingNestedRoute?.nestedRoutes) { const nestedBreadcrumbs = generateNestedBreadcrumbsSync( remainingSegments, @@ -560,4 +537,3 @@ export function clearBreadcrumbCache() { breadcrumbCache.clear(); cacheTimestamps.clear(); } - diff --git a/apps/dashboard/src/lib/features/ui/navigation/lms-breadcrumb.ts b/apps/dashboard/src/lib/features/ui/navigation/lms-breadcrumb.ts index 3e2c8f92..21fb0d4b 100644 --- a/apps/dashboard/src/lib/features/ui/navigation/lms-breadcrumb.ts +++ b/apps/dashboard/src/lib/features/ui/navigation/lms-breadcrumb.ts @@ -105,4 +105,3 @@ export function generateLmsBreadcrumbs( return breadcrumbs; } - diff --git a/apps/dashboard/src/lib/features/ui/navigation/lms-breadcrumbs.svelte b/apps/dashboard/src/lib/features/ui/navigation/lms-breadcrumbs.svelte index 4b5b938e..f7ab30bf 100644 --- a/apps/dashboard/src/lib/features/ui/navigation/lms-breadcrumbs.svelte +++ b/apps/dashboard/src/lib/features/ui/navigation/lms-breadcrumbs.svelte @@ -15,7 +15,7 @@ // Get breadcrumb title from store for LMS community posts const breadcrumbTitle = $derived($currentCommunityQuestion.title); - + const breadcrumbs = $derived( generateLmsBreadcrumbs( page.url.pathname, @@ -98,4 +98,3 @@ {/if} - diff --git a/apps/dashboard/src/lib/features/ui/navigation/lms-header.svelte b/apps/dashboard/src/lib/features/ui/navigation/lms-header.svelte index bceeacc6..8b02d235 100644 --- a/apps/dashboard/src/lib/features/ui/navigation/lms-header.svelte +++ b/apps/dashboard/src/lib/features/ui/navigation/lms-header.svelte @@ -60,4 +60,3 @@ {/if} - diff --git a/apps/dashboard/src/lib/features/ui/senja-embed.svelte b/apps/dashboard/src/lib/features/ui/senja-embed.svelte index 7510c93e..2bca2727 100644 --- a/apps/dashboard/src/lib/features/ui/senja-embed.svelte +++ b/apps/dashboard/src/lib/features/ui/senja-embed.svelte @@ -14,4 +14,3 @@
- diff --git a/apps/dashboard/src/lib/features/ui/sidebar/lms-sidebar/nav-main.svelte b/apps/dashboard/src/lib/features/ui/sidebar/lms-sidebar/nav-main.svelte index 30b2a0dd..5f37236e 100644 --- a/apps/dashboard/src/lib/features/ui/sidebar/lms-sidebar/nav-main.svelte +++ b/apps/dashboard/src/lib/features/ui/sidebar/lms-sidebar/nav-main.svelte @@ -75,4 +75,3 @@ {/each} - diff --git a/apps/dashboard/src/lib/features/ui/upload-widget/upload-widget.svelte b/apps/dashboard/src/lib/features/ui/upload-widget/upload-widget.svelte index 24c2ff99..94262bfb 100644 --- a/apps/dashboard/src/lib/features/ui/upload-widget/upload-widget.svelte +++ b/apps/dashboard/src/lib/features/ui/upload-widget/upload-widget.svelte @@ -108,78 +108,84 @@ {$t('course.navItem.landing_page.upload_widget.title')}
- - - {#each tabs as tab} - - {tab.label} - - {/each} - - - -
- - -

- {$t('course.navItem.landing_page.upload_widget.width')} -

-

- {$t('course.navItem.landing_page.upload_widget.size')} -

-
-
- - -
-
- - -
- {#if unsplashImages && unsplashImages.length > 0} -
- {#each unsplashImages as unsplashImages (unsplashImages.id)} -
-
- -
- {#if unsplashImages.user.name} -

- By {unsplashImages.user.name} -

- {/if} -
- {/each} -
- {:else} -

- {$t('course.navItem.landing_page.upload_widget.no_images')} + + + {#each tabs as tab} + + {tab.label} + + {/each} + + + +

+ + +

+ {$t('course.navItem.landing_page.upload_widget.width')}

- {/if} -
- - -
+

+ {$t('course.navItem.landing_page.upload_widget.size')} +

+
+ + + +
+
+ + +
+ {#if unsplashImages && unsplashImages.length > 0} +
+ {#each unsplashImages as unsplashImages (unsplashImages.id)} +
+
+ +
+ {#if unsplashImages.user.name} +

+ By {unsplashImages.user.name} +

+ {/if} +
+ {/each} +
+ {:else} +

+ {$t('course.navItem.landing_page.upload_widget.no_images')} +

+ {/if} +
+
+ + diff --git a/apps/dashboard/src/lib/features/ui/vote.svelte b/apps/dashboard/src/lib/features/ui/vote.svelte index 835bd345..d6a6fb8d 100644 --- a/apps/dashboard/src/lib/features/ui/vote.svelte +++ b/apps/dashboard/src/lib/features/ui/vote.svelte @@ -16,4 +16,3 @@ - diff --git a/apps/dashboard/src/lib/utils/functions/fileValidation.ts b/apps/dashboard/src/lib/utils/functions/fileValidation.ts index 5f0b2fcd..658c5750 100644 --- a/apps/dashboard/src/lib/utils/functions/fileValidation.ts +++ b/apps/dashboard/src/lib/utils/functions/fileValidation.ts @@ -2,13 +2,7 @@ * Server-side file type validation utilities for preventing malicious uploads * Specifically designed to prevent SVG XSS attacks and other security issues */ -export const ALLOWED_IMAGE_TYPES = [ - 'image/jpeg', - 'image/jpg', - 'image/png', - 'image/gif', - 'image/webp' -] as const; +export const ALLOWED_IMAGE_TYPES = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'] as const; export const ALLOWED_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp'] as const; diff --git a/apps/dashboard/src/lib/utils/functions/permissions.ts b/apps/dashboard/src/lib/utils/functions/permissions.ts index dfd6beec..0854dc1e 100644 --- a/apps/dashboard/src/lib/utils/functions/permissions.ts +++ b/apps/dashboard/src/lib/utils/functions/permissions.ts @@ -24,11 +24,7 @@ export async function checkUserCoursePermissions( .single(); // Check if user is org admin - const { data: orgData } = await supabase - .from('group') - .select('organization_id') - .eq('id', courseGroupId) - .single(); + const { data: orgData } = await supabase.from('group').select('organization_id').eq('id', courseGroupId).single(); let isOrgAdmin = false; if (orgData?.organization_id) { diff --git a/apps/dashboard/src/lib/utils/translations/da.json b/apps/dashboard/src/lib/utils/translations/da.json index e397a76f..a0d01866 100644 --- a/apps/dashboard/src/lib/utils/translations/da.json +++ b/apps/dashboard/src/lib/utils/translations/da.json @@ -1624,4 +1624,4 @@ } } } -} \ No newline at end of file +} diff --git a/apps/dashboard/src/lib/utils/translations/de.json b/apps/dashboard/src/lib/utils/translations/de.json index d81ef81a..d8594d85 100644 --- a/apps/dashboard/src/lib/utils/translations/de.json +++ b/apps/dashboard/src/lib/utils/translations/de.json @@ -1655,4 +1655,4 @@ } } } -} \ No newline at end of file +} diff --git a/apps/dashboard/src/lib/utils/translations/es.json b/apps/dashboard/src/lib/utils/translations/es.json index a40813e5..94966244 100644 --- a/apps/dashboard/src/lib/utils/translations/es.json +++ b/apps/dashboard/src/lib/utils/translations/es.json @@ -1655,4 +1655,4 @@ } } } -} \ No newline at end of file +} diff --git a/apps/dashboard/src/lib/utils/translations/fr.json b/apps/dashboard/src/lib/utils/translations/fr.json index 8453a5a6..066ca288 100644 --- a/apps/dashboard/src/lib/utils/translations/fr.json +++ b/apps/dashboard/src/lib/utils/translations/fr.json @@ -1655,4 +1655,4 @@ } } } -} \ No newline at end of file +} diff --git a/apps/dashboard/src/lib/utils/translations/hi.json b/apps/dashboard/src/lib/utils/translations/hi.json index 310411dd..eebe756d 100644 --- a/apps/dashboard/src/lib/utils/translations/hi.json +++ b/apps/dashboard/src/lib/utils/translations/hi.json @@ -1655,4 +1655,4 @@ } } } -} \ No newline at end of file +} diff --git a/apps/dashboard/src/lib/utils/translations/pl.json b/apps/dashboard/src/lib/utils/translations/pl.json index 3e9e297b..6ce55ce3 100644 --- a/apps/dashboard/src/lib/utils/translations/pl.json +++ b/apps/dashboard/src/lib/utils/translations/pl.json @@ -1625,4 +1625,4 @@ } } } -} \ No newline at end of file +} diff --git a/apps/dashboard/src/lib/utils/translations/pt.json b/apps/dashboard/src/lib/utils/translations/pt.json index 8b88d392..2c65e624 100644 --- a/apps/dashboard/src/lib/utils/translations/pt.json +++ b/apps/dashboard/src/lib/utils/translations/pt.json @@ -1655,4 +1655,4 @@ } } } -} \ No newline at end of file +} diff --git a/apps/dashboard/src/lib/utils/translations/ru.json b/apps/dashboard/src/lib/utils/translations/ru.json index 5830bca8..4d517401 100644 --- a/apps/dashboard/src/lib/utils/translations/ru.json +++ b/apps/dashboard/src/lib/utils/translations/ru.json @@ -1655,4 +1655,4 @@ } } } -} \ No newline at end of file +} diff --git a/apps/dashboard/src/lib/utils/translations/vi.json b/apps/dashboard/src/lib/utils/translations/vi.json index c6da97bd..b2a12152 100644 --- a/apps/dashboard/src/lib/utils/translations/vi.json +++ b/apps/dashboard/src/lib/utils/translations/vi.json @@ -1655,4 +1655,4 @@ } } } -} \ No newline at end of file +} diff --git a/apps/dashboard/src/lib/utils/validation/USAGE.md b/apps/dashboard/src/lib/utils/validation/USAGE.md index 77a4214a..b8346611 100644 --- a/apps/dashboard/src/lib/utils/validation/USAGE.md +++ b/apps/dashboard/src/lib/utils/validation/USAGE.md @@ -3,6 +3,7 @@ ## Overview We use **convention-based translation keys** for Zod validation errors. This approach: + - ✅ No metadata needed - just follow the naming pattern - ✅ Clean shared schemas without hardcoded messages - ✅ Automatic fallback to generic translations @@ -18,14 +19,14 @@ validations.{fieldName}.{errorCode} ### Common Error Codes -| Error Code | When It Occurs | Example Field | -|------------|----------------|---------------| -| `too_small` | String too short, number too small, array too few items | username.too_small | -| `too_big` | String too long, number too large, array too many items | bio.too_big | -| `invalid_email` | Invalid email format | email.invalid_email | -| `invalid_url` | Invalid URL format | website.invalid_url | -| `invalid_type` | Wrong type (e.g., undefined when string expected) | username.invalid_type | -| `invalid_enum_value` | Value not in allowed options | role.invalid_enum_value | +| Error Code | When It Occurs | Example Field | +| -------------------- | ------------------------------------------------------- | ----------------------- | +| `too_small` | String too short, number too small, array too few items | username.too_small | +| `too_big` | String too long, number too large, array too many items | bio.too_big | +| `invalid_email` | Invalid email format | email.invalid_email | +| `invalid_url` | Invalid URL format | website.invalid_url | +| `invalid_type` | Wrong type (e.g., undefined when string expected) | username.invalid_type | +| `invalid_enum_value` | Value not in allowed options | role.invalid_enum_value | ## Quick Start @@ -40,12 +41,12 @@ let errors: Record = {}; function handleSubmit() { const result = ZEmailData.safeParse(formData); - + if (!result.success) { errors = mapZodErrorsToTranslations(result.error); return; } - + // Use validated data sendEmail(result.data); } @@ -68,7 +69,7 @@ function handleSubmit() { "too_small": "Content must be at least 1 character", "too_big": "Content is too long" }, - + "generic": { "required": "This field is required", "invalid_email": "Invalid email address", @@ -101,24 +102,24 @@ export const ZUserProfile = z.object({ @@ -55,7 +52,7 @@ -{#if type === 'legacy_method_blocked'} -
-
- -
-

- {$t('email_verification.security_notice.title')} -

-

- {$t('email_verification.security_notice.message')} -

+ {#if type === 'legacy_method_blocked'} +
+
+ +
+

+ {$t('email_verification.security_notice.title')} +

+

+ {$t('email_verification.security_notice.message')} +

+
+
+ {/if} +
+ +
-
-{/if} -
- - -
diff --git a/apps/dashboard/src/routes/+layout.svelte b/apps/dashboard/src/routes/+layout.svelte index bc129534..ddb6b5c4 100644 --- a/apps/dashboard/src/routes/+layout.svelte +++ b/apps/dashboard/src/routes/+layout.svelte @@ -77,8 +77,7 @@ } console.log('path', path); console.log('isPublicRoute', isPublicRoute(path)); - console.log('data', $session.data) - + console.log('data', $session.data); // No need to require login for public routes if (isPublicRoute(path) && path !== '/') return; diff --git a/apps/dashboard/src/routes/api/admin/cleanup-tokens/+server.ts b/apps/dashboard/src/routes/api/admin/cleanup-tokens/+server.ts index 02bbe0ac..8a80e394 100644 --- a/apps/dashboard/src/routes/api/admin/cleanup-tokens/+server.ts +++ b/apps/dashboard/src/routes/api/admin/cleanup-tokens/+server.ts @@ -19,9 +19,7 @@ export async function POST({ request }) { try { // Clean up expired tokens - const { data: cleanupResult, error } = await supabase.rpc( - 'cleanup_expired_verification_tokens' - ); + const { data: cleanupResult, error } = await supabase.rpc('cleanup_expired_verification_tokens'); if (error) { console.error('Token cleanup failed:', error); diff --git a/apps/dashboard/src/routes/api/admin/security-monitor/+server.ts b/apps/dashboard/src/routes/api/admin/security-monitor/+server.ts index ddb673c2..5fb1b549 100644 --- a/apps/dashboard/src/routes/api/admin/security-monitor/+server.ts +++ b/apps/dashboard/src/routes/api/admin/security-monitor/+server.ts @@ -43,14 +43,11 @@ export async function GET({ url, request }) { const recent = tokenStats.filter((token) => new Date(token.created_at) > last24Hours); const used = tokenStats.filter((token) => token.used_at); - const expired = tokenStats.filter( - (token) => new Date(token.expires_at) < now && !token.used_at - ); + const expired = tokenStats.filter((token) => new Date(token.expires_at) < now && !token.used_at); // Count unique IPs for anomaly detection const uniqueCreationIPs = new Set(tokenStats.map((t) => t.created_by_ip)).size; - const uniqueUsageIPs = new Set(tokenStats.filter((t) => t.used_by_ip).map((t) => t.used_by_ip)) - .size; + const uniqueUsageIPs = new Set(tokenStats.filter((t) => t.used_by_ip).map((t) => t.used_by_ip)).size; return json({ success: true, @@ -61,8 +58,7 @@ export async function GET({ url, request }) { expired_unused: expired.length, unique_creation_ips: uniqueCreationIPs, unique_usage_ips: uniqueUsageIPs, - success_rate: - tokenStats.length > 0 ? ((used.length / tokenStats.length) * 100).toFixed(1) : 0 + success_rate: tokenStats.length > 0 ? ((used.length / tokenStats.length) * 100).toFixed(1) : 0 }, recent_activity: recent.slice(0, 10).map((token) => ({ created_at: token.created_at, diff --git a/apps/dashboard/src/routes/api/courses/analytics/+server.ts b/apps/dashboard/src/routes/api/courses/analytics/+server.ts index 4c2cbcc2..0ec15e2c 100644 --- a/apps/dashboard/src/routes/api/courses/analytics/+server.ts +++ b/apps/dashboard/src/routes/api/courses/analytics/+server.ts @@ -108,9 +108,7 @@ async function getCourseAnalytics(supabase: any, courseId: string): Promise member.role_id === 3) - .map((member) => getStudentOverview(supabase, courseId, member)) + members.filter((member) => member.role_id === 3).map((member) => getStudentOverview(supabase, courseId, member)) ); analytics.students = studentAnalytics.filter((student): student is StudentOverview => student !== null); @@ -155,11 +153,7 @@ async function getLastLogin(supabase: any, userId: string): Promise { +async function getStudentOverview(supabase: any, courseId: string, member: any): Promise { try { // Use the same service function that the working analytics/user API uses const { data: courseProgressData, error: progressError } = await fetchProfileCourseProgress( diff --git a/apps/dashboard/src/routes/api/courses/data/+server.ts b/apps/dashboard/src/routes/api/courses/data/+server.ts index ba00514b..9f413e2c 100644 --- a/apps/dashboard/src/routes/api/courses/data/+server.ts +++ b/apps/dashboard/src/routes/api/courses/data/+server.ts @@ -40,12 +40,7 @@ async function getCourse(supabase: any, courseId: string) { return course; } -async function getGroupMembers( - supabase: any, - groupId: string, - isStudent: boolean, - userMembership: any -) { +async function getGroupMembers(supabase: any, groupId: string, isStudent: boolean, userMembership: any) { if (isStudent) { return { id: groupId, @@ -53,11 +48,7 @@ async function getGroupMembers( }; } - const { data: group, error } = await supabase - .from('group') - .select(GROUP_MEMBERS_SELECT) - .eq('id', groupId) - .single(); + const { data: group, error } = await supabase.from('group').select(GROUP_MEMBERS_SELECT).eq('id', groupId).single(); if (error) { throw new Error('Error fetching group members'); diff --git a/apps/dashboard/src/routes/api/courses/exercises/+server.ts b/apps/dashboard/src/routes/api/courses/exercises/+server.ts index bce9f3a1..3b58fd1f 100644 --- a/apps/dashboard/src/routes/api/courses/exercises/+server.ts +++ b/apps/dashboard/src/routes/api/courses/exercises/+server.ts @@ -19,11 +19,7 @@ export const POST: RequestHandler = async ({ request }) => { const supabase = getServerSupabase(); // Get course group_id for permission check - const { data: course } = await supabase - .from('course') - .select('group_id') - .eq('id', courseId) - .single(); + const { data: course } = await supabase.from('course').select('group_id').eq('id', courseId).single(); if (!course) { return json({ success: false, message: 'Course not found' }, { status: 404 }); @@ -41,9 +37,7 @@ export const POST: RequestHandler = async ({ request }) => { ); } - const { data: exercises, error } = await supabase - .rpc('get_exercises') - .eq('course_id', courseId); + const { data: exercises, error } = await supabase.rpc('get_exercises').eq('course_id', courseId); if (error) { throw new Error('Error fetching exercises'); diff --git a/apps/dashboard/src/routes/api/courses/marks/+server.ts b/apps/dashboard/src/routes/api/courses/marks/+server.ts index 56f7ec5c..4ddd993d 100644 --- a/apps/dashboard/src/routes/api/courses/marks/+server.ts +++ b/apps/dashboard/src/routes/api/courses/marks/+server.ts @@ -19,11 +19,7 @@ export const POST: RequestHandler = async ({ request }) => { const supabase = getServerSupabase(); // Get course group_id for permission check - const { data: course } = await supabase - .from('course') - .select('group_id') - .eq('id', courseId) - .single(); + const { data: course } = await supabase.from('course').select('group_id').eq('id', courseId).single(); if (!course) { return json({ success: false, message: 'Course not found' }, { status: 404 }); diff --git a/apps/dashboard/src/routes/api/polar/webhook/+server.ts b/apps/dashboard/src/routes/api/polar/webhook/+server.ts index c50c6b92..1b019e54 100644 --- a/apps/dashboard/src/routes/api/polar/webhook/+server.ts +++ b/apps/dashboard/src/routes/api/polar/webhook/+server.ts @@ -37,8 +37,8 @@ async function onPayload(payload: PolarWebhookPayload) { if (isSubscriptionActive) { try { const planData = { - orgId, - triggeredBy: parseInt(triggeredBy), + orgId, + triggeredBy: parseInt(triggeredBy), planName: PLAN.EARLY_ADOPTER as 'EARLY_ADOPTER' | 'ENTERPRISE' | 'BASIC', subscriptionId, payload: payload.data as unknown as Record @@ -56,9 +56,9 @@ async function onPayload(payload: PolarWebhookPayload) { // Handle Cancel Subscription try { const result = await OrgPlanApiServer.cancelOrgPlan({ - subscriptionId, + subscriptionId, payload: payload.data as unknown as Record - }); + }); console.log('Subscription canceled', result); } catch (error) { console.error('Error canceling org plan', error); @@ -67,9 +67,9 @@ async function onPayload(payload: PolarWebhookPayload) { // Handle Update Subscription try { const result = await OrgPlanApiServer.updateOrgPlan({ - subscriptionId, + subscriptionId, payload: payload.data as unknown as Record - }); + }); console.log('Subscription updated', result); } catch (error) { console.error('Error updating org plan', error); diff --git a/apps/dashboard/src/routes/courses/[id]/+layout.svelte b/apps/dashboard/src/routes/courses/[id]/+layout.svelte index d53d3be8..3bc60e00 100644 --- a/apps/dashboard/src/routes/courses/[id]/+layout.svelte +++ b/apps/dashboard/src/routes/courses/[id]/+layout.svelte @@ -15,12 +15,12 @@ - + - + {@render children?.()} diff --git a/apps/dashboard/src/routes/invite/s/[hash]/+page.svelte b/apps/dashboard/src/routes/invite/s/[hash]/+page.svelte index 2d236fbb..da099d4c 100644 --- a/apps/dashboard/src/routes/invite/s/[hash]/+page.svelte +++ b/apps/dashboard/src/routes/invite/s/[hash]/+page.svelte @@ -130,12 +130,6 @@
- +
diff --git a/apps/dashboard/src/routes/lms/settings/+layout.svelte b/apps/dashboard/src/routes/lms/settings/+layout.svelte index 97e5abde..92f89795 100644 --- a/apps/dashboard/src/routes/lms/settings/+layout.svelte +++ b/apps/dashboard/src/routes/lms/settings/+layout.svelte @@ -10,4 +10,3 @@ {@render children?.()} - diff --git a/apps/dashboard/src/routes/lms/settings/integrations/+page.svelte b/apps/dashboard/src/routes/lms/settings/integrations/+page.svelte index 32a767a2..ea45b617 100644 --- a/apps/dashboard/src/routes/lms/settings/integrations/+page.svelte +++ b/apps/dashboard/src/routes/lms/settings/integrations/+page.svelte @@ -18,4 +18,3 @@ {/snippet} - diff --git a/apps/website/.prettierrc b/apps/website/.prettierrc deleted file mode 100644 index 73195ab7..00000000 --- a/apps/website/.prettierrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "useTabs": false, - "singleQuote": true, - "trailingComma": "none", - "printWidth": 100, - "overrides": [ - { - "files": ["*.svelte"], - "options": { - "svelteBracketNewLine": true - } - } - ] -} diff --git a/apps/website/FOLDER_STRUCTURE.md b/apps/website/FOLDER_STRUCTURE.md index 68ecdff5..8392c2fe 100644 --- a/apps/website/FOLDER_STRUCTURE.md +++ b/apps/website/FOLDER_STRUCTURE.md @@ -121,11 +121,8 @@ src/lib/ **Import Pattern**: ```svelte -// ✅ CORRECT - Use named imports from the index -import { Footer, Navigation, Hero } from '$lib/components'; - -// ❌ INCORRECT - Direct file imports are not allowed -// import Footer from '$lib/components/footer.svelte'; +// ✅ CORRECT - Use named imports from the index import {(Footer, Navigation, Hero)} from '$lib/components'; // ❌ INCORRECT +- Direct file imports are not allowed // import Footer from '$lib/components/footer.svelte'; ``` **Note**: All components must be imported using named imports from `$lib/components`. Direct file imports are not allowed. @@ -150,8 +147,7 @@ import { Footer, Navigation, Hero } from '$lib/components'; **Import Pattern**: ```svelte -import FeedbackForm from '$lib/features/contact/feedback-form.svelte'; -import { htmlBody, openModal } from '$lib/features/tools/progress/store'; +import FeedbackForm from '$lib/features/contact/feedback-form.svelte'; import {(htmlBody, openModal)} from '$lib/features/tools/progress/store'; ``` ### `/utils` - Utilities @@ -338,7 +334,7 @@ export { default as Navigation } from './navigation.svelte'; This allows for clean, consistent imports: ```svelte -import { Footer, Navigation, Hero } from '$lib/components'; +import {(Footer, Navigation, Hero)} from '$lib/components'; ``` **Rule**: Direct file imports (e.g., `import Footer from '$lib/components/footer.svelte'`) are **not allowed**. Always use named imports from `$lib/components`. diff --git a/apps/website/package.json b/apps/website/package.json index 6d93781a..e6f7586e 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -8,6 +8,7 @@ "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch", + "format": "prettier --write .", "prepare": "svelte-kit sync" }, "devDependencies": { diff --git a/apps/website/src/lib/components/blog-list-item.svelte b/apps/website/src/lib/components/blog-list-item.svelte index bfe8e799..7f5a477b 100644 --- a/apps/website/src/lib/components/blog-list-item.svelte +++ b/apps/website/src/lib/components/blog-list-item.svelte @@ -13,7 +13,7 @@ {#if !isRecommended} -
+

{formatDate(post.date)}

@@ -29,24 +29,16 @@ {/if} - {post.title} -

+ {post.title} +

{@html post.title}

-

{post.description}

+

{post.description}

-
- avatar +
+ avatar

{post.author}

{post.role}

diff --git a/apps/website/src/lib/components/core-feature-section.svelte b/apps/website/src/lib/components/core-feature-section.svelte index 2c9413f0..f7a04f91 100644 --- a/apps/website/src/lib/components/core-feature-section.svelte +++ b/apps/website/src/lib/components/core-feature-section.svelte @@ -26,14 +26,14 @@ } = $props(); -
+
-
-
+
+
{#if taglineIcon === 'simplified'} {:else if taglineIcon === 'flexible'} @@ -46,16 +46,16 @@ {:else if taglineIcon === 'productivity'} {/if} -

{tagline}

+

{tagline}

-

{title}

-

+

{title}

+

{description}

{#if more}
-

+

{@render more?.()}

{/if} @@ -64,7 +64,7 @@