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
98 changes: 94 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-navigation-menu": "^1.2.13",
"@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.5",
"@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-tooltip": "^1.2.7",
Expand Down
20 changes: 5 additions & 15 deletions src/app/quiz/[level]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ClientQuizPage from '@/features/quiz/ClientQuizPage';
import Link from 'next/link';
import EmptyContent from '@/features/empty/EmptyContent';

type Props = {
params: Promise<{
Expand All @@ -13,20 +13,10 @@ export default async function QuizLevelPage({ params }: Props) {
// 4급, 5급, 6급은 아직 데이터가 없음
if (['4', '5', '6'].includes(level)) {
return (
<div className="flex flex-col justify-center items-center h-screen gap-4">
<div className="text-xl text-center">
{level}급 퀴즈를 준비 중이에요. 조금만 기다려주세요😅
</div>
<div className="text-gray-600 text-center">
현재 1급, 2급, 3급 퀴즈만 이용 가능합니다.
</div>
<Link
href="/"
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700"
>
홈으로 돌아가기
</Link>
</div>
<EmptyContent
title="퀴즈가 준비되지 않았어요."
content={`${level}급 퀴즈를 준비 중이에요. 조금만 기다려주세요😅`}
/>
);
}

Expand Down
17 changes: 5 additions & 12 deletions src/app/word/[level]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import Link from 'next/link';
import { createClient } from '@/lib/supabase/server';
import ChallengeButton from '@/features/word/ChallengeButton';
import ErrorFallback from '@/components/ErrorFallback';
import ClientWordList from '@/features/word/ClientWordList';
import EmptyContent from '@/features/empty/EmptyContent';

type Props = {
params: Promise<{
Expand All @@ -28,17 +28,10 @@ export default async function WordPage({ params }: Props) {

if (words.length < 1) {
return (
<div className="flex flex-col justify-center items-center min-h-screen gap-4">
<div className="text-xl">
{level}급 단어를 준비 중 이에요. 조금만 기다려주세요😅
</div>
<Link
href="/"
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700"
>
홈으로 돌아가기
</Link>
</div>
<EmptyContent
title="단어가 준비되지 않았어요."
content={`${level}급 단어를 준비 중이에요. 조금만 기다려주세요😅`}
/>
);
}

Expand Down
104 changes: 104 additions & 0 deletions src/components/ui/empty.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

function Empty({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty"
className={cn(
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12",
className
)}
{...props}
/>
)
}

function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-header"
className={cn(
"flex max-w-sm flex-col items-center gap-2 text-center",
className
)}
{...props}
/>
)
}

const emptyMediaVariants = cva(
"flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-transparent",
icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6",
},
},
defaultVariants: {
variant: "default",
},
}
)

function EmptyMedia({
className,
variant = "default",
...props
}: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
return (
<div
data-slot="empty-icon"
data-variant={variant}
className={cn(emptyMediaVariants({ variant, className }))}
{...props}
/>
)
}

function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-title"
className={cn("text-lg font-medium tracking-tight", className)}
{...props}
/>
)
}

function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<div
data-slot="empty-description"
className={cn(
"text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4",
className
)}
{...props}
/>
)
}

function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="empty-content"
className={cn(
"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
className
)}
{...props}
/>
)
}

export {
Empty,
EmptyHeader,
EmptyTitle,
EmptyDescription,
EmptyContent,
EmptyMedia,
}
43 changes: 43 additions & 0 deletions src/features/empty/EmptyContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import Link from 'next/link';

import {
Empty,
EmptyDescription,
EmptyHeader,
EmptyMedia,
EmptyTitle,
} from '@/components/ui/empty';
import { Button } from '@/components/ui/button';

import { ArrowUpRightIcon } from 'lucide-react';
import { Hourglass } from 'lucide-react';

export default function EmptyContent({
title,
content,
}: {
title: string;
content: string;
}) {
return (
<Empty>
<EmptyHeader>
<EmptyMedia variant="icon">
<Hourglass />
</EmptyMedia>
<EmptyTitle>{title}</EmptyTitle>
<EmptyDescription>{content}</EmptyDescription>
</EmptyHeader>
<Button
variant="link"
asChild
className="text-muted-foreground"
size="sm"
>
<Link href="/">
홈으로 돌아가기 <ArrowUpRightIcon />
</Link>
</Button>
</Empty>
);
}
1 change: 0 additions & 1 deletion src/features/word/ClientWordDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,6 @@ export default function ClientWordDetail({
</div>
) : (
<div className="bg-gray-50 rounded-lg p-6 text-center">
<div className="text-gray-400 mb-2">🔄</div>
<p className="text-gray-600">
반의어가 준비되지 않았어요
</p>
Expand Down
Loading