Skip to content

Fix/imports#23

Merged
Z4phxr merged 4 commits intomainfrom
fix/imports
Apr 25, 2026
Merged

Fix/imports#23
Z4phxr merged 4 commits intomainfrom
fix/imports

Conversation

@Z4phxr
Copy link
Copy Markdown
Owner

@Z4phxr Z4phxr commented Apr 25, 2026

This pull request introduces a comprehensive "archive/unarchive" feature for courses and flashcard decks in the learning platform. It adds backend API endpoints to archive and unarchive items, updates the UI to allow users to manage their archived content, and ensures that archived courses and decks are excluded from recommendations and practice sessions. Additionally, it improves the robustness of Docker builds and refines queries to handle archived content correctly.

Archive/Unarchive Functionality

Excluding Archived Content from User Experience

  • Updated practice session and recommendation APIs to filter out tasks belonging to archived courses, ensuring users are not assigned tasks from content they've archived. [1] [2] [3] [4] [5]
  • Modified course progress queries to ignore archived courses in dashboards and progress tracking.
  • In the course page, added logic to hide the main flashcard deck if it is archived for the user. (LearningPlatform/app/(student)/(shell)/courses/[slug]/page.tsxL150-R163)

Build Process Improvements

  • Enhanced the Dockerfile to make the npm ci step more robust by adding retry logic and cache verification, reducing build failures due to transient network issues.

These changes collectively provide users with fine-grained control over their learning content, improve the reliability of the build process, and ensure a cleaner, more relevant user experience by respecting archived states throughout the application.

Z4phxr added 3 commits April 25, 2026 14:21
…urseSlug and moduleSlug instead of courseId and moduleId for improved clarity and consistency
Copilot AI review requested due to automatic review settings April 25, 2026 14:01
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an “archive/unarchive” capability for courses and flashcard decks, wiring it through DB schema, API routes, and student UI so archived content is hidden from practice/recommendations and can be managed from the profile.

Changes:

  • Introduces archivedAt flags for course progress and user deck enrollments, plus filtering logic across dashboards/recommendations/practice.
  • Adds /api/profile/archive and /api/profile/unarchive endpoints and new UI actions/pages to archive/unarchive courses/decks.
  • Improves import/build robustness (import helper changes; Docker npm ci retry logic) and refines flashcard deck upsert behavior.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
