UI 수정:화면 수정
This commit is contained in:
237
frontend/src/app/demo/auth-findid/page.tsx
Normal file
237
frontend/src/app/demo/auth-findid/page.tsx
Normal file
@@ -0,0 +1,237 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import Image from "next/image";
|
||||
import { User, Mail, ArrowLeft } from "lucide-react";
|
||||
|
||||
// 시안 1: 현재 디자인
|
||||
function FindIdDesign1() {
|
||||
return (
|
||||
<div className="grid min-h-[600px] lg:grid-cols-2 border rounded-lg overflow-hidden">
|
||||
<div className="bg-white relative hidden lg:flex items-center justify-center">
|
||||
<Image src="/logo-graphic.svg" alt="로고" fill className="object-contain p-16" priority />
|
||||
</div>
|
||||
<div className="flex flex-col p-6 md:p-10 bg-white">
|
||||
<div className="flex flex-1 items-center justify-center lg:justify-start lg:pl-16">
|
||||
<div className="w-full max-w-[320px] lg:max-w-sm">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col items-center gap-1 text-center mb-2">
|
||||
<h1 className="text-2xl font-bold">아이디 찾기</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
가입 시 등록한 이름과 이메일을 입력해주세요
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">이름</label>
|
||||
<Input placeholder="홍길동" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">이메일</label>
|
||||
<Input type="email" placeholder="example@email.com" />
|
||||
<p className="text-xs text-gray-500">가입 시 등록한 이메일 주소를 입력해주세요</p>
|
||||
</div>
|
||||
<Button className="w-full">인증번호 발송</Button>
|
||||
<Button variant="outline" className="w-full border-2 border-primary text-primary">로그인</Button>
|
||||
<div className="relative my-2">
|
||||
<div className="absolute inset-0 flex items-center"><span className="w-full border-t" /></div>
|
||||
<div className="relative flex justify-center text-xs"><span className="bg-white px-2 text-gray-500">계정이 없으신가요?</span></div>
|
||||
</div>
|
||||
<Button variant="outline" className="w-full border-2 border-primary text-primary">회원가입</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 시안 2: 아이콘 + 간결한 레이아웃
|
||||
function FindIdDesign2() {
|
||||
return (
|
||||
<div className="grid min-h-[600px] lg:grid-cols-2 border rounded-lg overflow-hidden">
|
||||
<div className="bg-white relative hidden lg:flex items-center justify-center">
|
||||
<Image src="/logo-graphic.svg" alt="로고" fill className="object-contain p-16" priority />
|
||||
</div>
|
||||
<div className="flex flex-col p-6 md:p-10 bg-white">
|
||||
<div className="flex flex-1 items-center justify-center lg:justify-start lg:pl-16">
|
||||
<div className="w-full max-w-[320px] lg:max-w-sm">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col items-center gap-1 text-center mb-2">
|
||||
<h1 className="text-2xl font-bold">아이디 찾기</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
가입 시 등록한 정보로 아이디를 찾을 수 있습니다
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">이름</label>
|
||||
<div className="relative">
|
||||
<User className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
<Input placeholder="이름을 입력하세요" className="pl-10 h-11" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">이메일</label>
|
||||
<div className="relative">
|
||||
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
<Input type="email" placeholder="이메일을 입력하세요" className="pl-10 h-11" />
|
||||
</div>
|
||||
</div>
|
||||
<Button className="w-full h-11">인증번호 발송</Button>
|
||||
<div className="relative my-2">
|
||||
<div className="absolute inset-0 flex items-center"><span className="w-full border-t" /></div>
|
||||
<div className="relative flex justify-center text-xs"><span className="bg-white px-2 text-gray-500">또는</span></div>
|
||||
</div>
|
||||
<Button variant="outline" className="w-full h-11 border-2 border-primary text-primary">로그인으로 돌아가기</Button>
|
||||
<div className="text-center">
|
||||
<a href="#" className="text-sm text-gray-500 hover:text-primary">비밀번호를 잊으셨나요?</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 시안 3: 뒤로가기 버튼 + 깔끔한 구조
|
||||
function FindIdDesign3() {
|
||||
return (
|
||||
<div className="grid min-h-[600px] lg:grid-cols-2 border rounded-lg overflow-hidden">
|
||||
<div className="bg-white relative hidden lg:flex items-center justify-center">
|
||||
<Image src="/logo-graphic.svg" alt="로고" fill className="object-contain p-16" priority />
|
||||
</div>
|
||||
<div className="flex flex-col p-6 md:p-10 bg-white">
|
||||
<div className="flex flex-1 items-center justify-center lg:justify-start lg:pl-16">
|
||||
<div className="w-full max-w-[320px] lg:max-w-sm">
|
||||
<div className="flex flex-col gap-4">
|
||||
<a href="#" className="flex items-center gap-1 text-sm text-gray-500 hover:text-primary mb-2">
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
로그인으로 돌아가기
|
||||
</a>
|
||||
<div className="flex flex-col gap-1 mb-2">
|
||||
<h1 className="text-2xl font-bold">아이디 찾기</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
가입 시 등록한 이름과 이메일을 입력해주세요
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">이름</label>
|
||||
<Input placeholder="이름을 입력하세요" className="h-11" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">이메일</label>
|
||||
<Input type="email" placeholder="이메일을 입력하세요" className="h-11" />
|
||||
</div>
|
||||
<Button className="w-full h-11 mt-2">인증번호 발송</Button>
|
||||
<div className="flex items-center justify-center gap-4 text-sm text-gray-500 mt-2">
|
||||
<a href="#" className="hover:text-primary">비밀번호 찾기</a>
|
||||
<span>|</span>
|
||||
<a href="#" className="hover:text-primary">회원가입</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 시안 4: 로그인과 통일된 스타일 (추천)
|
||||
function FindIdDesign4() {
|
||||
return (
|
||||
<div className="grid min-h-[600px] lg:grid-cols-2 border rounded-lg overflow-hidden">
|
||||
<div className="bg-white relative hidden lg:flex items-center justify-center">
|
||||
<Image src="/logo-graphic.svg" alt="로고" fill className="object-contain p-16" priority />
|
||||
</div>
|
||||
<div className="flex flex-col p-6 md:p-10 bg-white">
|
||||
<div className="flex flex-1 items-center justify-center lg:justify-start lg:pl-16">
|
||||
<div className="w-full max-w-[320px] lg:max-w-sm">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col items-center gap-1 text-center mb-2">
|
||||
<h1 className="text-2xl font-bold">아이디 찾기</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
가입 시 등록한 정보를 입력해주세요
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">이름</label>
|
||||
<Input placeholder="이름을 입력하세요" className="h-11" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm font-medium">이메일</label>
|
||||
<a href="#" className="text-xs text-primary hover:underline">비밀번호 찾기</a>
|
||||
</div>
|
||||
<Input type="email" placeholder="이메일을 입력하세요" className="h-11" />
|
||||
</div>
|
||||
<Button className="w-full h-11">인증번호 발송</Button>
|
||||
<div className="relative my-2">
|
||||
<div className="absolute inset-0 flex items-center"><span className="w-full border-t" /></div>
|
||||
<div className="relative flex justify-center text-xs"><span className="bg-white px-2 text-gray-500">또는</span></div>
|
||||
</div>
|
||||
<Button variant="outline" className="w-full h-11 border-2 border-primary text-primary">로그인</Button>
|
||||
<div className="text-center">
|
||||
<a href="#" className="text-sm text-gray-500 hover:text-primary">계정이 없으신가요? 회원가입</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FindIdDemo() {
|
||||
const designs = [
|
||||
{ id: "current", name: "현재", description: "현재 적용된 디자인", features: ["기존 레이아웃"], component: FindIdDesign1 },
|
||||
{ id: "icon", name: "시안 2", description: "아이콘 + 간결한 레이아웃", features: ["입력 필드 아이콘", "간결한 하단 링크"], component: FindIdDesign2 },
|
||||
{ id: "back", name: "시안 3", description: "뒤로가기 버튼 + 좌측 정렬 제목", features: ["뒤로가기 버튼", "좌측 정렬"], component: FindIdDesign3 },
|
||||
{ id: "unified", name: "시안 4", description: "로그인과 통일된 스타일 (추천)", features: ["로그인 스타일 통일", "비밀번호 찾기 위치"], component: FindIdDesign4 },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 p-8">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900">아이디 찾기 페이지 디자인 시안</h1>
|
||||
<p className="text-gray-600 mt-2">각 탭을 클릭하여 다른 디자인 시안을 비교해보세요</p>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="current" className="space-y-6">
|
||||
<TabsList className="grid grid-cols-4 w-full h-auto p-1">
|
||||
{designs.map((design) => (
|
||||
<TabsTrigger key={design.id} value={design.id} className="py-3 text-xs sm:text-sm data-[state=active]:bg-primary data-[state=active]:text-white">
|
||||
{design.name}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
|
||||
{designs.map((design) => (
|
||||
<TabsContent key={design.id} value={design.id} className="space-y-4">
|
||||
<div className="bg-white p-4 rounded-lg border">
|
||||
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">{design.name}: {design.description}</h2>
|
||||
</div>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{design.features.map((feature, idx) => (
|
||||
<Badge key={idx} variant="secondary">{feature}</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white p-4 rounded-lg border">
|
||||
<design.component />
|
||||
</div>
|
||||
</TabsContent>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
237
frontend/src/app/demo/auth-findpw/page.tsx
Normal file
237
frontend/src/app/demo/auth-findpw/page.tsx
Normal file
@@ -0,0 +1,237 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import Image from "next/image";
|
||||
import { User, Mail, ArrowLeft, Eye, EyeOff } from "lucide-react";
|
||||
|
||||
// 시안 1: 현재 디자인
|
||||
function FindPwDesign1() {
|
||||
return (
|
||||
<div className="grid min-h-[600px] lg:grid-cols-2 border rounded-lg overflow-hidden">
|
||||
<div className="bg-white relative hidden lg:flex items-center justify-center">
|
||||
<Image src="/logo-graphic.svg" alt="로고" fill className="object-contain p-16" priority />
|
||||
</div>
|
||||
<div className="flex flex-col p-6 md:p-10 bg-white">
|
||||
<div className="flex flex-1 items-center justify-center lg:justify-start lg:pl-16">
|
||||
<div className="w-full max-w-[320px] lg:max-w-sm">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col items-center gap-1 text-center mb-2">
|
||||
<h1 className="text-2xl font-bold">비밀번호 찾기</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
아이디와 이메일을 입력해주세요
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">아이디</label>
|
||||
<Input placeholder="아이디를 입력하세요" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">이메일</label>
|
||||
<Input type="email" placeholder="이메일 주소를 입력해주세요" />
|
||||
<p className="text-xs text-gray-500">가입 시 등록한 이메일 주소를 입력해주세요</p>
|
||||
</div>
|
||||
<Button className="w-full">인증번호 발송</Button>
|
||||
<Button variant="outline" className="w-full border-2 border-primary text-primary">로그인</Button>
|
||||
<div className="relative my-2">
|
||||
<div className="absolute inset-0 flex items-center"><span className="w-full border-t" /></div>
|
||||
<div className="relative flex justify-center text-xs"><span className="bg-white px-2 text-gray-500">계정이 없으신가요?</span></div>
|
||||
</div>
|
||||
<Button variant="outline" className="w-full border-2 border-primary text-primary">회원가입</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 시안 2: 아이콘 + 간결한 레이아웃
|
||||
function FindPwDesign2() {
|
||||
return (
|
||||
<div className="grid min-h-[600px] lg:grid-cols-2 border rounded-lg overflow-hidden">
|
||||
<div className="bg-white relative hidden lg:flex items-center justify-center">
|
||||
<Image src="/logo-graphic.svg" alt="로고" fill className="object-contain p-16" priority />
|
||||
</div>
|
||||
<div className="flex flex-col p-6 md:p-10 bg-white">
|
||||
<div className="flex flex-1 items-center justify-center lg:justify-start lg:pl-16">
|
||||
<div className="w-full max-w-[320px] lg:max-w-sm">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col items-center gap-1 text-center mb-2">
|
||||
<h1 className="text-2xl font-bold">비밀번호 찾기</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
등록된 정보로 비밀번호를 재설정할 수 있습니다
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">아이디</label>
|
||||
<div className="relative">
|
||||
<User className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
<Input placeholder="아이디를 입력하세요" className="pl-10 h-11" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">이메일</label>
|
||||
<div className="relative">
|
||||
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
<Input type="email" placeholder="이메일을 입력하세요" className="pl-10 h-11" />
|
||||
</div>
|
||||
</div>
|
||||
<Button className="w-full h-11">인증번호 발송</Button>
|
||||
<div className="relative my-2">
|
||||
<div className="absolute inset-0 flex items-center"><span className="w-full border-t" /></div>
|
||||
<div className="relative flex justify-center text-xs"><span className="bg-white px-2 text-gray-500">또는</span></div>
|
||||
</div>
|
||||
<Button variant="outline" className="w-full h-11 border-2 border-primary text-primary">로그인으로 돌아가기</Button>
|
||||
<div className="text-center">
|
||||
<a href="#" className="text-sm text-gray-500 hover:text-primary">아이디를 잊으셨나요?</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 시안 3: 뒤로가기 버튼 + 좌측 정렬
|
||||
function FindPwDesign3() {
|
||||
return (
|
||||
<div className="grid min-h-[600px] lg:grid-cols-2 border rounded-lg overflow-hidden">
|
||||
<div className="bg-white relative hidden lg:flex items-center justify-center">
|
||||
<Image src="/logo-graphic.svg" alt="로고" fill className="object-contain p-16" priority />
|
||||
</div>
|
||||
<div className="flex flex-col p-6 md:p-10 bg-white">
|
||||
<div className="flex flex-1 items-center justify-center lg:justify-start lg:pl-16">
|
||||
<div className="w-full max-w-[320px] lg:max-w-sm">
|
||||
<div className="flex flex-col gap-4">
|
||||
<a href="#" className="flex items-center gap-1 text-sm text-gray-500 hover:text-primary mb-2">
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
로그인으로 돌아가기
|
||||
</a>
|
||||
<div className="flex flex-col gap-1 mb-2">
|
||||
<h1 className="text-2xl font-bold">비밀번호 찾기</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
아이디와 이메일을 입력해주세요
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">아이디</label>
|
||||
<Input placeholder="아이디를 입력하세요" className="h-11" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">이메일</label>
|
||||
<Input type="email" placeholder="이메일을 입력하세요" className="h-11" />
|
||||
</div>
|
||||
<Button className="w-full h-11 mt-2">인증번호 발송</Button>
|
||||
<div className="flex items-center justify-center gap-4 text-sm text-gray-500 mt-2">
|
||||
<a href="#" className="hover:text-primary">아이디 찾기</a>
|
||||
<span>|</span>
|
||||
<a href="#" className="hover:text-primary">회원가입</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 시안 4: 로그인과 통일된 스타일 (추천)
|
||||
function FindPwDesign4() {
|
||||
return (
|
||||
<div className="grid min-h-[600px] lg:grid-cols-2 border rounded-lg overflow-hidden">
|
||||
<div className="bg-white relative hidden lg:flex items-center justify-center">
|
||||
<Image src="/logo-graphic.svg" alt="로고" fill className="object-contain p-16" priority />
|
||||
</div>
|
||||
<div className="flex flex-col p-6 md:p-10 bg-white">
|
||||
<div className="flex flex-1 items-center justify-center lg:justify-start lg:pl-16">
|
||||
<div className="w-full max-w-[320px] lg:max-w-sm">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col items-center gap-1 text-center mb-2">
|
||||
<h1 className="text-2xl font-bold">비밀번호 찾기</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
등록된 정보를 입력해주세요
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">아이디</label>
|
||||
<Input placeholder="아이디를 입력하세요" className="h-11" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm font-medium">이메일</label>
|
||||
<a href="#" className="text-xs text-primary hover:underline">아이디 찾기</a>
|
||||
</div>
|
||||
<Input type="email" placeholder="이메일을 입력하세요" className="h-11" />
|
||||
</div>
|
||||
<Button className="w-full h-11">인증번호 발송</Button>
|
||||
<div className="relative my-2">
|
||||
<div className="absolute inset-0 flex items-center"><span className="w-full border-t" /></div>
|
||||
<div className="relative flex justify-center text-xs"><span className="bg-white px-2 text-gray-500">또는</span></div>
|
||||
</div>
|
||||
<Button variant="outline" className="w-full h-11 border-2 border-primary text-primary">로그인</Button>
|
||||
<div className="text-center">
|
||||
<a href="#" className="text-sm text-gray-500 hover:text-primary">계정이 없으신가요? 회원가입</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function FindPwDemo() {
|
||||
const designs = [
|
||||
{ id: "current", name: "현재", description: "현재 적용된 디자인", features: ["기존 레이아웃"], component: FindPwDesign1 },
|
||||
{ id: "icon", name: "시안 2", description: "아이콘 + 간결한 레이아웃", features: ["입력 필드 아이콘", "간결한 하단 링크"], component: FindPwDesign2 },
|
||||
{ id: "back", name: "시안 3", description: "뒤로가기 버튼 + 좌측 정렬 제목", features: ["뒤로가기 버튼", "좌측 정렬"], component: FindPwDesign3 },
|
||||
{ id: "unified", name: "시안 4", description: "로그인과 통일된 스타일 (추천)", features: ["로그인 스타일 통일", "아이디 찾기 위치"], component: FindPwDesign4 },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 p-8">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900">비밀번호 찾기 페이지 디자인 시안</h1>
|
||||
<p className="text-gray-600 mt-2">각 탭을 클릭하여 다른 디자인 시안을 비교해보세요</p>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="current" className="space-y-6">
|
||||
<TabsList className="grid grid-cols-4 w-full h-auto p-1">
|
||||
{designs.map((design) => (
|
||||
<TabsTrigger key={design.id} value={design.id} className="py-3 text-xs sm:text-sm data-[state=active]:bg-primary data-[state=active]:text-white">
|
||||
{design.name}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
|
||||
{designs.map((design) => (
|
||||
<TabsContent key={design.id} value={design.id} className="space-y-4">
|
||||
<div className="bg-white p-4 rounded-lg border">
|
||||
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">{design.name}: {design.description}</h2>
|
||||
</div>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{design.features.map((feature, idx) => (
|
||||
<Badge key={idx} variant="secondary">{feature}</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white p-4 rounded-lg border">
|
||||
<design.component />
|
||||
</div>
|
||||
</TabsContent>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
549
frontend/src/app/demo/auth-pages/page.tsx
Normal file
549
frontend/src/app/demo/auth-pages/page.tsx
Normal file
@@ -0,0 +1,549 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import Image from "next/image";
|
||||
import { LogIn, Eye, EyeOff, User, Lock } from "lucide-react";
|
||||
|
||||
// 시안 1: 현재 디자인
|
||||
function LoginDesign1() {
|
||||
return (
|
||||
<div className="grid min-h-[600px] lg:grid-cols-2 border rounded-lg overflow-hidden">
|
||||
<div className="bg-white relative hidden lg:flex items-center justify-center">
|
||||
<Image
|
||||
src="/logo-graphic.svg"
|
||||
alt="로고"
|
||||
fill
|
||||
className="object-contain p-16"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col p-6 md:p-10 bg-white">
|
||||
<div className="flex flex-1 items-center justify-center lg:justify-start lg:pl-16">
|
||||
<div className="w-full max-w-[320px] lg:max-w-sm">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col items-center gap-1 text-center mb-2">
|
||||
<h1 className="text-2xl font-bold">로그인</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
한우 유전능력 컨설팅 서비스에 오신 것을 환영합니다
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">아이디</label>
|
||||
<Input placeholder="아이디를 입력하세요" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">비밀번호</label>
|
||||
<Input type="password" placeholder="비밀번호를 입력하세요" />
|
||||
</div>
|
||||
<Button className="w-full">로그인</Button>
|
||||
<div className="relative my-2">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="bg-white px-2 text-muted-foreground">계정이 없으신가요?</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" className="w-full border-2 border-primary text-primary">
|
||||
회원가입
|
||||
</Button>
|
||||
<div className="text-center text-sm">
|
||||
<a href="#" className="hover:underline">아이디 찾기</a>
|
||||
{" | "}
|
||||
<a href="#" className="hover:underline">비밀번호 찾기</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 시안 2: 현재 + 비밀번호 토글 + 아이콘
|
||||
function LoginDesign2() {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="grid min-h-[600px] lg:grid-cols-2 border rounded-lg overflow-hidden">
|
||||
<div className="bg-white relative hidden lg:flex items-center justify-center">
|
||||
<Image
|
||||
src="/logo-graphic.svg"
|
||||
alt="로고"
|
||||
fill
|
||||
className="object-contain p-16"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col p-6 md:p-10 bg-white">
|
||||
<div className="flex flex-1 items-center justify-center lg:justify-start lg:pl-16">
|
||||
<div className="w-full max-w-[320px] lg:max-w-sm">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col items-center gap-1 text-center mb-2">
|
||||
<h1 className="text-2xl font-bold">로그인</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
한우 유전능력 컨설팅 서비스에 오신 것을 환영합니다
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">아이디</label>
|
||||
<div className="relative">
|
||||
<User className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
<Input placeholder="아이디를 입력하세요" className="pl-10" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">비밀번호</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
|
||||
<Input
|
||||
type={showPassword ? "text" : "password"}
|
||||
placeholder="비밀번호를 입력하세요"
|
||||
className="pl-10 pr-10"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Button className="w-full">
|
||||
<LogIn className="w-4 h-4 mr-2" />
|
||||
로그인
|
||||
</Button>
|
||||
<div className="relative my-2">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="bg-white px-2 text-muted-foreground">계정이 없으신가요?</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" className="w-full border-2 border-primary text-primary">
|
||||
회원가입
|
||||
</Button>
|
||||
<div className="text-center text-sm">
|
||||
<a href="#" className="hover:underline">아이디 찾기</a>
|
||||
{" | "}
|
||||
<a href="#" className="hover:underline">비밀번호 찾기</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 시안 3: 현재 + 더 큰 입력 필드 + 부드러운 그림자
|
||||
function LoginDesign3() {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="grid min-h-[600px] lg:grid-cols-2 border rounded-lg overflow-hidden">
|
||||
<div className="bg-gradient-to-br from-slate-50 to-slate-100 relative hidden lg:flex items-center justify-center">
|
||||
<Image
|
||||
src="/logo-graphic.svg"
|
||||
alt="로고"
|
||||
fill
|
||||
className="object-contain p-16"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col p-6 md:p-10 bg-white">
|
||||
<div className="flex flex-1 items-center justify-center lg:justify-start lg:pl-12">
|
||||
<div className="w-full max-w-[360px]">
|
||||
<div className="flex flex-col gap-5">
|
||||
<div className="flex flex-col items-center gap-2 text-center mb-4">
|
||||
<h1 className="text-2xl font-bold text-gray-900">로그인</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
한우 유전능력 컨설팅 서비스에 오신 것을 환영합니다
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-700">아이디</label>
|
||||
<Input
|
||||
placeholder="아이디를 입력하세요"
|
||||
className="h-12 text-base shadow-sm border-gray-200 focus:border-primary focus:ring-primary"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-gray-700">비밀번호</label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showPassword ? "text" : "password"}
|
||||
placeholder="비밀번호를 입력하세요"
|
||||
className="h-12 text-base pr-10 shadow-sm border-gray-200 focus:border-primary focus:ring-primary"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Button className="w-full h-12 text-base shadow-md hover:shadow-lg transition-shadow">
|
||||
로그인
|
||||
</Button>
|
||||
<div className="relative my-2">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t border-gray-200" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs">
|
||||
<span className="bg-white px-3 text-gray-500">계정이 없으신가요?</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" className="w-full h-12 text-base border-2 border-primary text-primary hover:bg-primary hover:text-white transition-colors">
|
||||
회원가입
|
||||
</Button>
|
||||
<div className="text-center text-sm text-gray-500">
|
||||
<a href="#" className="hover:text-primary transition-colors">아이디 찾기</a>
|
||||
<span className="mx-2">|</span>
|
||||
<a href="#" className="hover:text-primary transition-colors">비밀번호 찾기</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 시안 4: 현재 + 아이디 저장 + 간결한 링크
|
||||
function LoginDesign4() {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="grid min-h-[600px] lg:grid-cols-2 border rounded-lg overflow-hidden">
|
||||
<div className="bg-white relative hidden lg:flex items-center justify-center">
|
||||
<Image
|
||||
src="/logo-graphic.svg"
|
||||
alt="로고"
|
||||
fill
|
||||
className="object-contain p-16"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col p-6 md:p-10 bg-white">
|
||||
<div className="flex flex-1 items-center justify-center lg:justify-start lg:pl-16">
|
||||
<div className="w-full max-w-[320px] lg:max-w-sm">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col items-center gap-1 text-center mb-2">
|
||||
<h1 className="text-2xl font-bold">로그인</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
한우 유전능력 컨설팅 서비스에 오신 것을 환영합니다
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">아이디</label>
|
||||
<Input placeholder="아이디를 입력하세요" className="h-11" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm font-medium">비밀번호</label>
|
||||
<a href="#" className="text-xs text-primary hover:underline">비밀번호 찾기</a>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showPassword ? "text" : "password"}
|
||||
placeholder="비밀번호를 입력하세요"
|
||||
className="h-11 pr-10"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input type="checkbox" className="rounded border-gray-300 text-primary focus:ring-primary" />
|
||||
<span className="text-sm text-gray-600">아이디 저장</span>
|
||||
</label>
|
||||
<Button className="w-full h-11">로그인</Button>
|
||||
<div className="relative my-2">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="bg-white px-2 text-muted-foreground">또는</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" className="w-full h-11 border-2 border-primary text-primary">
|
||||
회원가입
|
||||
</Button>
|
||||
<div className="text-center">
|
||||
<a href="#" className="text-sm text-gray-500 hover:text-primary">아이디를 잊으셨나요?</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 시안 5: 현재 + 컬러 강조 배경
|
||||
function LoginDesign5() {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="grid min-h-[600px] lg:grid-cols-2 border rounded-lg overflow-hidden">
|
||||
<div className="bg-primary/5 relative hidden lg:flex items-center justify-center">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-primary/10 via-transparent to-primary/5" />
|
||||
<Image
|
||||
src="/logo-graphic.svg"
|
||||
alt="로고"
|
||||
fill
|
||||
className="object-contain p-16"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col p-6 md:p-10 bg-white">
|
||||
<div className="flex flex-1 items-center justify-center lg:justify-start lg:pl-16">
|
||||
<div className="w-full max-w-[320px] lg:max-w-sm">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col items-center gap-1 text-center mb-2">
|
||||
<h1 className="text-2xl font-bold text-primary">로그인</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
한우 유전능력 컨설팅 서비스에 오신 것을 환영합니다
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">아이디</label>
|
||||
<div className="relative">
|
||||
<User className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-primary/60" />
|
||||
<Input
|
||||
placeholder="아이디를 입력하세요"
|
||||
className="pl-10 h-11 border-primary/20 focus:border-primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">비밀번호</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-primary/60" />
|
||||
<Input
|
||||
type={showPassword ? "text" : "password"}
|
||||
placeholder="비밀번호를 입력하세요"
|
||||
className="pl-10 pr-10 h-11 border-primary/20 focus:border-primary"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-primary"
|
||||
>
|
||||
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<Button className="w-full h-11 bg-primary hover:bg-primary/90">
|
||||
<LogIn className="w-4 h-4 mr-2" />
|
||||
로그인
|
||||
</Button>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input type="checkbox" className="rounded border-primary/30 text-primary focus:ring-primary" />
|
||||
<span className="text-gray-600">아이디 저장</span>
|
||||
</label>
|
||||
<a href="#" className="text-primary hover:underline">비밀번호 찾기</a>
|
||||
</div>
|
||||
<div className="relative my-2">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t border-primary/20" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs">
|
||||
<span className="bg-white px-2 text-gray-500">계정이 없으신가요?</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" className="w-full h-11 border-2 border-primary text-primary hover:bg-primary/5">
|
||||
회원가입
|
||||
</Button>
|
||||
<div className="text-center">
|
||||
<a href="#" className="text-sm text-gray-500 hover:text-primary">아이디 찾기</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 시안 6: 현재 + 라운드 스타일
|
||||
function LoginDesign6() {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="grid min-h-[600px] lg:grid-cols-2 border rounded-lg overflow-hidden">
|
||||
<div className="bg-white relative hidden lg:flex items-center justify-center">
|
||||
<Image
|
||||
src="/logo-graphic.svg"
|
||||
alt="로고"
|
||||
fill
|
||||
className="object-contain p-16"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col p-6 md:p-10 bg-slate-50">
|
||||
<div className="flex flex-1 items-center justify-center lg:justify-start lg:pl-12">
|
||||
<div className="w-full max-w-[360px] bg-white p-8 rounded-2xl shadow-lg">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col items-center gap-1 text-center mb-2">
|
||||
<h1 className="text-2xl font-bold">로그인</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
한우 유전능력 컨설팅 서비스
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">아이디</label>
|
||||
<Input
|
||||
placeholder="아이디를 입력하세요"
|
||||
className="h-11 rounded-xl bg-slate-50 border-0 focus:bg-white focus:ring-2 focus:ring-primary/20"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">비밀번호</label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type={showPassword ? "text" : "password"}
|
||||
placeholder="비밀번호를 입력하세요"
|
||||
className="h-11 pr-10 rounded-xl bg-slate-50 border-0 focus:bg-white focus:ring-2 focus:ring-primary/20"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input type="checkbox" className="rounded-md border-gray-300" />
|
||||
<span className="text-gray-600">아이디 저장</span>
|
||||
</label>
|
||||
<a href="#" className="text-primary hover:underline">비밀번호 찾기</a>
|
||||
</div>
|
||||
<Button className="w-full h-11 rounded-xl">로그인</Button>
|
||||
<Button variant="outline" className="w-full h-11 rounded-xl border-2">
|
||||
회원가입
|
||||
</Button>
|
||||
<div className="text-center">
|
||||
<a href="#" className="text-sm text-gray-500 hover:text-primary">아이디 찾기</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function AuthPagesDemo() {
|
||||
const designs = [
|
||||
{
|
||||
id: "current",
|
||||
name: "현재",
|
||||
description: "현재 적용된 디자인",
|
||||
features: ["기존 레이아웃"],
|
||||
component: LoginDesign1
|
||||
},
|
||||
{
|
||||
id: "icon",
|
||||
name: "시안 2",
|
||||
description: "아이콘 + 비밀번호 토글 추가",
|
||||
features: ["입력 필드 아이콘", "비밀번호 보기"],
|
||||
component: LoginDesign2
|
||||
},
|
||||
{
|
||||
id: "large",
|
||||
name: "시안 3",
|
||||
description: "더 큰 입력 필드 + 그림자",
|
||||
features: ["h-12 입력필드", "그림자 효과", "부드러운 배경"],
|
||||
component: LoginDesign3
|
||||
},
|
||||
{
|
||||
id: "save",
|
||||
name: "시안 4",
|
||||
description: "아이디 저장 + 간결한 링크",
|
||||
features: ["아이디 저장", "비밀번호 찾기 위치 변경"],
|
||||
component: LoginDesign4
|
||||
},
|
||||
{
|
||||
id: "color",
|
||||
name: "시안 5",
|
||||
description: "브랜드 컬러 강조",
|
||||
features: ["컬러 배경", "컬러 아이콘", "컬러 제목"],
|
||||
component: LoginDesign5
|
||||
},
|
||||
{
|
||||
id: "round",
|
||||
name: "시안 6",
|
||||
description: "라운드 카드 스타일",
|
||||
features: ["라운드 입력필드", "카드 레이아웃", "부드러운 그림자"],
|
||||
component: LoginDesign6
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 p-8">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900">로그인 페이지 디자인 시안</h1>
|
||||
<p className="text-gray-600 mt-2">
|
||||
현재 디자인 기반 개선안 - 각 탭을 클릭하여 비교해보세요
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="current" className="space-y-6">
|
||||
<TabsList className="grid grid-cols-6 w-full h-auto p-1">
|
||||
{designs.map((design) => (
|
||||
<TabsTrigger
|
||||
key={design.id}
|
||||
value={design.id}
|
||||
className="py-3 text-xs sm:text-sm data-[state=active]:bg-primary data-[state=active]:text-white"
|
||||
>
|
||||
{design.name}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
|
||||
{designs.map((design) => (
|
||||
<TabsContent key={design.id} value={design.id} className="space-y-4">
|
||||
<div className="bg-white p-4 rounded-lg border">
|
||||
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">{design.name}: {design.description}</h2>
|
||||
</div>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{design.features.map((feature, idx) => (
|
||||
<Badge key={idx} variant="secondary">{feature}</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white p-4 rounded-lg border">
|
||||
<design.component />
|
||||
</div>
|
||||
</TabsContent>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
455
frontend/src/app/demo/auth-signup/page.tsx
Normal file
455
frontend/src/app/demo/auth-signup/page.tsx
Normal file
@@ -0,0 +1,455 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import Image from "next/image";
|
||||
import { CheckCircle2, ChevronLeft, ChevronRight } from "lucide-react";
|
||||
|
||||
// 시안 1: 현재 디자인 (3단계 스텝)
|
||||
function SignupDesign1() {
|
||||
const [step, setStep] = useState(1);
|
||||
|
||||
return (
|
||||
<div className="grid min-h-[600px] lg:grid-cols-2 border rounded-lg overflow-hidden">
|
||||
<div className="bg-white relative hidden lg:flex items-center justify-center">
|
||||
<Image src="/logo-graphic.svg" alt="로고" fill className="object-contain p-16" priority />
|
||||
</div>
|
||||
<div className="flex flex-col p-6 md:p-10 bg-white">
|
||||
<div className="flex flex-1 items-center justify-center lg:justify-start lg:pl-16">
|
||||
<div className="w-full max-w-[320px] lg:max-w-sm">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col items-center gap-1 text-center">
|
||||
<h1 className="text-2xl font-bold">회원가입</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{step === 1 && "기본 정보"}
|
||||
{step === 2 && "이메일 인증"}
|
||||
{step === 3 && "추가 정보"}
|
||||
</p>
|
||||
</div>
|
||||
{/* 스텝 인디케이터 */}
|
||||
<div className="flex items-center justify-center gap-2 py-2">
|
||||
{[1, 2, 3].map((s) => (
|
||||
<div key={s} className="flex items-center">
|
||||
<div className={cn(
|
||||
"w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium",
|
||||
step === s ? "bg-primary text-white" : step > s ? "bg-primary/20 text-primary" : "bg-gray-100 text-gray-400"
|
||||
)}>
|
||||
{step > s ? <CheckCircle2 className="w-4 h-4" /> : s}
|
||||
</div>
|
||||
{s < 3 && <div className={cn("w-8 h-0.5 mx-1", step > s ? "bg-primary/20" : "bg-gray-200")} />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{step === 1 && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">회원 유형 *</label>
|
||||
<Select><SelectTrigger><SelectValue placeholder="회원 유형을 선택하세요" /></SelectTrigger>
|
||||
<SelectContent><SelectItem value="FARM">농가</SelectItem><SelectItem value="CNSLT">컨설턴트</SelectItem></SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">아이디 *</label>
|
||||
<Input placeholder="아이디를 입력하세요 (4자 이상)" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">이름 *</label>
|
||||
<Input placeholder="이름을 입력하세요" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{step === 2 && (
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">이메일 *</label>
|
||||
<Input type="email" placeholder="이메일을 입력하세요" />
|
||||
<Button variant="outline" className="w-full">인증번호 발송</Button>
|
||||
</div>
|
||||
)}
|
||||
{step === 3 && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">휴대폰 번호 *</label>
|
||||
<Input placeholder="010-0000-0000" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">비밀번호 *</label>
|
||||
<Input type="password" placeholder="비밀번호 (8자 이상)" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="flex gap-2 pt-2">
|
||||
{step > 1 && (
|
||||
<Button variant="outline" onClick={() => setStep(s => s - 1)} className="flex-1">
|
||||
<ChevronLeft className="w-4 h-4 mr-1" />이전
|
||||
</Button>
|
||||
)}
|
||||
{step < 3 ? (
|
||||
<Button onClick={() => setStep(s => s + 1)} className="flex-1">다음<ChevronRight className="w-4 h-4 ml-1" /></Button>
|
||||
) : (
|
||||
<Button className="flex-1">회원가입</Button>
|
||||
)}
|
||||
</div>
|
||||
<Button variant="outline" className="w-full">로그인으로 돌아가기</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 시안 2: 스텝에 라벨 추가
|
||||
function SignupDesign2() {
|
||||
const [step, setStep] = useState(1);
|
||||
const steps = [{ num: 1, label: "기본정보" }, { num: 2, label: "이메일인증" }, { num: 3, label: "추가정보" }];
|
||||
|
||||
return (
|
||||
<div className="grid min-h-[600px] lg:grid-cols-2 border rounded-lg overflow-hidden">
|
||||
<div className="bg-white relative hidden lg:flex items-center justify-center">
|
||||
<Image src="/logo-graphic.svg" alt="로고" fill className="object-contain p-16" priority />
|
||||
</div>
|
||||
<div className="flex flex-col p-6 md:p-10 bg-white">
|
||||
<div className="flex flex-1 items-center justify-center lg:justify-start lg:pl-16">
|
||||
<div className="w-full max-w-[360px]">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col items-center gap-1 text-center">
|
||||
<h1 className="text-2xl font-bold">회원가입</h1>
|
||||
</div>
|
||||
{/* 스텝 인디케이터 with 라벨 */}
|
||||
<div className="flex items-center justify-between py-4">
|
||||
{steps.map((s, idx) => (
|
||||
<div key={s.num} className="flex flex-col items-center flex-1">
|
||||
<div className="flex items-center w-full">
|
||||
{idx > 0 && <div className={cn("flex-1 h-0.5", step > idx ? "bg-primary" : "bg-gray-200")} />}
|
||||
<div className={cn(
|
||||
"w-10 h-10 rounded-full flex items-center justify-center text-sm font-medium shrink-0",
|
||||
step === s.num ? "bg-primary text-white" : step > s.num ? "bg-primary text-white" : "bg-gray-100 text-gray-400"
|
||||
)}>
|
||||
{step > s.num ? <CheckCircle2 className="w-5 h-5" /> : s.num}
|
||||
</div>
|
||||
{idx < 2 && <div className={cn("flex-1 h-0.5", step > s.num ? "bg-primary" : "bg-gray-200")} />}
|
||||
</div>
|
||||
<span className={cn("text-xs mt-2", step >= s.num ? "text-primary font-medium" : "text-gray-400")}>{s.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{step === 1 && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">회원 유형 *</label>
|
||||
<Select><SelectTrigger className="h-11"><SelectValue placeholder="회원 유형을 선택하세요" /></SelectTrigger>
|
||||
<SelectContent><SelectItem value="FARM">농가</SelectItem><SelectItem value="CNSLT">컨설턴트</SelectItem></SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">아이디 *</label>
|
||||
<Input placeholder="아이디를 입력하세요 (4자 이상)" className="h-11" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">이름 *</label>
|
||||
<Input placeholder="이름을 입력하세요" className="h-11" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{step === 2 && (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">이메일 *</label>
|
||||
<Input type="email" placeholder="이메일을 입력하세요" className="h-11" />
|
||||
</div>
|
||||
<Button variant="outline" className="w-full h-11">인증번호 발송</Button>
|
||||
</div>
|
||||
)}
|
||||
{step === 3 && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">휴대폰 번호 *</label>
|
||||
<Input placeholder="010-0000-0000" className="h-11" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">비밀번호 *</label>
|
||||
<Input type="password" placeholder="비밀번호 (8자 이상)" className="h-11" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">비밀번호 확인 *</label>
|
||||
<Input type="password" placeholder="비밀번호를 다시 입력하세요" className="h-11" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="flex gap-2 pt-2">
|
||||
{step > 1 && (
|
||||
<Button variant="outline" onClick={() => setStep(s => s - 1)} className="flex-1 h-11">
|
||||
<ChevronLeft className="w-4 h-4 mr-1" />이전
|
||||
</Button>
|
||||
)}
|
||||
{step < 3 ? (
|
||||
<Button onClick={() => setStep(s => s + 1)} className="flex-1 h-11">다음<ChevronRight className="w-4 h-4 ml-1" /></Button>
|
||||
) : (
|
||||
<Button className="flex-1 h-11">회원가입</Button>
|
||||
)}
|
||||
</div>
|
||||
<Button variant="outline" className="w-full h-11">로그인으로 돌아가기</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 시안 3: 프로그레스 바 스타일
|
||||
function SignupDesign3() {
|
||||
const [step, setStep] = useState(1);
|
||||
const progress = ((step - 1) / 2) * 100;
|
||||
|
||||
return (
|
||||
<div className="grid min-h-[600px] lg:grid-cols-2 border rounded-lg overflow-hidden">
|
||||
<div className="bg-white relative hidden lg:flex items-center justify-center">
|
||||
<Image src="/logo-graphic.svg" alt="로고" fill className="object-contain p-16" priority />
|
||||
</div>
|
||||
<div className="flex flex-col p-6 md:p-10 bg-white">
|
||||
<div className="flex flex-1 items-center justify-center lg:justify-start lg:pl-16">
|
||||
<div className="w-full max-w-[320px] lg:max-w-sm">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col items-center gap-1 text-center">
|
||||
<h1 className="text-2xl font-bold">회원가입</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{step === 1 && "기본 정보를 입력해주세요"}
|
||||
{step === 2 && "이메일 인증을 진행해주세요"}
|
||||
{step === 3 && "마지막 단계입니다"}
|
||||
</p>
|
||||
</div>
|
||||
{/* 프로그레스 바 */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between text-xs text-gray-500">
|
||||
<span>단계 {step}/3</span>
|
||||
<span>{Math.round(progress)}% 완료</span>
|
||||
</div>
|
||||
<div className="w-full h-2 bg-gray-100 rounded-full overflow-hidden">
|
||||
<div className="h-full bg-primary transition-all duration-300" style={{ width: `${progress}%` }} />
|
||||
</div>
|
||||
</div>
|
||||
{step === 1 && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">회원 유형 *</label>
|
||||
<Select><SelectTrigger className="h-11"><SelectValue placeholder="회원 유형을 선택하세요" /></SelectTrigger>
|
||||
<SelectContent><SelectItem value="FARM">농가</SelectItem><SelectItem value="CNSLT">컨설턴트</SelectItem></SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">아이디 *</label>
|
||||
<Input placeholder="아이디를 입력하세요" className="h-11" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">이름 *</label>
|
||||
<Input placeholder="이름을 입력하세요" className="h-11" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{step === 2 && (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">이메일 *</label>
|
||||
<Input type="email" placeholder="이메일을 입력하세요" className="h-11" />
|
||||
</div>
|
||||
<Button variant="outline" className="w-full h-11">인증번호 발송</Button>
|
||||
</div>
|
||||
)}
|
||||
{step === 3 && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">휴대폰 번호 *</label>
|
||||
<Input placeholder="010-0000-0000" className="h-11" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">비밀번호 *</label>
|
||||
<Input type="password" placeholder="비밀번호 (8자 이상)" className="h-11" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="flex gap-2 pt-2">
|
||||
{step > 1 && (
|
||||
<Button variant="outline" onClick={() => setStep(s => s - 1)} className="flex-1 h-11">이전</Button>
|
||||
)}
|
||||
{step < 3 ? (
|
||||
<Button onClick={() => setStep(s => s + 1)} className="flex-1 h-11">다음</Button>
|
||||
) : (
|
||||
<Button className="flex-1 h-11">회원가입 완료</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<a href="#" className="text-sm text-gray-500 hover:text-primary">이미 계정이 있으신가요? 로그인</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 시안 4: 현재 + 개선 (추천)
|
||||
function SignupDesign4() {
|
||||
const [step, setStep] = useState(1);
|
||||
|
||||
return (
|
||||
<div className="grid min-h-[600px] lg:grid-cols-2 border rounded-lg overflow-hidden">
|
||||
<div className="bg-white relative hidden lg:flex items-center justify-center">
|
||||
<Image src="/logo-graphic.svg" alt="로고" fill className="object-contain p-16" priority />
|
||||
</div>
|
||||
<div className="flex flex-col p-6 md:p-10 bg-white">
|
||||
<div className="flex flex-1 items-center justify-center lg:justify-start lg:pl-16">
|
||||
<div className="w-full max-w-[320px] lg:max-w-sm">
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col items-center gap-1 text-center">
|
||||
<h1 className="text-2xl font-bold">회원가입</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{step === 1 && "기본 정보"}
|
||||
{step === 2 && "이메일 인증"}
|
||||
{step === 3 && "추가 정보"}
|
||||
</p>
|
||||
</div>
|
||||
{/* 스텝 인디케이터 */}
|
||||
<div className="flex items-center justify-center gap-2 py-2">
|
||||
{[1, 2, 3].map((s) => (
|
||||
<div key={s} className="flex items-center">
|
||||
<div className={cn(
|
||||
"w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium transition-colors",
|
||||
step === s ? "bg-primary text-white" : step > s ? "bg-primary/20 text-primary" : "bg-gray-100 text-gray-400"
|
||||
)}>
|
||||
{step > s ? <CheckCircle2 className="w-4 h-4" /> : s}
|
||||
</div>
|
||||
{s < 3 && <div className={cn("w-8 h-0.5 mx-1 transition-colors", step > s ? "bg-primary/20" : "bg-gray-200")} />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{step === 1 && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">회원 유형 *</label>
|
||||
<Select><SelectTrigger className="h-11"><SelectValue placeholder="회원 유형을 선택하세요" /></SelectTrigger>
|
||||
<SelectContent><SelectItem value="FARM">농가</SelectItem><SelectItem value="CNSLT">컨설턴트</SelectItem><SelectItem value="ORGAN">기관</SelectItem></SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">아이디 *</label>
|
||||
<Input placeholder="아이디를 입력하세요 (4자 이상)" className="h-11" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">이름 *</label>
|
||||
<Input placeholder="이름을 입력하세요 (2자 이상)" className="h-11" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{step === 2 && (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">이메일 *</label>
|
||||
<div className="flex gap-2">
|
||||
<Input type="text" placeholder="이메일 아이디" className="h-11 flex-1" />
|
||||
<span className="flex items-center text-gray-400">@</span>
|
||||
<Select><SelectTrigger className="h-11 flex-1"><SelectValue placeholder="선택" /></SelectTrigger>
|
||||
<SelectContent><SelectItem value="gmail.com">gmail.com</SelectItem><SelectItem value="naver.com">naver.com</SelectItem></SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="outline" className="w-full h-11">인증번호 발송</Button>
|
||||
<p className="text-xs text-center text-green-600">✓ 이메일 인증이 완료되면 다음 단계로 진행됩니다</p>
|
||||
</div>
|
||||
)}
|
||||
{step === 3 && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">휴대폰 번호 *</label>
|
||||
<Input placeholder="010-0000-0000" className="h-11" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">비밀번호 *</label>
|
||||
<Input type="password" placeholder="비밀번호를 입력하세요 (8자 이상)" className="h-11" />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">비밀번호 확인 *</label>
|
||||
<Input type="password" placeholder="비밀번호를 다시 입력하세요" className="h-11" />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="flex gap-2 pt-2">
|
||||
{step > 1 && (
|
||||
<Button variant="outline" onClick={() => setStep(s => s - 1)} className="flex-1 h-11">
|
||||
<ChevronLeft className="w-4 h-4 mr-1" />이전
|
||||
</Button>
|
||||
)}
|
||||
{step < 3 ? (
|
||||
<Button onClick={() => setStep(s => s + 1)} className="flex-1 h-11">다음<ChevronRight className="w-4 h-4 ml-1" /></Button>
|
||||
) : (
|
||||
<Button className="flex-1 h-11">회원가입</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="relative my-2">
|
||||
<div className="absolute inset-0 flex items-center"><span className="w-full border-t" /></div>
|
||||
<div className="relative flex justify-center text-xs"><span className="bg-white px-2 text-gray-500">또는</span></div>
|
||||
</div>
|
||||
<Button variant="outline" className="w-full h-11 border-2 border-primary text-primary">로그인</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SignupDemo() {
|
||||
const designs = [
|
||||
{ id: "current", name: "현재", description: "현재 적용된 3단계 스텝", features: ["숫자 인디케이터"], component: SignupDesign1 },
|
||||
{ id: "label", name: "시안 2", description: "스텝에 라벨 추가", features: ["단계별 라벨", "연결선"], component: SignupDesign2 },
|
||||
{ id: "progress", name: "시안 3", description: "프로그레스 바 스타일", features: ["진행률 바", "퍼센트 표시"], component: SignupDesign3 },
|
||||
{ id: "improved", name: "시안 4", description: "현재 + 개선 (추천)", features: ["h-11 입력필드", "로그인 통일 스타일"], component: SignupDesign4 },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 p-8">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900">회원가입 페이지 디자인 시안</h1>
|
||||
<p className="text-gray-600 mt-2">각 탭을 클릭하여 다른 디자인 시안을 비교해보세요</p>
|
||||
</div>
|
||||
|
||||
<Tabs defaultValue="current" className="space-y-6">
|
||||
<TabsList className="grid grid-cols-4 w-full h-auto p-1">
|
||||
{designs.map((design) => (
|
||||
<TabsTrigger key={design.id} value={design.id} className="py-3 text-xs sm:text-sm data-[state=active]:bg-primary data-[state=active]:text-white">
|
||||
{design.name}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
|
||||
{designs.map((design) => (
|
||||
<TabsContent key={design.id} value={design.id} className="space-y-4">
|
||||
<div className="bg-white p-4 rounded-lg border">
|
||||
<div className="flex items-center justify-between flex-wrap gap-2">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">{design.name}: {design.description}</h2>
|
||||
</div>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{design.features.map((feature, idx) => (
|
||||
<Badge key={idx} variant="secondary">{feature}</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white p-4 rounded-lg border">
|
||||
<design.component />
|
||||
</div>
|
||||
</TabsContent>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
331
frontend/src/app/demo/email-domain/page.tsx
Normal file
331
frontend/src/app/demo/email-domain/page.tsx
Normal file
@@ -0,0 +1,331 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
|
||||
const emailDomains = [
|
||||
'gmail.com',
|
||||
'naver.com',
|
||||
'daum.net',
|
||||
'hanmail.net',
|
||||
'nate.com',
|
||||
'kakao.com',
|
||||
'직접입력',
|
||||
];
|
||||
|
||||
// 시안 1: 직접입력 시 별도 행에 입력창
|
||||
function EmailDomain1() {
|
||||
const [emailId, setEmailId] = useState('');
|
||||
const [emailDomain, setEmailDomain] = useState('');
|
||||
const [customDomain, setCustomDomain] = useState('');
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-semibold">시안 1: 직접입력 시 별도 행</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex gap-2 items-center">
|
||||
<Input
|
||||
placeholder="이메일"
|
||||
value={emailId}
|
||||
onChange={(e) => setEmailId(e.target.value)}
|
||||
className="flex-1 h-11"
|
||||
/>
|
||||
<span className="text-muted-foreground">@</span>
|
||||
<Select value={emailDomain} onValueChange={setEmailDomain}>
|
||||
<SelectTrigger className="flex-1 h-11">
|
||||
<SelectValue placeholder="도메인 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{emailDomains.map((domain) => (
|
||||
<SelectItem key={domain} value={domain}>
|
||||
{domain}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{emailDomain === '직접입력' && (
|
||||
<Input
|
||||
placeholder="도메인을 입력하세요 (예: company.com)"
|
||||
value={customDomain}
|
||||
onChange={(e) => setCustomDomain(e.target.value)}
|
||||
className="h-11"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
결과: {emailId}@{emailDomain === '직접입력' ? customDomain : emailDomain}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 시안 2: 직접입력 시 드롭다운 자리에 인풋 + 옆에 드롭다운 버튼
|
||||
function EmailDomain2() {
|
||||
const [emailId, setEmailId] = useState('');
|
||||
const [emailDomain, setEmailDomain] = useState('');
|
||||
const [customDomain, setCustomDomain] = useState('');
|
||||
const [isSelectOpen, setIsSelectOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-semibold">시안 2: 인풋 + 별도 드롭다운 버튼</h3>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Input
|
||||
placeholder="이메일"
|
||||
value={emailId}
|
||||
onChange={(e) => setEmailId(e.target.value)}
|
||||
className="flex-1 h-11"
|
||||
/>
|
||||
<span className="text-muted-foreground">@</span>
|
||||
{emailDomain === '직접입력' ? (
|
||||
<div className="flex flex-1 gap-1">
|
||||
<Input
|
||||
placeholder="도메인 입력"
|
||||
value={customDomain}
|
||||
onChange={(e) => setCustomDomain(e.target.value)}
|
||||
className="flex-1 h-11"
|
||||
/>
|
||||
<Select value={emailDomain} onValueChange={(v) => {
|
||||
setEmailDomain(v);
|
||||
if (v !== '직접입력') setCustomDomain('');
|
||||
}}>
|
||||
<SelectTrigger className="w-11 h-11 px-0 justify-center">
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{emailDomains.map((domain) => (
|
||||
<SelectItem key={domain} value={domain}>
|
||||
{domain}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
) : (
|
||||
<Select value={emailDomain} onValueChange={setEmailDomain}>
|
||||
<SelectTrigger className="flex-1 h-11">
|
||||
<SelectValue placeholder="도메인 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{emailDomains.map((domain) => (
|
||||
<SelectItem key={domain} value={domain}>
|
||||
{domain}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
결과: {emailId}@{emailDomain === '직접입력' ? customDomain : emailDomain}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 시안 3: Combobox 스타일 - 인풋이면서 드롭다운
|
||||
function EmailDomain3() {
|
||||
const [emailId, setEmailId] = useState('');
|
||||
const [emailDomain, setEmailDomain] = useState('');
|
||||
const [customDomain, setCustomDomain] = useState('');
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const displayValue = emailDomain === '직접입력' ? customDomain : emailDomain;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-semibold">시안 3: Combobox 스타일 (인풋 + 드롭다운 통합)</h3>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Input
|
||||
placeholder="이메일"
|
||||
value={emailId}
|
||||
onChange={(e) => setEmailId(e.target.value)}
|
||||
className="flex-1 h-11"
|
||||
/>
|
||||
<span className="text-muted-foreground">@</span>
|
||||
<div className="relative flex-1">
|
||||
<Input
|
||||
placeholder="도메인 선택 또는 입력"
|
||||
value={displayValue}
|
||||
onChange={(e) => {
|
||||
setEmailDomain('직접입력');
|
||||
setCustomDomain(e.target.value);
|
||||
}}
|
||||
onFocus={() => setIsOpen(true)}
|
||||
className="h-11 pr-10"
|
||||
/>
|
||||
<Select
|
||||
value={emailDomain}
|
||||
onValueChange={(v) => {
|
||||
setEmailDomain(v);
|
||||
if (v !== '직접입력') setCustomDomain('');
|
||||
setIsOpen(false);
|
||||
}}
|
||||
open={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
>
|
||||
<SelectTrigger className="absolute right-0 top-0 w-10 h-11 border-0 bg-transparent hover:bg-transparent focus:ring-0">
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{emailDomains.map((domain) => (
|
||||
<SelectItem key={domain} value={domain}>
|
||||
{domain}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
결과: {emailId}@{displayValue}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 시안 4: 드롭다운 영역과 입력 영역 분리
|
||||
function EmailDomain4() {
|
||||
const [emailId, setEmailId] = useState('');
|
||||
const [emailDomain, setEmailDomain] = useState('');
|
||||
const [customDomain, setCustomDomain] = useState('');
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-semibold">시안 4: 드롭다운 + 직접입력 시 인풋으로 교체 (동일 너비)</h3>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Input
|
||||
placeholder="이메일"
|
||||
value={emailId}
|
||||
onChange={(e) => setEmailId(e.target.value)}
|
||||
className="w-[140px] h-11"
|
||||
/>
|
||||
<span className="text-muted-foreground shrink-0">@</span>
|
||||
<div className="flex-1 flex gap-1">
|
||||
{emailDomain === '직접입력' ? (
|
||||
<Input
|
||||
placeholder="도메인 입력"
|
||||
value={customDomain}
|
||||
onChange={(e) => setCustomDomain(e.target.value)}
|
||||
className="flex-1 h-11"
|
||||
/>
|
||||
) : (
|
||||
<div className="flex-1" />
|
||||
)}
|
||||
<Select
|
||||
value={emailDomain}
|
||||
onValueChange={(v) => {
|
||||
setEmailDomain(v);
|
||||
if (v !== '직접입력') setCustomDomain('');
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className={emailDomain === '직접입력' ? "w-[100px] h-11" : "w-full h-11"}>
|
||||
<SelectValue placeholder="도메인 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{emailDomains.map((domain) => (
|
||||
<SelectItem key={domain} value={domain}>
|
||||
{domain}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
결과: {emailId}@{emailDomain === '직접입력' ? customDomain : emailDomain}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 시안 5: 인풋과 드롭다운 자연스럽게 통합 (하나의 필드처럼 보이게)
|
||||
function EmailDomain5() {
|
||||
const [emailId, setEmailId] = useState('');
|
||||
const [emailDomain, setEmailDomain] = useState('');
|
||||
const [customDomain, setCustomDomain] = useState('');
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<h3 className="font-semibold">시안 5: 인풋 + 드롭다운 자연스럽게 통합</h3>
|
||||
<div className="flex gap-2 items-center">
|
||||
<Input
|
||||
placeholder="이메일"
|
||||
value={emailId}
|
||||
onChange={(e) => setEmailId(e.target.value)}
|
||||
className="flex-1 h-11"
|
||||
/>
|
||||
<span className="text-muted-foreground shrink-0">@</span>
|
||||
<div className="flex items-center flex-1 h-11 border rounded-md focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2">
|
||||
{emailDomain === '직접입력' ? (
|
||||
<Input
|
||||
placeholder="도메인 입력"
|
||||
value={customDomain}
|
||||
onChange={(e) => setCustomDomain(e.target.value)}
|
||||
className="flex-1 h-full border-0 focus-visible:ring-0 focus-visible:ring-offset-0 rounded-r-none"
|
||||
/>
|
||||
) : (
|
||||
<span className="flex-1 px-3 text-sm truncate">
|
||||
{emailDomain || <span className="text-muted-foreground">도메인 선택</span>}
|
||||
</span>
|
||||
)}
|
||||
<Select
|
||||
value={emailDomain}
|
||||
onValueChange={(v) => {
|
||||
setEmailDomain(v);
|
||||
if (v !== '직접입력') setCustomDomain('');
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="w-10 h-full border-0 bg-transparent px-0 focus:ring-0 rounded-l-none justify-center" />
|
||||
<SelectContent>
|
||||
{emailDomains.map((domain) => (
|
||||
<SelectItem key={domain} value={domain}>
|
||||
{domain}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
결과: {emailId}@{emailDomain === '직접입력' ? customDomain : emailDomain}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function EmailDomainDemo() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 p-8">
|
||||
<div className="max-w-2xl mx-auto space-y-8">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">이메일 도메인 입력 UI 시안</h1>
|
||||
<p className="text-muted-foreground mt-2">
|
||||
직접입력 선택 시 인풋과 드롭다운이 함께 동작하는 다양한 방식
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-6 rounded-lg border space-y-8">
|
||||
<EmailDomain1 />
|
||||
<hr />
|
||||
<EmailDomain2 />
|
||||
<hr />
|
||||
<EmailDomain3 />
|
||||
<hr />
|
||||
<EmailDomain4 />
|
||||
<hr />
|
||||
<EmailDomain5 />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user