From 6213dd2338402f81f4a6eb4a4dcf81256ee208de Mon Sep 17 00:00:00 2001 From: goddonebianu Date: Sat, 14 Mar 2026 11:34:46 +0800 Subject: [PATCH] refactor: move memorize feature to /decks/[deck_id]/learn route - Delete (features)/memorize directory - Create /decks/[deck_id]/learn with Memorize component and page - Update InDeck.tsx to navigate to new learn route - Fix homepage memorize link to point to /decks --- src/app/(features)/memorize/DeckSelector.tsx | 114 ------------------ src/app/(features)/memorize/page.tsx | 57 --------- src/app/decks/[deck_id]/InDeck.tsx | 4 +- .../[deck_id]/learn}/Memorize.tsx | 8 +- .../[deck_id]/learn}/interval-preview.ts | 2 +- src/app/decks/[deck_id]/learn/page.tsx | 34 ++++++ src/app/page.tsx | 2 +- 7 files changed, 42 insertions(+), 179 deletions(-) delete mode 100644 src/app/(features)/memorize/DeckSelector.tsx delete mode 100644 src/app/(features)/memorize/page.tsx rename src/app/{(features)/memorize => decks/[deck_id]/learn}/Memorize.tsx (97%) rename src/app/{(features)/memorize => decks/[deck_id]/learn}/interval-preview.ts (97%) create mode 100644 src/app/decks/[deck_id]/learn/page.tsx diff --git a/src/app/(features)/memorize/DeckSelector.tsx b/src/app/(features)/memorize/DeckSelector.tsx deleted file mode 100644 index 436792d..0000000 --- a/src/app/(features)/memorize/DeckSelector.tsx +++ /dev/null @@ -1,114 +0,0 @@ -"use client"; - -import { useRouter } from "next/navigation"; -import { useTranslations } from "next-intl"; -import Link from "next/link"; -import { Layers } from "lucide-react"; -import type { ActionOutputDeck } from "@/modules/deck/deck-action-dto"; -import type { ActionOutputCardStats } from "@/modules/card/card-action-dto"; -import { PageLayout } from "@/components/ui/PageLayout"; -import { PrimaryButton } from "@/design-system/base/button"; - -interface DeckWithStats extends ActionOutputDeck { - stats?: ActionOutputCardStats; -} - -interface DeckSelectorProps { - decks: ActionOutputDeck[]; - deckStats: Map; -} - -const DeckSelector: React.FC = ({ decks, deckStats }) => { - const t = useTranslations("memorize.deck_selector"); - const router = useRouter(); - - const formatCardStats = (stats: ActionOutputCardStats | undefined): string => { - if (!stats) return t("noCards"); - const parts: string[] = []; - if (stats.new > 0) parts.push(`${t("new")}: ${stats.new}`); - if (stats.learning > 0) parts.push(`${t("learning")}: ${stats.learning}`); - if (stats.review > 0) parts.push(`${t("review")}: ${stats.review}`); - if (stats.due > 0) parts.push(`${t("due")}: ${stats.due}`); - return parts.length > 0 ? parts.join(" • ") : t("noCards"); - }; - - const getDueCount = (deckId: number): number => { - const stats = deckStats.get(deckId); - return stats?.due ?? 0; - }; - - return ( - - {decks.length === 0 ? ( -
-

- {t("noDecks")} -

- - - {t("goToDecks")} - - -
- ) : ( - <> -

- {t("selectDeck")} -

-
- {decks - .toSorted((a, b) => a.id - b.id) - .map((deck) => { - const stats = deckStats.get(deck.id); - const dueCount = getDueCount(deck.id); - - return ( -
- router.push(`/memorize?deck_id=${deck.id}`) - } - className="flex flex-row items-center p-4 gap-3 hover:cursor-pointer hover:bg-gray-50 transition-colors border-b border-gray-100 last:border-b-0" - > -
- -
-
-
- {deck.name} -
-
- {formatCardStats(stats)} -
-
- {dueCount > 0 && ( -
- {dueCount} -
- )} -
- - - -
-
- ); - })} -
- - )} -
- ); -}; - -export { DeckSelector }; diff --git a/src/app/(features)/memorize/page.tsx b/src/app/(features)/memorize/page.tsx deleted file mode 100644 index 31c3fef..0000000 --- a/src/app/(features)/memorize/page.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { redirect } from "next/navigation"; -import { getTranslations } from "next-intl/server"; -import { isNonNegativeInteger } from "@/utils/random"; -import { DeckSelector } from "./DeckSelector"; -import { Memorize } from "./Memorize"; -import { auth } from "@/auth"; -import { headers } from "next/headers"; -import { actionGetDecksByUserId } from "@/modules/deck/deck-action"; -import { actionGetCardStats } from "@/modules/card/card-action"; - -export default async function MemorizePage({ - searchParams, -}: { - searchParams: Promise<{ deck_id?: string; }>; -}) { - const deckIdParam = (await searchParams).deck_id; - - const t = await getTranslations("memorize.page"); - - const deckId = deckIdParam - ? isNonNegativeInteger(deckIdParam) - ? parseInt(deckIdParam) - : null - : null; - - const session = await auth.api.getSession({ headers: await headers() }); - if (!session) redirect("/login?redirect=/memorize"); - - if (!deckId) { - const decksResult = await actionGetDecksByUserId(session.user.id); - const decks = decksResult.data ?? []; - - const deckStats = new Map>["data"]>(); - for (const deck of decks) { - const statsResult = await actionGetCardStats({ deckId: deck.id }); - if (statsResult.success && statsResult.data) { - deckStats.set(deck.id, statsResult.data); - } - } - - return ( - - ); - } - - const decksResult = await actionGetDecksByUserId(session.user.id); - const deck = decksResult.data?.find(d => d.id === deckId); - - if (!deck) { - redirect("/memorize"); - } - - return ; -} diff --git a/src/app/decks/[deck_id]/InDeck.tsx b/src/app/decks/[deck_id]/InDeck.tsx index 8fd7a63..84c5eee 100644 --- a/src/app/decks/[deck_id]/InDeck.tsx +++ b/src/app/decks/[deck_id]/InDeck.tsx @@ -2,7 +2,7 @@ import { ArrowLeft, Plus, RotateCcw } from "lucide-react"; import { useEffect, useState } from "react"; -import { redirect, useRouter } from "next/navigation"; +import { useRouter } from "next/navigation"; import { AddCardModal } from "./AddCardModal"; import { CardItem } from "./CardItem"; import { useTranslations } from "next-intl"; @@ -99,7 +99,7 @@ export function InDeck({ deckId, isReadOnly }: { deckId: number; isReadOnly: boo
{ - redirect(`/memorize?deck_id=${deckId}`); + router.push(`/decks/${deckId}/learn`); }} > {t("memorize")} diff --git a/src/app/(features)/memorize/Memorize.tsx b/src/app/decks/[deck_id]/learn/Memorize.tsx similarity index 97% rename from src/app/(features)/memorize/Memorize.tsx rename to src/app/decks/[deck_id]/learn/Memorize.tsx index a634d45..9912865 100644 --- a/src/app/(features)/memorize/Memorize.tsx +++ b/src/app/decks/[deck_id]/learn/Memorize.tsx @@ -9,11 +9,11 @@ import type { ActionOutputCardWithNote, ActionOutputScheduledCard } from "@/modu import { actionGetCardsForReview, actionAnswerCard } from "@/modules/card/card-action"; import { PageLayout } from "@/components/ui/PageLayout"; import { LightButton } from "@/design-system/base/button"; -import { CardType } from "../../../../generated/prisma/enums"; +import { CardType } from "../../../../../generated/prisma/enums"; import { calculatePreviewIntervals, formatPreviewInterval, type CardPreview } from "./interval-preview"; const myFont = localFont({ - src: "../../../../public/fonts/NotoNaskhArabic-VariableFont_wght.ttf", + src: "../../../../../public/fonts/NotoNaskhArabic-VariableFont_wght.ttf", }); interface MemorizeProps { @@ -207,7 +207,7 @@ const Memorize: React.FC = ({ deckId, deckName }) => {

{error}

- router.push("/memorize")} className="px-4 py-2"> + router.push("/decks")} className="px-4 py-2"> {t("backToDecks")}
@@ -224,7 +224,7 @@ const Memorize: React.FC = ({ deckId, deckName }) => {

{t("allDone")}

{t("allDoneDesc")}

- router.push("/memorize")} className="px-4 py-2"> + router.push("/decks")} className="px-4 py-2"> {t("backToDecks")} diff --git a/src/app/(features)/memorize/interval-preview.ts b/src/app/decks/[deck_id]/learn/interval-preview.ts similarity index 97% rename from src/app/(features)/memorize/interval-preview.ts rename to src/app/decks/[deck_id]/learn/interval-preview.ts index a09f574..11244e0 100644 --- a/src/app/(features)/memorize/interval-preview.ts +++ b/src/app/decks/[deck_id]/learn/interval-preview.ts @@ -1,4 +1,4 @@ -import { CardType } from "../../../../generated/prisma/enums"; +import { CardType } from "../../../../../generated/prisma/enums"; import { SM2_CONFIG } from "@/modules/card/card-service-dto"; export interface CardPreview { diff --git a/src/app/decks/[deck_id]/learn/page.tsx b/src/app/decks/[deck_id]/learn/page.tsx new file mode 100644 index 0000000..c4c8764 --- /dev/null +++ b/src/app/decks/[deck_id]/learn/page.tsx @@ -0,0 +1,34 @@ +import { redirect } from "next/navigation"; +import { auth } from "@/auth"; +import { headers } from "next/headers"; +import { actionGetDeckById } from "@/modules/deck/deck-action"; +import { Memorize } from "./Memorize"; + +export default async function LearnPage({ + params, +}: { + params: Promise<{ deck_id: string }>; +}) { + const session = await auth.api.getSession({ headers: await headers() }); + const { deck_id } = await params; + const deckId = Number(deck_id); + + if (!deckId) { + redirect("/decks"); + } + + const deckInfo = (await actionGetDeckById({ deckId })).data; + + if (!deckInfo) { + redirect("/decks"); + } + + const isOwner = session?.user?.id === deckInfo.userId; + const isPublic = deckInfo.visibility === "PUBLIC"; + + if (!isOwner && !isPublic) { + redirect("/decks"); + } + + return ; +} diff --git a/src/app/page.tsx b/src/app/page.tsx index 2e598bd..fe6f944 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -73,7 +73,7 @@ export default async function HomePage() { color="#dd7486" >