From c2104ec1a64c8d41e43f4af8cc12e74558ce91bf Mon Sep 17 00:00:00 2001 From: msuhyeon Date: Thu, 8 Jan 2026 18:18:58 +0900 Subject: [PATCH 1/4] style: modify page layout --- src/app/mypage/page.tsx | 2 +- src/app/word/[level]/[id]/page.tsx | 2 +- src/app/word/[level]/page.tsx | 2 +- src/components/Header.tsx | 5 +++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/app/mypage/page.tsx b/src/app/mypage/page.tsx index 77975c4..dc56b7a 100644 --- a/src/app/mypage/page.tsx +++ b/src/app/mypage/page.tsx @@ -6,7 +6,7 @@ import ClientMyPage from '@/features/mypage/ClientMyPage'; export default function MyPage() { return (
-
+

마이페이지

{/* TODO: 계정 정보 뭘 수정할지 확인 */} diff --git a/src/app/word/[level]/[id]/page.tsx b/src/app/word/[level]/[id]/page.tsx index 1e53a2a..12496dd 100644 --- a/src/app/word/[level]/[id]/page.tsx +++ b/src/app/word/[level]/[id]/page.tsx @@ -58,7 +58,7 @@ export default async function WordDetailPage({ params }: Props) { }; return ( -
+
); diff --git a/src/app/word/[level]/page.tsx b/src/app/word/[level]/page.tsx index 5e3f70e..5e8834a 100644 --- a/src/app/word/[level]/page.tsx +++ b/src/app/word/[level]/page.tsx @@ -43,7 +43,7 @@ export default async function WordPage({ params }: Props) { } return ( -
+

{level}급 단어

{/* 학습중 으로 상태 변경하려면... usestate 필요. */} diff --git a/src/components/Header.tsx b/src/components/Header.tsx index f0fefcc..c6f5c50 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -55,9 +55,10 @@ export default function Header() { ]; return ( -
+ //
+
-

+

HSKPass

{/* desktop layout */} From 9f5c87ba19e828cb2c4a1a1dd639b98257169715 Mon Sep 17 00:00:00 2001 From: msuhyeon Date: Thu, 8 Jan 2026 18:19:27 +0900 Subject: [PATCH 2/4] =?UTF-8?q?refactor:=20login=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EB=AA=A8=EB=93=88=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Login.tsx | 41 ++++------------------------- src/components/icons/GoogleIcon.tsx | 26 ++++++++++++++++++ src/lib/supabase/userApi.ts | 14 ++++++++++ 3 files changed, 45 insertions(+), 36 deletions(-) create mode 100644 src/components/icons/GoogleIcon.tsx diff --git a/src/components/Login.tsx b/src/components/Login.tsx index 862fada..61f5e36 100644 --- a/src/components/Login.tsx +++ b/src/components/Login.tsx @@ -1,7 +1,7 @@ 'use client'; import { Button } from '@/components/ui/button'; -import { supabase } from '@/lib/supabase/client'; + import { toast } from 'sonner'; import { Dialog, @@ -10,7 +10,7 @@ import { DialogHeader, DialogTitle, } from '@/components/ui/dialog'; -import { logout } from '@/lib/supabase/userApi'; +import { logout, loginWithGoogle } from '@/lib/supabase/userApi'; import { LogInIcon } from 'lucide-react'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { useRouter } from 'next/navigation'; @@ -18,33 +18,7 @@ import { useUser } from '@/hooks/useUser'; import { useQueryClient } from '@tanstack/react-query'; import { useModal } from '@/hooks/useMoal'; -// Google Icon -function GoogleIcon() { - return ( - - - - - - - ); -} +import { GoogleIcon } from '@/components/icons/GoogleIcon'; export default function Login() { const { data: user } = useUser(); @@ -52,15 +26,10 @@ export default function Login() { const router = useRouter(); const { loginOpen, openLoginModal, closeLoginModal } = useModal(); - const handleLogin = () => { + const handleLogin = async () => { try { // 브라우저에서 팝업 또는 리다이렉트를 통해 동작하므로 client component - supabase.auth.signInWithOAuth({ - provider: 'google', - options: { - redirectTo: process.env.NEXT_PUBLIC_BASE_URL, - }, - }); + await loginWithGoogle(); } catch (error) { console.error(`[ERROR] Failed login: ${error}`); toast.error('로그인 실패. 다시 시도해주세요.'); diff --git a/src/components/icons/GoogleIcon.tsx b/src/components/icons/GoogleIcon.tsx new file mode 100644 index 0000000..89469c2 --- /dev/null +++ b/src/components/icons/GoogleIcon.tsx @@ -0,0 +1,26 @@ +export function GoogleIcon() { + return ( + + + + + + + ); +} diff --git a/src/lib/supabase/userApi.ts b/src/lib/supabase/userApi.ts index 7eb11bc..a3ddcea 100644 --- a/src/lib/supabase/userApi.ts +++ b/src/lib/supabase/userApi.ts @@ -24,3 +24,17 @@ export const logout = async () => { throw error; } }; + +export const loginWithGoogle = async () => { + const { error } = await supabase.auth.signInWithOAuth({ + provider: 'google', + options: { + redirectTo: process.env.NEXT_PUBLIC_BASE_URL, + }, + }); + + if (error) { + console.error(`[ERROR] Failed login: ${error}`); + throw error; + } +}; From 562e87b7c48af11eb399602742878f2c989d74a6 Mon Sep 17 00:00:00 2001 From: msuhyeon Date: Thu, 8 Jan 2026 18:20:10 +0900 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=ED=9E=88=EC=96=B4=EB=A1=9C=20?= =?UTF-8?q?=EC=84=B9=EC=85=98=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EC=83=88=EB=A1=9C=EC=9A=B4=20?= =?UTF-8?q?=EC=98=81=EC=97=AD=20=EC=B6=94=EA=B0=80=20=20-=20PrimaryContent?= =?UTF-8?q?Section=20=20-=20CTASection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/page.tsx | 21 ++----- src/components/ui/badge.tsx | 2 +- src/features/home/CTASection.tsx | 3 + src/features/home/HeroSection.tsx | 67 ++++++++++++++++++--- src/features/home/PrimaryContentSection.tsx | 45 ++++++++++++++ 5 files changed, 113 insertions(+), 25 deletions(-) create mode 100644 src/features/home/CTASection.tsx create mode 100644 src/features/home/PrimaryContentSection.tsx diff --git a/src/app/page.tsx b/src/app/page.tsx index 6fe1269..f9d28c4 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,26 +1,13 @@ +import CTASection from '@/features/home/CTASection'; import { HeroSection } from '@/features/home/HeroSection'; +import PrimaryContentSection from '@/features/home/PrimaryContentSection'; export default function Home() { return (
- {/*

- 도전할 HSK 급수를 선택하세요 -

-
- {levels.map((item, index) => ( - - ))} -
*/} + +
); } diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx index 0205413..fd3a406 100644 --- a/src/components/ui/badge.tsx +++ b/src/components/ui/badge.tsx @@ -5,7 +5,7 @@ import { cva, type VariantProps } from "class-variance-authority" import { cn } from "@/lib/utils" const badgeVariants = cva( - "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", + "inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", { variants: { variant: { diff --git a/src/features/home/CTASection.tsx b/src/features/home/CTASection.tsx new file mode 100644 index 0000000..a0d11ca --- /dev/null +++ b/src/features/home/CTASection.tsx @@ -0,0 +1,3 @@ +export default function CTASection() { + return
; +} diff --git a/src/features/home/HeroSection.tsx b/src/features/home/HeroSection.tsx index a512c29..ac2237e 100644 --- a/src/features/home/HeroSection.tsx +++ b/src/features/home/HeroSection.tsx @@ -1,9 +1,23 @@ 'use client'; -import { ArrowRight, BookOpen, Sparkles, Trophy } from 'lucide-react'; import Link from 'next/link'; +import { GoogleIcon } from '@/components/icons/GoogleIcon'; +import { loginWithGoogle } from '@/lib/supabase/userApi'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { useModal } from '@/hooks/useMoal'; +import { toast } from 'sonner'; +import { ArrowRight, BookOpen, Sparkles, Trophy } from 'lucide-react'; export function HeroSection() { + const { loginOpen, openLoginModal, closeLoginModal } = useModal(); + const hskLevels = [ { level: 1, words: 150, color: 'from-green-400 to-emerald-500' }, { level: 2, words: 300, color: 'from-blue-400 to-cyan-500' }, @@ -28,8 +42,18 @@ export function HeroSection() { { char: '文', left: '65%', top: '80%' }, ]; + const handleLogin = async () => { + try { + // 브라우저에서 팝업 또는 리다이렉트를 통해 동작하므로 client component + await loginWithGoogle(); + } catch (error) { + console.error(`[ERROR] Failed login: ${error}`); + toast.error('로그인 실패. 다시 시도해주세요.'); + } + }; + return ( -
+
{/* 백그라운드에 한자 플로팅 */}
{floatingChars.map((item, i) => ( @@ -53,7 +77,7 @@ export function HeroSection() {
-
+
@@ -65,9 +89,9 @@ export function HeroSection() { 똑똑한 HSK 학습

- 10,000개 이상의 단어, AI 생성 예문, 실시간 퀴즈로 + 10,000개 이상의 HSK 단어를
- 획순 애니메이션과 함께 정확하게 배우세요 + AI가 생성한 예문과 퀴즈로 학습하세요

@@ -88,7 +112,10 @@ export function HeroSection() { {/* CTA Buttons */}
- @@ -144,7 +171,33 @@ export function HeroSection() {
+ + {/* 로그인 모달 */} + { + if (!open) closeLoginModal(); + }} + > + + + 로그인 + + + + + + +
- //
); } diff --git a/src/features/home/PrimaryContentSection.tsx b/src/features/home/PrimaryContentSection.tsx new file mode 100644 index 0000000..65c84e5 --- /dev/null +++ b/src/features/home/PrimaryContentSection.tsx @@ -0,0 +1,45 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; + +export default function PrimaryContentSection() { + const contents = [ + { + title: 'AI가 만든 예문·동의어로 맥락 학습', + subtitle: `AI가 단어의 의미를 분석해 + 자연스러운 예문과 함께 + 유의어·반의어·동의어까지 + 한 번에 이해할 수 있도록 제공합니다.`, + }, + { + title: 'AI 퀴즈로 바로 복습', + subtitle: `AI가 다양한 유형의 + 10가지 퀴즈를 자동 생성해 + 실제 시험처럼 반복 학습할 수 있습니다.`, + }, + { + title: '학습 결과를 한눈에', + subtitle: `잘 외워지지 않는 단어는 + 북마크로 저장하고, + 퀴즈 결과와 오답 내역은 + 마이페이지에서 한눈에 확인하세요.`, + }, + ]; + + return ( +
+ {contents.map((content, index) => ( + + + + + Step {index + 1} + + {content.title} + + + {content.subtitle} + + ))} +
+ ); +} From 532b6823b398b15c7e32789e31d497c781601d2b Mon Sep 17 00:00:00 2001 From: msuhyeon Date: Fri, 9 Jan 2026 12:37:13 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20pirmary=20contents=20section?= =?UTF-8?q?=EC=97=90=20=EB=B3=B4=EC=97=AC=EC=A4=84=20=EB=82=B4=EC=9A=A9=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/home/PrimaryContentSection.tsx | 48 ++++++++++++++------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/src/features/home/PrimaryContentSection.tsx b/src/features/home/PrimaryContentSection.tsx index 65c84e5..7d4cfcf 100644 --- a/src/features/home/PrimaryContentSection.tsx +++ b/src/features/home/PrimaryContentSection.tsx @@ -1,27 +1,33 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; +import { Dot } from 'lucide-react'; + export default function PrimaryContentSection() { const contents = [ { title: 'AI가 만든 예문·동의어로 맥락 학습', - subtitle: `AI가 단어의 의미를 분석해 - 자연스러운 예문과 함께 - 유의어·반의어·동의어까지 - 한 번에 이해할 수 있도록 제공합니다.`, + features: [ + '자연스러운 예문으로 단어 이해', + '유의어·반의어·동의어 한눈에 파악', + '맥락 속에서 실제 사용법 학습', + ], }, { title: 'AI 퀴즈로 바로 복습', - subtitle: `AI가 다양한 유형의 - 10가지 퀴즈를 자동 생성해 - 실제 시험처럼 반복 학습할 수 있습니다.`, + features: [ + '다양한 유형의 퀴즈 자동 생성', + '실제 시험처럼 반복 학습', + '즉시 피드백으로 완벽 이해', + ], }, { title: '학습 결과를 한눈에', - subtitle: `잘 외워지지 않는 단어는 - 북마크로 저장하고, - 퀴즈 결과와 오답 내역은 - 마이페이지에서 한눈에 확인하세요.`, + features: [ + '잘 외워지지 않는 단어 자동 체크', + '퀴즈 결과와 오답 내역 확인', + '마이페이지에서 한눈에 확인', + ], }, ]; @@ -29,15 +35,27 @@ export default function PrimaryContentSection() {
{contents.map((content, index) => ( - + - + Step {index + 1} - {content.title} +

{content.title}

- {content.subtitle} + +
    + {content.features.map((item, index) => ( +
  • + + {item} +
  • + ))} +
+
))}