feat(courses): add unarchive functionality and toast notifications#25
feat(courses): add unarchive functionality and toast notifications#25
Conversation
…r course and deck actions
There was a problem hiding this comment.
Pull request overview
Adds student-facing unarchive actions and introduces lightweight toast notifications to confirm archive/unarchive operations across course/deck UI, including conditional archive vs unarchive controls on the course page.
Changes:
- Added an inline
ActionToastcomponent +useActionToasthook and wired to archive/unarchive buttons. - Added archived-state lookup on the course page to render
UnarchiveCourseButtonwhen applicable. - Updated course page imports to include
UnarchiveCourseButton.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
LearningPlatform/components/profile/archive-actions.tsx |
Adds toast UI + hook and shows toasts after archive/unarchive actions. |
LearningPlatform/app/(student)/(shell)/courses/[slug]/page.tsx |
Checks whether the current user has archived the course and conditionally renders archive/unarchive action. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| startTransition(async () => { | ||
| try { | ||
| await postJson('/api/profile/archive', { | ||
| type: 'course', | ||
| courseSlug, | ||
| archiveLinkedDeck, | ||
| }) | ||
| router.refresh() | ||
| } catch (err) { | ||
| setError(err instanceof Error ? err.message : 'Failed to archive course') | ||
| } | ||
| await postJson('/api/profile/archive', { | ||
| type: 'course', | ||
| courseSlug, | ||
| archiveLinkedDeck, | ||
| }) | ||
| showToast('Course archived') | ||
| router.refresh() | ||
| }) |
There was a problem hiding this comment.
Because the toast state is local to each button component, calling router.refresh() right after showToast() can cause the component to unmount (e.g. when the parent server component re-renders and the archived item disappears / the button switches from Archive→Unarchive). In those cases the toast will be cleared immediately and users may not see any feedback. Consider moving the toast state/host to a stable parent (context/provider/portal) that survives refreshes, or delaying the refresh until after the toast duration.
| {courseArchived ? ( | ||
| <UnarchiveCourseButton courseId={courseId} /> | ||
| ) : ( | ||
| <ArchiveCourseButton courseSlug={slug} /> | ||
| )} |
There was a problem hiding this comment.
The course page swaps ↔ based on courseArchived, and the button components own the toast state. After an archive/unarchive click, router.refresh() will re-render the page and replace the button component, unmounting it and clearing the toast state—so the toast may flash briefly or not be visible at all. Consider hoisting the toast host/state to a parent that won’t unmount on refresh (e.g. a layout-level toast provider), or keep a stable client wrapper around this section and render the toast there instead of inside the button components.
| {courseArchived ? ( | |
| <UnarchiveCourseButton courseId={courseId} /> | |
| ) : ( | |
| <ArchiveCourseButton courseSlug={slug} /> | |
| )} | |
| <div | |
| className={courseArchived ? 'hidden' : undefined} | |
| aria-hidden={courseArchived} | |
| tabIndex={courseArchived ? -1 : undefined} | |
| > | |
| <ArchiveCourseButton courseSlug={slug} /> | |
| </div> | |
| <div | |
| className={courseArchived ? undefined : 'hidden'} | |
| aria-hidden={!courseArchived} | |
| tabIndex={!courseArchived ? -1 : undefined} | |
| > | |
| <UnarchiveCourseButton courseId={courseId} /> | |
| </div> |
| const archivedCourse = await prisma.courseProgress.findFirst({ | ||
| where: { | ||
| userId: session.user.id, | ||
| courseId, |
There was a problem hiding this comment.
CourseProgress has a @@unique([userId, courseId]), so this lookup should use findUnique({ where: { userId_courseId: ... } }) instead of findFirst. Using findUnique makes the intent clearer and lets Prisma take advantage of the unique constraint/index.
| const archivedCourse = await prisma.courseProgress.findFirst({ | |
| where: { | |
| userId: session.user.id, | |
| courseId, | |
| const archivedCourse = await prisma.courseProgress.findUnique({ | |
| where: { | |
| userId_courseId: { | |
| userId: session.user.id, | |
| courseId, | |
| }, |
| <div | ||
| role="status" | ||
| aria-live="polite" | ||
| className="fixed bottom-4 left-1/2 z-50 -translate-x-1/2 rounded-md border border-black/10 bg-black/85 px-3 py-2 text-xs text-white shadow-lg" |
There was a problem hiding this comment.
This toast is position: fixed and can intercept pointer events while visible. Adding pointer-events-none (or otherwise ensuring it doesn’t capture clicks) prevents it from blocking underlying UI controls for ~2.2s, which is especially noticeable on small screens.
| className="fixed bottom-4 left-1/2 z-50 -translate-x-1/2 rounded-md border border-black/10 bg-black/85 px-3 py-2 text-xs text-white shadow-lg" | |
| className="pointer-events-none fixed bottom-4 left-1/2 z-50 -translate-x-1/2 rounded-md border border-black/10 bg-black/85 px-3 py-2 text-xs text-white shadow-lg" |
This pull request enhances the course and deck archiving experience for students by introducing user feedback to archiving actions and enabling unarchiving of courses directly from the course page. The changes focus on improving user interaction and providing immediate confirmation of actions.
User feedback improvements:
ActionToastcomponent and auseActionToasthook to display temporary toast messages after archiving or unarchiving actions, providing clear feedback to users. Toasts are now shown after archiving/unarchiving courses and decks in all related action buttons (ArchiveCourseButton,ArchiveDeckButton,UnarchiveCourseButton,UnarchiveDeckButton). [1] [2] [3] [4] [5]Course page enhancements:
ArchiveCourseButtonor a newUnarchiveCourseButtonon the course page. This allows students to unarchive a course directly from its page if it has been archived. (LearningPlatform/app/(student)/(shell)/courses/[slug]/page.tsxL16-R16, LearningPlatform/app/(student)/(shell)/courses/[slug]/page.tsxR150-R160, LearningPlatform/app/(student)/(shell)/courses/[slug]/page.tsxR494-R498)Component and import updates:
UnarchiveCourseButtonfromarchive-actions.tsx. (LearningPlatform/app/(student)/(shell)/courses/[slug]/page.tsxL16-R16, LearningPlatform/components/profile/archive-actions.tsxL3-R3)