Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
469 changes: 243 additions & 226 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@
"framer-motion": "^12.23.20",
"hanzi-writer": "^3.7.2",
"lucide-react": "^0.513.0",
"next": "15.3.3",
"next": "^16.1.1",
"next-themes": "^0.4.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"recharts": "^3.1.0",
"recharts": "^3.5.1",
"sonner": "^2.0.5",
"tailwind-merge": "^3.3.0",
"zustand": "^5.0.5"
Expand Down
1 change: 0 additions & 1 deletion src/app/api/v1/quiz/[level]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export async function GET(request: NextRequest, { params }: Props) {
});

if (error) {
console.error(`[ERROR] SELECT Quiz: ${error}`);
throw error;
}

Expand Down
2 changes: 1 addition & 1 deletion src/app/api/v2/quiz/[level]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export async function GET(request: NextRequest, { params }: Props) {
{ level, count: 10 }
);
if (wordsError) {
console.error(`[ERROR] SELECT Quiz: ${wordsError}`);
console.error('[ERROR] SELECT Quiz:', wordsError);
throw wordsError;
}

Expand Down
25 changes: 25 additions & 0 deletions src/app/api/v2/word/[hanzi]/route.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
import { NextRequest, NextResponse } from 'next/server';
// import { createClient as createServerClient } from '@/lib/supabase/server';
// import { SupabaseClient, User } from '@supabase/supabase-js';