LearningPlatform/scripts/imports/helpers/payload-client.js Adjusts Payload config loading and adds an @next/env compatibility shim for import scripts.
LearningPlatform/scripts/imports/helpers/next-env-default-shim.cjs Adds a standalone shim for @next/env default export compatibility.
LearningPlatform/scripts/imports/helpers/flashcard-import.js Improves deck upsert matching (by slug and module) and updates by id.
LearningPlatform/prisma/schema.prisma Adds archivedAt fields + indexes for course progress and user deck enrollments.
LearningPlatform/prisma/migrations/20260425122000_add_archive_flags/migration.sql Applies the new archivedAt columns and supporting indexes.
LearningPlatform/lib/started-courses.ts Excludes archived courses from “started courses” ordering.
LearningPlatform/lib/flashcards-dashboard-summary.ts Filters out archived courses/decks from flashcard dashboard summaries.
LearningPlatform/documentation/prompts/standalone_flashcards_prompt.MD Updates generation guidance to use slugs/orders instead of DB IDs.
LearningPlatform/documentation/prompts/creation_prompt.MD Updates flashcard-file conventions to avoid DB UUID placeholders and prefer slugs.
LearningPlatform/components/profile/archive-actions.tsx Adds client-side archive/unarchive buttons that call the new API routes.
LearningPlatform/components/navbar.tsx Makes the user avatar/name area link to the profile page.
LearningPlatform/components/dashboard/flashcard-deck-carousel.tsx Adds a carousel UI for decks with optional per-deck actions (used for archived decks).
LearningPlatform/components/dashboard/course-carousel.tsx Adds compact rendering and per-course footer actions (used for unarchive).
LearningPlatform/app/api/recommend/tasks/route.ts Filters recommended tasks to exclude those belonging to archived courses.
LearningPlatform/app/api/profile/unarchive/route.ts Adds unarchive endpoint for courses/decks.
LearningPlatform/app/api/profile/archive/route.ts Adds archive endpoint for courses/decks (with optional linked-item archiving).
LearningPlatform/app/api/practice/session/route.ts Filters practice session tasks to exclude archived-course tasks.
LearningPlatform/app/actions/course-progress.ts Excludes archived courses from “all course progress” action result.
LearningPlatform/app/(student)/(shell)/profile/page.tsx Adds profile page sections to view and unarchive archived courses/decks.
LearningPlatform/app/(student)/(shell)/dashboard/flashcards/page.tsx Adds “Archive deck” action to deck tree page.
LearningPlatform/app/(student)/(shell)/courses/[slug]/page.tsx Adds “Archive course” action and hides archived main deck on course page.
LearningPlatform/Dockerfile Adds retry/cache-verify logic to make npm ci more resilient in Docker builds.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 26 to 37
async function loadPayloadConfig() {
const base = path.join(__dirname, '../../../src/payload/payload.config')
const candidates = [`${base}.ts`, `${base}.js`].map((p) => pathToFileURL(p).href)

for (const href of candidates) {
try {
const mod = await import(href)
return await unwrapConfig(mod)
} catch {
// try next
}
}

const configJsPath = path.join(__dirname, '../../../src/payload/payload.config.js')
try {
const mod = tryLoadPayloadConfigWithTsxRequire()
const mod = await import(pathToFileURL(configJsPath).href)
return await unwrapConfig(mod)
} catch (err) {
const errDetails = err?.stack || err?.message || String(err)
throw new Error(
'Could not load Payload config. Install devDependency `tsx` and use tsconfig.scripts.json (see documentation/CONTENT_IMPORTS.md). ' +
`Last error: ${err?.message || err}`,
'Could not load Payload config via src/payload/payload.config.js import path. ' +
`Last error: ${errDetails}`,
)
}
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadPayloadConfig() now hard-codes src/payload/payload.config.js, but the repo’s Payload config is src/payload/payload.config.ts (and there is no .js sibling). This will cause all import runners that call initPayloadClient() to fail at runtime. Consider restoring the previous logic (try .ts via tsx-scoped require / TSX_TSCONFIG_PATH, then .js, then @payload-config) or otherwise ensuring a compiled .js exists in that location.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +10
// Shim for environments where transpiled code expects `@next/env` default export.
try {
// eslint-disable-next-line global-require
const nextEnv = require('@next/env')
if (nextEnv && nextEnv.default == null) {
nextEnv.default = nextEnv
}
} catch {
// noop
}
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file duplicates the @next/env default-export shim that’s already implemented inline in scripts/imports/helpers/payload-client.js, but there are no references to this shim anywhere in the repo. Either require this shim from the import runner entrypoints / payload-client.js, or delete it to avoid dead code and future confusion about which shim is actually used.

Suggested change
// Shim for environments where transpiled code expects `@next/env` default export.
try {
// eslint-disable-next-line global-require
const nextEnv = require('@next/env')
if (nextEnv && nextEnv.default == null) {
nextEnv.default = nextEnv
}
} catch {
// noop
}
// Duplicate `@next/env` default-export shim removed.
// The active shim is implemented inline in `scripts/imports/helpers/payload-client.js`.

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +5
import Link from 'next/link'
import { Button } from '@/components/ui/button'
import { Card, CardContent } from '@/components/ui/card'
import { DashboardHorizontalScroll } from '@/components/dashboard/dashboard-horizontal-scroll'
import { Brain } from 'lucide-react'
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This component imports DashboardHorizontalScroll, which is a Client Component ('use client'), but this file itself is missing a 'use client' directive. In Next.js App Router this will error at build/runtime (“You're importing a component that needs 'use client'”). Add 'use client' at the top (or refactor to avoid importing client-only code).

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +73
<Link href={row.openHref} className="min-w-0">
<Button
size="sm"
variant="hero"
className="auth-hero-cta h-auto w-full min-w-0 justify-center gap-1 whitespace-normal px-2 py-2 text-xs leading-tight sm:text-sm"
disabled={!canOpen}
>
<Brain className="h-4 w-4 shrink-0" />
Open Deck Tree
</Button>
</Link>
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<Link> wraps a disabled <Button>, but disabling the button does not prevent navigation via the link (users can still click/tap and navigate to openHref). If the intent is to block opening empty decks, conditionally render the <Link> only when canOpen is true (or apply aria-disabled + pointer-events: none on the link when disabled).

Suggested change
<Link href={row.openHref} className="min-w-0">
<Button
size="sm"
variant="hero"
className="auth-hero-cta h-auto w-full min-w-0 justify-center gap-1 whitespace-normal px-2 py-2 text-xs leading-tight sm:text-sm"
disabled={!canOpen}
>
<Brain className="h-4 w-4 shrink-0" />
Open Deck Tree
</Button>
</Link>
{canOpen ? (
<Link href={row.openHref} className="min-w-0">
<Button
size="sm"
variant="hero"
className="auth-hero-cta h-auto w-full min-w-0 justify-center gap-1 whitespace-normal px-2 py-2 text-xs leading-tight sm:text-sm"
>
<Brain className="h-4 w-4 shrink-0" />
Open Deck Tree
</Button>
</Link>
) : (
<Button
size="sm"
variant="hero"
className="auth-hero-cta h-auto w-full min-w-0 justify-center gap-1 whitespace-normal px-2 py-2 text-xs leading-tight sm:text-sm"
disabled
>
<Brain className="h-4 w-4 shrink-0" />
Open Deck Tree
</Button>
)}

Copilot uses AI. Check for mistakes.
Comment on lines 88 to 91
const startedRows = await prisma.courseProgress.findMany({
where: { userId },
where: { userId, archivedAt: null },
select: { courseId: true },
})
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This query filters on the new archivedAt column, but the file explicitly tries to remain usable “until migrations are applied”. If the DB hasn’t been migrated yet, referencing archivedAt on course_progress will throw and break the dashboard. To preserve the intended backward-compat behavior, wrap this in a try/catch with a fallback query (e.g., omit the archivedAt filter) or gate the filter behind a feature check.

Copilot uses AI. Check for mistakes.
Comment on lines +33 to +40
startTransition(async () => {
await postJson('/api/profile/archive', {
type: 'course',
courseSlug,
archiveLinkedDeck,
})
router.refresh()
})
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

postJson() throws on non-2xx responses, but these startTransition(async () => ...) handlers don’t catch errors. A 401/403 (not logged in) or 5xx will surface as an unhandled promise rejection and the UI won’t give any feedback. Wrap the transition body in try/catch and show a user-facing error (consistent with patterns like components/student/task-card.tsx).

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +57
const courseIdFromTask = (task: any): string | null => {
const lesson = task?.lesson
if (!lesson || typeof lesson !== 'object') return null
const course = (lesson as { course?: unknown }).course
if (!course) return null
if (typeof course === 'string' || typeof course === 'number') return String(course)
if (typeof course === 'object' && 'id' in course) return String((course as { id: string | number }).id)
return null
}
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

courseIdFromTask() is now duplicated here and in app/api/practice/session/route.ts. To avoid the two implementations drifting (especially around the Payload task/lesson/course shapes), consider extracting this helper into a shared utility (e.g. lib/payload-task-helpers.ts) and importing it from both routes.

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +47
const courseIdFromTask = (task: any): string | null => {
const lesson = task?.lesson
if (!lesson || typeof lesson !== 'object') return null
const course = (lesson as { course?: unknown }).course
if (!course) return null
if (typeof course === 'string' || typeof course === 'number') return String(course)
if (typeof course === 'object' && 'id' in course) return String((course as { id: string | number }).id)
return null
}
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

courseIdFromTask() is duplicated with the same helper in app/api/recommend/tasks/route.ts. Consider extracting it into a shared util to keep task→course parsing logic consistent across recommendation/practice endpoints.

Copilot uses AI. Check for mistakes.
…rses and decks when user resumes learning, enhancing user experience
@Z4phxr Z4phxr merged commit 39e3a3a into main Apr 25, 2026
4 checks passed
@Z4phxr Z4phxr deleted the fix/imports branch April 25, 2026 18:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants