From b986ee53eef3eebcf5a9f0d653a19502c6d4a886 Mon Sep 17 00:00:00 2001 From: Ranjeet2063 Date: Sat, 3 Oct 2026 01:18:48 +0545 Subject: [PATCH 1/2] fix: eliminate duplicate courses API requests on courses page - Extract `useEnrollments` hook out of `useCourses` - Use `useEnrollments()` directly on the courses list page instead of `useCourses()` - Eliminates duplicate `getCourses` network calls since `useInfiniteCourses` already fetches the catalog list Closes #517 --- src/app/courses/page.tsx | 4 ++-- src/lib/hooks/use-courses.ts | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/app/courses/page.tsx b/src/app/courses/page.tsx index ffd025c..9895af6 100644 --- a/src/app/courses/page.tsx +++ b/src/app/courses/page.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useMemo, useRef, useState } from "react"; -import { useCourses, useInfiniteCourses } from "@/lib/hooks/use-courses"; +import { useEnrollments, useInfiniteCourses } from "@/lib/hooks/use-courses"; import { useDebounce } from "@/lib/hooks/use-debounce"; import { useCourseStore } from "@/store/course-store"; import { CourseCard } from "@/components/course/course-card"; @@ -29,7 +29,7 @@ const categories = [ const difficulties = ["All", "Beginner", "Intermediate", "Advanced"]; export default function CoursesPage() { - const { enrollments } = useCourses(); + const { enrollments } = useEnrollments(); const progress = useCourseStore((s) => s.progress); const [search, setSearch] = useState(""); const debouncedSearch = useDebounce(search, 300); diff --git a/src/lib/hooks/use-courses.ts b/src/lib/hooks/use-courses.ts index 91563f7..f08b839 100644 --- a/src/lib/hooks/use-courses.ts +++ b/src/lib/hooks/use-courses.ts @@ -121,6 +121,41 @@ function invalidateEnrollmentsCache(jwt?: string | null) { enrollmentsCache.clear(); } + +export function useEnrollments() { + const jwt = useAuthStore((s) => s.jwt); + const { enrollments, setEnrollments, enroll: addEnrollment } = useCourseStore(); + const abortRef = useRef(null); + + const fetchEnrollments = useCallback(async () => { + if (!jwt) return; + + const cached = getCachedEnrollments(jwt); + if (cached) { + setEnrollments(cached); + return; + } + + const controller = new AbortController(); + abortRef.current = controller; + + try { + const data = await loadEnrollments(jwt, controller.signal); + setEnrollments(data); + } catch (err) { + if (isAbortError(err)) return; + console.error("Failed to fetch enrollments:", err); + } + }, [jwt, setEnrollments]); + + useEffect(() => { + fetchEnrollments(); + return () => abortRef.current?.abort(); + }, [fetchEnrollments]); + + return { enrollments, fetchEnrollments }; +} + export function useCourses() { const jwt = useAuthStore((s) => s.jwt); const { enrollments, setEnrollments, enroll: addEnrollment } = useCourseStore(); From 7ac5c03fa47b5a31cc6892b55b79621f6fe83e48 Mon Sep 17 00:00:00 2001 From: Ranjeet2063 Date: Sat, 3 Oct 2026 01:56:05 +0545 Subject: [PATCH 2/2] style: auto-format for CI compliance --- PR_TESTING_AND_BUG_FIXES.md | 10 +- next.config.mjs | 2 +- package-lock.json | 115 +++++-------- src/app/(auth)/connect/page.tsx | 13 +- src/app/(auth)/onboarding/page.tsx | 29 +++- src/app/courses/[courseId]/layout.tsx | 4 +- .../[courseId]/modules/[moduleId]/page.tsx | 44 +++-- src/app/courses/[courseId]/page.tsx | 29 ++-- src/app/courses/[courseId]/quiz/page.tsx | 38 +++-- src/app/courses/not-found.tsx | 4 +- src/app/courses/page.tsx | 29 ++-- src/app/credentials/[credentialId]/layout.tsx | 6 +- .../[credentialId]/opengraph-image.tsx | 3 +- src/app/credentials/[credentialId]/page.tsx | 49 +++--- src/app/credentials/page.tsx | 21 ++- src/app/dashboard/page.tsx | 43 +++-- src/app/globals.css | 23 ++- src/app/page.tsx | 12 +- src/app/rewards/page.tsx | 2 +- src/app/settings/loading.tsx | 5 +- src/app/settings/page.tsx | 30 +++- src/app/verify/[credentialId]/layout.tsx | 4 +- .../verify/[credentialId]/opengraph-image.tsx | 2 +- src/app/verify/[credentialId]/page.tsx | 11 +- src/components/course/course-card.tsx | 7 +- .../course/course-progress-card.tsx | 4 +- src/components/course/module-list.tsx | 57 +++++-- src/components/course/progress-bar.test.tsx | 14 +- src/components/course/progress-bar.tsx | 18 +- src/components/course/quiz-interface.tsx | 36 ++-- src/components/course/results-screen.tsx | 44 +++-- .../credentials/credential-badge.tsx | 15 +- .../credentials/credential-card.test.tsx | 2 +- .../credentials/credential-card.tsx | 2 +- src/components/credentials/qr-code.tsx | 14 +- src/components/layout/footer.tsx | 4 +- src/components/layout/header.tsx | 51 ++++-- src/components/layout/mobile-nav.tsx | 15 +- src/components/layout/nav-links.ts | 7 +- src/components/layout/sidebar.tsx | 29 +++- src/components/rewards/claim-button.test.tsx | 12 +- .../rewards/reward-claim-dialog.tsx | 2 +- .../rewards/reward-history.test.tsx | 2 +- src/components/rewards/reward-history.tsx | 55 ++++-- src/components/rewards/token-balance.test.tsx | 2 +- src/components/rewards/token-balance.tsx | 6 +- src/components/shared/avatar-upload.tsx | 73 ++++---- src/components/shared/back-button.tsx | 8 +- src/components/shared/confirm-dialog.tsx | 17 +- src/components/shared/empty-state.tsx | 4 +- src/components/shared/error-boundary.test.tsx | 8 +- src/components/shared/error-boundary.tsx | 5 +- src/components/shared/leaderboard-table.tsx | 20 +-- src/components/shared/loading-skeleton.tsx | 6 +- src/components/shared/notification-bell.tsx | 20 ++- src/components/shared/share-button.tsx | 66 +++++--- src/components/shared/stats-card.tsx | 16 +- src/components/shared/toast.test.tsx | 14 +- src/components/shared/toast.tsx | 19 +-- src/components/theme/theme-provider.tsx | 7 +- src/components/ui/accordion.tsx | 2 +- src/components/ui/alert.tsx | 23 ++- src/components/ui/avatar.tsx | 16 +- src/components/ui/badge.test.tsx | 14 +- src/components/ui/badge.tsx | 22 ++- src/components/ui/breadcrumb.tsx | 11 +- src/components/ui/button.test.tsx | 4 +- src/components/ui/button.tsx | 4 +- src/components/ui/card.test.tsx | 2 +- src/components/ui/card.tsx | 18 +- src/components/ui/dialog.test.tsx | 42 +++-- src/components/ui/dialog.tsx | 23 ++- src/components/ui/dropdown-menu.tsx | 80 ++++----- src/components/ui/input.tsx | 7 +- src/components/ui/pagination.tsx | 8 +- src/components/ui/progress.tsx | 26 +-- src/components/ui/scroll-area.tsx | 7 +- src/components/ui/select.tsx | 11 +- src/components/ui/separator.tsx | 6 +- src/components/ui/skeleton.test.tsx | 4 +- src/components/ui/skeleton.tsx | 17 +- src/components/ui/switch.tsx | 11 +- src/components/ui/table.tsx | 20 +-- src/components/ui/tabs.tsx | 30 ++-- src/components/ui/tooltip.tsx | 2 +- src/components/wallet/balance-display.tsx | 57 +++++-- src/components/wallet/connect-button.tsx | 26 ++- .../wallet/wallet-provider.test.tsx | 2 +- src/components/wallet/wallet-provider.tsx | 8 +- src/lib/api/README.md | 109 ++++++------ src/lib/api/auth.ts | 40 +++-- src/lib/api/client.ts | 64 ++++--- src/lib/api/courses.test.ts | 10 +- src/lib/api/courses.ts | 64 +++---- src/lib/api/credentials.test.ts | 4 +- src/lib/api/credentials.ts | 16 +- src/lib/api/index.ts | 2 +- src/lib/api/notifications.ts | 10 +- src/lib/api/quizzes.ts | 16 +- src/lib/api/rewards.ts | 18 +- src/lib/hooks/use-async.test.tsx | 10 +- src/lib/hooks/use-async.ts | 4 +- src/lib/hooks/use-auth.ts | 34 ++-- src/lib/hooks/use-boolean.test.tsx | 32 ++-- src/lib/hooks/use-boolean.ts | 2 +- src/lib/hooks/use-click-outside.test.tsx | 41 ++--- src/lib/hooks/use-click-outside.ts | 2 +- src/lib/hooks/use-courses.ts | 61 +++++-- src/lib/hooks/use-credentials.test.tsx | 44 ++--- src/lib/hooks/use-credentials.ts | 18 +- src/lib/hooks/use-debounce.test.tsx | 4 +- .../hooks/use-intersection-observer.test.tsx | 11 +- src/lib/hooks/use-intersection-observer.ts | 11 +- src/lib/hooks/use-interval.test.tsx | 9 +- src/lib/hooks/use-interval.ts | 2 +- src/lib/hooks/use-local-storage.ts | 11 +- src/lib/hooks/use-media-query.test.tsx | 7 +- src/lib/hooks/use-mount.test.tsx | 25 ++- src/lib/hooks/use-mount.ts | 2 +- src/lib/hooks/use-notifications.test.tsx | 63 ++++--- src/lib/hooks/use-notifications.ts | 35 ++-- src/lib/hooks/use-previous.test.tsx | 22 +-- src/lib/hooks/use-previous.ts | 2 +- src/lib/hooks/use-require-auth.test.tsx | 3 +- src/lib/hooks/use-rewards.test.tsx | 54 +++--- src/lib/hooks/use-rewards.ts | 83 +++++----- src/lib/hooks/use-sessions.test.tsx | 14 +- src/lib/hooks/use-sessions.ts | 71 ++++---- src/lib/hooks/use-swipe.test.tsx | 64 +++---- src/lib/hooks/use-token-price.ts | 8 +- src/lib/hooks/use-token-refresh.test.tsx | 41 +++-- src/lib/hooks/use-token-refresh.ts | 4 +- src/lib/hooks/use-unmount.test.tsx | 29 ++-- src/lib/hooks/use-unmount.ts | 2 +- src/lib/hooks/use-wallet-watcher.ts | 2 +- src/lib/stellar/contracts.ts | 40 +++-- src/lib/stellar/transactions.ts | 30 +++- src/lib/stellar/wallet.ts | 86 ++++++---- src/lib/utils/__tests__/crypto.test.ts | 8 +- src/lib/utils/__tests__/format.test.ts | 8 +- src/lib/utils/__tests__/validation.test.ts | 61 +++---- src/lib/utils/crypto.ts | 20 ++- src/lib/utils/format.ts | 55 ++++-- src/lib/utils/jwt.test.ts | 33 ++-- src/lib/utils/jwt.ts | 18 +- src/lib/utils/og-image.tsx | 76 +++++---- src/lib/utils/validation.ts | 122 ++++++++++---- src/middleware.test.ts | 47 ++++-- src/store/__tests__/auth-store.test.ts | 132 ++++++++------- src/store/__tests__/course-store.test.ts | 156 +++++++++++------- src/store/__tests__/error-store.test.ts | 58 +++---- src/store/auth-store.ts | 19 +-- src/store/course-store.ts | 9 +- src/tests/auth/auth-provider.test.tsx | 36 +++- src/tests/course/course-card.test.tsx | 4 +- .../course/course-progress-card.test.tsx | 18 +- src/tests/course/progress-bar.test.tsx | 15 +- .../credentials/credential-badge.test.tsx | 6 +- .../credentials/credential-card.test.tsx | 5 +- src/tests/layout/header.test.tsx | 19 ++- src/tests/layout/mobile-nav.test.tsx | 8 +- src/tests/layout/sidebar.test.tsx | 8 +- src/tests/rewards/claim-button.test.tsx | 10 +- src/tests/rewards/reward-history.test.tsx | 6 +- src/tests/rewards/token-balance.test.tsx | 6 +- src/tests/setup.tsx | 9 +- src/tests/shared/error-boundary.test.tsx | 18 +- src/tests/shared/format.test.ts | 20 ++- src/tests/shared/leaderboard-table.test.tsx | 22 ++- src/tests/shared/notification-bell.test.tsx | 15 +- src/tests/shared/route-error.test.tsx | 8 +- src/tests/shared/stats-card.test.tsx | 23 ++- .../shared/stellar-contracts-env.test.ts | 6 +- .../shared/stellar-contracts-helpers.test.ts | 42 +++-- src/tests/shared/stellar-transactions.test.ts | 58 +++++-- src/tests/shared/stellar-wallet.test.ts | 65 ++++++-- src/tests/shared/toast.test.tsx | 4 +- src/tests/ui/avatar.test.tsx | 21 ++- src/tests/ui/button.test.tsx | 10 +- src/tests/ui/card.test.tsx | 14 +- src/tests/ui/select.test.tsx | 8 +- src/tests/ui/separator.test.tsx | 4 +- src/tests/wallet/balance-display.test.tsx | 2 +- src/tests/wallet/connect-button.test.tsx | 4 +- src/tests/wallet/wallet-provider.test.tsx | 11 +- src/types/api.ts | 2 +- src/types/course.ts | 8 +- src/types/notification.ts | 2 +- tailwind.config.ts | 15 +- 189 files changed, 2704 insertions(+), 1698 deletions(-) diff --git a/PR_TESTING_AND_BUG_FIXES.md b/PR_TESTING_AND_BUG_FIXES.md index 1f72508..2f913d7 100644 --- a/PR_TESTING_AND_BUG_FIXES.md +++ b/PR_TESTING_AND_BUG_FIXES.md @@ -10,7 +10,7 @@ This PR addresses multiple issues related to test coverage and bug fixes in the - connect, disconnect, setJwt, applyRefreshedTokens - isTokenExpired, hydration, persistence - Session cookie handling - + - **Created** `src/store/__tests__/course-store.test.ts` - Comprehensive tests for course-store including: - setCurrentCourse, setEnrollments, enroll (with deduplication) - updateProgress (percent calculation, module tracking) @@ -40,7 +40,8 @@ This PR addresses multiple issues related to test coverage and bug fixes in the **Problem**: The `connectWallet` callback included `walletError` in its dependency array, but reads it inside the catch block. Since `walletError` is state, the closure captures a stale value. -**Solution**: +**Solution**: + - Added `walletErrorRef` to track the latest `walletError` value - Changed catch block to check `walletErrorRef.current` instead of `walletError` - Removed `walletError` from the `connectWallet` dependency array @@ -52,6 +53,7 @@ This PR addresses multiple issues related to test coverage and bug fixes in the **Problem**: The `setSessionCookie` function sets the `chainlearn-session` cookie without the `Secure` flag, allowing JWT transmission over unencrypted HTTP. **Solution**: + - Added logic to detect HTTPS protocol using `window.location.protocol` - Conditionally adds `; Secure` flag when served over HTTPS - Maintains HTTP compatibility for local development @@ -59,12 +61,14 @@ This PR addresses multiple issues related to test coverage and bug fixes in the ## Testing All new tests follow existing patterns from the codebase (e.g., `use-debounce.test.tsx`) and cover: + - Successful operations - Error handling - Loading states - Abort/cleanup where applicable Run tests with: + ```bash npm test ``` @@ -72,6 +76,7 @@ npm test ## Type Checking Run type checking to verify no type errors: + ```bash npm run typecheck ``` @@ -103,6 +108,7 @@ git push -u origin feature/testing-and-bug-fixes ``` Then create a pull request using the GitHub UI or CLI with the title: + ``` Add unit tests for Zustand stores and hooks, fix walletError stale closure, add Secure flag to session cookie ``` diff --git a/next.config.mjs b/next.config.mjs index 754a019..53460af 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -82,7 +82,7 @@ const securityHeaders = [ const nextConfig = { reactStrictMode: true, images: { - formats: ['image/avif', 'image/webp'], + formats: ["image/avif", "image/webp"], deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840], imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], // Only trusted domains are allowed here to prevent SSRF via the image diff --git a/package-lock.json b/package-lock.json index e29b3e5..335f4b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1427,9 +1427,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1446,9 +1443,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1465,9 +1459,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1484,9 +1475,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2783,9 +2771,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2803,9 +2788,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2823,9 +2805,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2843,9 +2822,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2863,9 +2839,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2883,9 +2856,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7270,9 +7240,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7287,9 +7254,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7304,9 +7268,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7321,9 +7282,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7338,9 +7296,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7355,9 +7310,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7372,9 +7324,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7389,9 +7338,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7406,9 +7352,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -7423,9 +7366,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -7825,6 +7765,20 @@ "ws": "^7.5.1" } }, + "node_modules/@walletconnect/jsonrpc-ws-connection/node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "optional": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/@walletconnect/jsonrpc-ws-connection/node_modules/ws": { "version": "7.5.13", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", @@ -12417,6 +12371,20 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, + "node_modules/jayson/node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "optional": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/jayson/node_modules/ws": { "version": "7.5.13", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", @@ -12804,9 +12772,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -12828,9 +12793,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -12852,9 +12814,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -12876,9 +12835,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -17168,6 +17124,21 @@ "node": ">= 10" } }, + "node_modules/webpack-bundle-analyzer/node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/webpack-bundle-analyzer/node_modules/ws": { "version": "7.5.13", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.13.tgz", diff --git a/src/app/(auth)/connect/page.tsx b/src/app/(auth)/connect/page.tsx index e9dca42..f32c879 100644 --- a/src/app/(auth)/connect/page.tsx +++ b/src/app/(auth)/connect/page.tsx @@ -3,7 +3,13 @@ import { useRouter } from "next/navigation"; import { useAuth } from "@/lib/hooks/use-auth"; import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; +import { + Card, + CardContent, + CardHeader, + CardTitle, + CardDescription, +} from "@/components/ui/card"; import { Wallet, Shield, Loader2, AlertCircle } from "lucide-react"; import { isFreighterInstalled } from "@/lib/stellar/wallet"; import { useEffect, useRef, useState } from "react"; @@ -26,7 +32,7 @@ export default function ConnectPage() { } = useAuth(); const { addToast } = useToastContext(); const [freighterInstalled, setFreighterInstalled] = useState( - null + null, ); const connectingFromPage = useRef(false); @@ -80,7 +86,8 @@ export default function ConnectPage() { router.replace(profile?.displayName ? "/dashboard" : "/onboarding"); } catch (err) { connectingFromPage.current = false; - const errorMessage = err instanceof Error ? err.message : "Failed to fetch profile"; + const errorMessage = + err instanceof Error ? err.message : "Failed to fetch profile"; addToast(`Error: ${errorMessage}`, "error"); } }; diff --git a/src/app/(auth)/onboarding/page.tsx b/src/app/(auth)/onboarding/page.tsx index 76b0a4c..35f6d52 100644 --- a/src/app/(auth)/onboarding/page.tsx +++ b/src/app/(auth)/onboarding/page.tsx @@ -6,7 +6,13 @@ import { useAuthStore } from "@/store/auth-store"; import { updateProfile } from "@/lib/api/auth"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; +import { + Card, + CardContent, + CardHeader, + CardTitle, + CardDescription, +} from "@/components/ui/card"; import { User, Target, Zap, ArrowRight, Languages } from "lucide-react"; import { useToastContext } from "@/components/shared/toast"; @@ -49,15 +55,22 @@ export default function OnboardingPage() { const toggleGoal = (goal: string) => { setSelectedGoals((prev) => - prev.includes(goal) ? prev.filter((g) => g !== goal) : [...prev, goal] + prev.includes(goal) ? prev.filter((g) => g !== goal) : [...prev, goal], ); }; const handleNext = () => { if (step === 0) { const trimmed = displayName.trim(); - if (trimmed.length < 2 || trimmed.length > 50 || /[\r\n\t\0<>]/.test(trimmed)) { - addToast("Enter a valid display name between 2 and 50 characters.", "error"); + if ( + trimmed.length < 2 || + trimmed.length > 50 || + /[\r\n\t\0<>]/.test(trimmed) + ) { + addToast( + "Enter a valid display name between 2 and 50 characters.", + "error", + ); return; } } @@ -144,7 +157,9 @@ export default function OnboardingPage() { type="button" aria-pressed={selectedGoals.includes(goal)} onClick={() => toggleGoal(goal)} - disabled={!selectedGoals.includes(goal) && selectedGoals.length >= 3} + disabled={ + !selectedGoals.includes(goal) && selectedGoals.length >= 3 + } className={`rounded-full border px-4 py-2 text-sm transition-all ${ selectedGoals.includes(goal) ? "border-primary-500 bg-primary-50 text-primary-700" @@ -263,9 +278,7 @@ export default function OnboardingPage() { return (
- +
{steps.map((_, i) => ( diff --git a/src/app/courses/[courseId]/layout.tsx b/src/app/courses/[courseId]/layout.tsx index 5d13fd9..bd649a4 100644 --- a/src/app/courses/[courseId]/layout.tsx +++ b/src/app/courses/[courseId]/layout.tsx @@ -6,7 +6,9 @@ interface PageProps { params: { courseId: string }; } -export async function generateMetadata({ params }: PageProps): Promise { +export async function generateMetadata({ + params, +}: PageProps): Promise { const { courseId } = params; try { diff --git a/src/app/courses/[courseId]/modules/[moduleId]/page.tsx b/src/app/courses/[courseId]/modules/[moduleId]/page.tsx index 95983c6..fc92026 100644 --- a/src/app/courses/[courseId]/modules/[moduleId]/page.tsx +++ b/src/app/courses/[courseId]/modules/[moduleId]/page.tsx @@ -30,7 +30,7 @@ export default function ModulePage({ const { course } = useCourseDetail(courseId); const courseProgress = useCourseStore((s) => s.progress[courseId]); const enrollment = useCourseStore((s) => - s.enrollments.find((item) => item.courseId === courseId) + s.enrollments.find((item) => item.courseId === courseId), ); const { addToast } = useToastContext(); const [completing, setCompleting] = useState(false); @@ -41,8 +41,7 @@ export default function ModulePage({ ? [...course.modules].sort((a, b) => a.order - b.order) : []; const currentIndex = sortedModules.findIndex((m) => m.id === moduleId); - const prevModule = - currentIndex > 0 ? sortedModules[currentIndex - 1] : null; + const prevModule = currentIndex > 0 ? sortedModules[currentIndex - 1] : null; const nextModule = currentIndex >= 0 && currentIndex < sortedModules.length - 1 ? sortedModules[currentIndex + 1] @@ -94,7 +93,10 @@ export default function ModulePage({ {(courseProgress || enrollment) && (
-

+

{progressPercent}% complete

@@ -106,13 +108,19 @@ export default function ModulePage({

{module.title}

-

{module.description}

+

+ {module.description} +

{content.type === "video" ? (
{content.url ? ( -
) : error ? (
-

+

{error}

{/* On-chain Verification */} -
+
{verificationLoading ? ( @@ -223,11 +228,19 @@ export default function CredentialDetailPage({ )}
-

- {verificationLoading ? "Verifying On-Chain..." : verification?.isValid ? "On-Chain Verified" : "Verification Failed"} +

+ {verificationLoading + ? "Verifying On-Chain..." + : verification?.isValid + ? "On-Chain Verified" + : "Verification Failed"}

{credential.contractAddress && ( -

+

Contract: {truncateAddress(credential.contractAddress, 8)}

)} @@ -241,9 +254,7 @@ export default function CredentialDetailPage({

Skills Demonstrated

{credential.metadata.skills.map((skill) => ( - - {skill} - + {skill} ))}
diff --git a/src/app/credentials/page.tsx b/src/app/credentials/page.tsx index 35103d3..279e6c4 100644 --- a/src/app/credentials/page.tsx +++ b/src/app/credentials/page.tsx @@ -57,7 +57,9 @@ export default function CredentialsPage() { result = result.filter( (cred) => cred.courseTitle.toLowerCase().includes(query) || - cred.metadata.skills.some((skill) => skill.toLowerCase().includes(query)) + cred.metadata.skills.some((skill) => + skill.toLowerCase().includes(query), + ), ); } @@ -65,14 +67,18 @@ export default function CredentialsPage() { sorted.sort((a, b) => { switch (sort) { case "date_asc": - return new Date(a.issuedAt).getTime() - new Date(b.issuedAt).getTime(); + return ( + new Date(a.issuedAt).getTime() - new Date(b.issuedAt).getTime() + ); case "score_desc": return (b.metadata.score ?? 0) - (a.metadata.score ?? 0); case "score_asc": return (a.metadata.score ?? 0) - (b.metadata.score ?? 0); case "date_desc": default: - return new Date(b.issuedAt).getTime() - new Date(a.issuedAt).getTime(); + return ( + new Date(b.issuedAt).getTime() - new Date(a.issuedAt).getTime() + ); } }); @@ -146,7 +152,10 @@ export default function CredentialsPage() { - setSort(value as SortOption)} + > @@ -163,7 +172,9 @@ export default function CredentialsPage() { {filtered.length === 0 ? (
-

No credentials match your filters.

+

+ No credentials match your filters. +

diff --git a/src/app/rewards/page.tsx b/src/app/rewards/page.tsx index 4f1d780..b5a74cf 100644 --- a/src/app/rewards/page.tsx +++ b/src/app/rewards/page.tsx @@ -16,7 +16,7 @@ import { Gift, Coins, TrendingUp } from "lucide-react"; const RewardHistory = lazy(() => import("@/components/rewards/reward-history").then((m) => ({ default: m.RewardHistory, - })) + })), ); export default function RewardsPage() { diff --git a/src/app/settings/loading.tsx b/src/app/settings/loading.tsx index 0a68984..101a4d7 100644 --- a/src/app/settings/loading.tsx +++ b/src/app/settings/loading.tsx @@ -1,4 +1,7 @@ -import { ProfileSkeleton, FormSkeleton } from "@/components/shared/loading-skeleton"; +import { + ProfileSkeleton, + FormSkeleton, +} from "@/components/shared/loading-skeleton"; export default function Loading() { return ( diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx index 4cd030d..30ec586 100644 --- a/src/app/settings/page.tsx +++ b/src/app/settings/page.tsx @@ -10,7 +10,13 @@ import { getProfile, updateProfile } from "@/lib/api/auth"; import { AvatarUpload } from "@/components/shared/avatar-upload"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; -import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; +import { + Card, + CardContent, + CardHeader, + CardTitle, + CardDescription, +} from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Select, @@ -19,7 +25,15 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { Loader2, Save, Laptop, Smartphone, Globe, Trash2, ShieldCheck } from "lucide-react"; +import { + Loader2, + Save, + Laptop, + Smartphone, + Globe, + Trash2, + ShieldCheck, +} from "lucide-react"; const PACE_OPTIONS = [ { value: "slow", label: "Slow — take my time" }, @@ -44,7 +58,12 @@ export default function SettingsPage() { const { jwt, isAuthenticated, walletAddress } = useAuth(); const { addToast } = useToastContext(); const router = useRouter(); - const { sessions, loading: loadingSessions, revokingId, revoke } = useSessions(); + const { + sessions, + loading: loadingSessions, + revokingId, + revoke, + } = useSessions(); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); @@ -202,7 +221,10 @@ export default function SettingsPage() { - setPace(v as typeof pace)} + > diff --git a/src/app/verify/[credentialId]/layout.tsx b/src/app/verify/[credentialId]/layout.tsx index 3d7fb0f..aa4be4a 100644 --- a/src/app/verify/[credentialId]/layout.tsx +++ b/src/app/verify/[credentialId]/layout.tsx @@ -5,7 +5,9 @@ interface PageProps { params: { credentialId: string }; } -export async function generateMetadata({ params }: PageProps): Promise { +export async function generateMetadata({ + params, +}: PageProps): Promise { const { credentialId } = params; try { diff --git a/src/app/verify/[credentialId]/opengraph-image.tsx b/src/app/verify/[credentialId]/opengraph-image.tsx index f989503..944b878 100644 --- a/src/app/verify/[credentialId]/opengraph-image.tsx +++ b/src/app/verify/[credentialId]/opengraph-image.tsx @@ -19,7 +19,7 @@ export default async function Image({ params }: ImageProps) { try { const response = await fetch( `${API_URL}/credentials/${credentialId}/verify`, - { next: { revalidate: 3600 } } + { next: { revalidate: 3600 } }, ); if (response.ok) { const payload = (await response.json()) as { diff --git a/src/app/verify/[credentialId]/page.tsx b/src/app/verify/[credentialId]/page.tsx index 8b84b5d..a53af5a 100644 --- a/src/app/verify/[credentialId]/page.tsx +++ b/src/app/verify/[credentialId]/page.tsx @@ -157,7 +157,9 @@ export default function VerifyCredentialPage({ {/* Issuer Contact Info */}
-

Issuer Information

+

+ Issuer Information +

ChainLearn Official @@ -165,11 +167,14 @@ export default function VerifyCredentialPage({
Address: - {credential.metadata.issuerAddress ? truncateAddress(credential.metadata.issuerAddress, 8) : "N/A"} + {credential.metadata.issuerAddress + ? truncateAddress(credential.metadata.issuerAddress, 8) + : "N/A"}

- For verification inquiries, please contact support@chainlearn.org + For verification inquiries, please contact + support@chainlearn.org

diff --git a/src/components/course/course-card.tsx b/src/components/course/course-card.tsx index 391353e..cf45c4f 100644 --- a/src/components/course/course-card.tsx +++ b/src/components/course/course-card.tsx @@ -174,12 +174,15 @@ export const CourseCard = memo(function CourseCard({ {/* Progress bar with percentage label */} {enrolled && progress !== undefined && ( -
-