export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ hanzi: string }> }
) {
const { hanzi } = await params;
// TODO: 쿼리 확인 필요
// const supabase = await createServerClient();
// const { data, error } = await supabase
// .from('words')
// .select(
// `
// id,
// hanzi,
// pinyin,
// meaning,
// examples:examples (
// id,
// cn,
// pinyin,
// kr
// )
// `
// )
// .eq('hanzi', hanzi)
// .single();

// if (error) return Response.json({ error }, { status: 500 });

const systemPrompt =
'너는 중국어 교육 플랫폼의 콘텐츠 생성 도우미야. 입력된 한자 단어를 바탕으로 예문과 한국어로 해석된 문장을 만들어줘.';
const userPrompt = `
Expand Down
4 changes: 1 addition & 3 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ export default function RootLayout({
<Providers>
<ClientUserProvider />
<Header />
<main className="w-full max-w-5xl px-4 sm:px-6 lg:px-8 mx-auto flex flex-1 flex-col py-10 lg:py-15">
{children}
</main>
<main className="w-full flex flex-1 flex-col">{children}</main>
<Footer />
<Toaster position="top-right" expand={true} richColors />
</Providers>
Expand Down
6 changes: 2 additions & 4 deletions src/app/mypage/bookmarks/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type BookmarkType = {
id: string;
};

const BookmarksPage = () => {
export default function BookmarksPage() {
const [bookmarks, setBookmarks] = useState<BookmarkType[]>([]);
const [user, setUser] = useState<User | null>(null);

Expand Down Expand Up @@ -131,6 +131,4 @@ const BookmarksPage = () => {
})}
</div>
);
};

export default BookmarksPage;
}
8 changes: 3 additions & 5 deletions src/app/mypage/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import ClientMyPage from '@/components/mypage/ClientMyPage';
import ClientMyPage from '@/features/mypage/ClientMyPage';

// 공식 예제들이 대부분 async function을 씀. 이유는 TypeScript 타입 추론과 displayName (디버깅 시 이름) 때문에.
// const로 하면 스택트레이스에서 함수 이름이 익명으로 보이거나 최적화가 덜 되는 경우가 있었음.

const MyPage = () => {
export default function MyPage() {
return (
<div className="min-h-screen to-blue-50 md:p-6">
<div className="max-w-6xl mx-auto space-y-4 md:space-y-6">
Expand All @@ -25,6 +25,4 @@ const MyPage = () => {
</div>
</div>
);
};

export default MyPage;
}
6 changes: 2 additions & 4 deletions src/app/mypage/quizzes/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
'use client';

const QuizzesPage = () => {
export default function QuizzesPage() {
return <div></div>;
};

export default QuizzesPage;
}
6 changes: 2 additions & 4 deletions src/app/mypage/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
const SettingPage = () => {
export default function SettingPage() {
return <></>;
};

export default SettingPage;
}
41 changes: 6 additions & 35 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,10 @@
import { Button } from '@/components/ui/button';
import Link from 'next/link';
import { HeroSection } from '@/features/home/HeroSection';

export default function Home() {
const levels = [
{
name: '1급',
link: '1',
},
{
name: '2급',
link: '2',
},
{
name: '3급',
link: '3',
},
{
name: '4급',
link: '4',
},
{
name: '5급',
link: '5',
},
{
name: '6급',
link: '6',
},
];

return (
<div className="">
<section className="text-center max-w-4xl mx-auto">
<h1 className="text-3xl font-bold mb-4 text-left">
<section className="text-center w-full mx-auto">
<HeroSection />
{/* <h1 className="text-3xl font-bold mb-4 text-left">
도전할 HSK 급수를 선택하세요
</h1>
<div className="text-lg sm:text-xl text-gray-600 dark:text-gray-300 mb-6 grid grid-cols-3 gap-4">
Expand All @@ -48,8 +20,7 @@ export default function Home() {
</Link>
</Button>
))}
</div>
</section>
</div>
</div> */}
</section>
);
}
8 changes: 3 additions & 5 deletions src/app/quiz/[level]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ClientQuizPage from '@/components/quiz/ClientQuizPage';
import ClientQuizPage from '@/features/quiz/ClientQuizPage';
import Link from 'next/link';

type Props = {
Expand All @@ -7,7 +7,7 @@ type Props = {
}>;
};

const QuizLevelPage = async ({ params }: Props) => {
export default async function QuizLevelPage({ params }: Props) {
const { level } = await params;

// 4급, 5급, 6급은 아직 데이터가 없음
Expand All @@ -31,6 +31,4 @@ const QuizLevelPage = async ({ params }: Props) => {
}

return <ClientQuizPage level={level} />;
};

export default QuizLevelPage;
}
8 changes: 3 additions & 5 deletions src/app/quiz/result/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import ClientQuizResult from '@/components/quiz/ClientQuizResult';
import ClientQuizResult from '@/features/quiz/ClientQuizResult';

type Props = {
params: Promise<{
id: string;
}>;
};

const QuizResultPage = async ({ params }: Props) => {
export default async function QuizResultPage({ params }: Props) {
const { id } = await params;

return (
<div className="min-h-screen">
<ClientQuizResult quizId={id} />
</div>
);
};

export default QuizResultPage;
}
63 changes: 54 additions & 9 deletions src/app/word/[level]/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,65 @@
import ClientWordDetail from '@/components/word/ClientWordDetail';
import ClientWordDetail from '@/features/word/ClientWordDetail';
import { createClient } from '@/lib/supabase/server';
import { notFound } from 'next/navigation';
import { WordData } from '@/types/word';

type Props = {
params: Promise<{
id: string;
}>;
};

// 서버 컴포넌트: 파라미터만 전달
const WordDetailPage = async ({ params }: Props) => {
export default async function WordDetailPage({ params }: Props) {
const { id } = await params;
const supabase = await createClient();

return (
<section className="w-full p-0 md:p-6 bg-white">
<ClientWordDetail wordId={id} />
</section>
const { data: wordInfo, error } = await supabase
.from('words')
.select(
`
*,
examples!word_id (
sentence,
meaning,
pinyin,
context
),
word_relations!word_id (
word,
meaning,
pinyin,
relation_type
),
bookmarks!word_id (
id,
user_id
)
`
)
.eq('id', id)
.single();

if (error || !wordInfo) {
if (error) console.error('Error fetching word detail:', error);
return notFound();
}

const isBookmarked = !!(
wordInfo.bookmarks &&
Array.isArray(wordInfo.bookmarks) &&
wordInfo.bookmarks.length > 0
);
};

export default WordDetailPage;
const initialData: WordData = {
...wordInfo,
examples: wordInfo.examples || [],
word_relations: wordInfo.word_relations || [],
is_bookmarked: isBookmarked,
};

return (
<div className="w-full max-w-5xl mx-auto py-10 lg:py-15">
<ClientWordDetail wordId={id} initialData={initialData} />
</div>
);
}
12 changes: 5 additions & 7 deletions src/app/word/[level]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import Link from 'next/link';
import { createClient } from '@/lib/supabase/server';
import ChallengeButton from '@/components/word/ChallengeButton';
import ChallengeButton from '@/features/word/ChallengeButton';
import ErrorFallback from '@/components/ErrorFallback';
import ClientWordList from '@/components/word/ClientWordList';
import ClientWordList from '@/features/word/ClientWordList';

type Props = {
params: Promise<{
level: string;
}>;
};

const WordPage = async ({ params }: Props) => {
export default async function WordPage({ params }: Props) {
const { level } = await params;
const supabase = await createClient();
// 렌더링 속도가 느려 SSR 렌더링 시 28글자만 가져옴, 나머진 CSR로 가져옴
Expand Down Expand Up @@ -43,7 +43,7 @@ const WordPage = async ({ params }: Props) => {
}

return (
<div>
<div className="w-full max-w-5xl px-4 sm:px-6 lg:px-8 mx-auto py-10 lg:py-15">
<h1 className="title">{level}급 단어</h1>
<div className="flex justify-end mb-10 animate-wiggle">
{/* 학습중 으로 상태 변경하려면... usestate 필요. */}
Expand All @@ -52,6 +52,4 @@ const WordPage = async ({ params }: Props) => {
<ClientWordList wordList={words} level={Number(level)} />
</div>
);
};

export default WordPage;
}
Loading