feat: 전체 UI 리디자인 — Editorial Warmth 디자인 시스템 적용
Playfair Display + Noto Sans KR 폰트, warm 브라운 컬러 시스템, 글래스모피즘 카드, 애니메이션 효과를 전체 22개 페이지/컴포넌트에 적용. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
# Project: startover
|
||||
|
||||
## Execution Rules
|
||||
|
||||
- 사용자가 요청한 것을 실행하라. 대안을 제안하거나 다른 방법으로 회피하지 마라.
|
||||
- 도구/플러그인/의존성이 없으면 설치를 시도하라. "없으니 다른 걸로 하겠습니다"는 금지.
|
||||
- NEVER suggest fallbacks or alternatives unless the user explicitly asks for them.
|
||||
- If a tool, plugin, or dependency is missing, install it. Do not skip the task or suggest a manual workaround.
|
||||
- "고쳤다", "완료했다"고 말하기 전에 실제 동작 확인 결과를 보여줘라.
|
||||
- 확실하지 않으면 추측하지 말고 코드를 직접 읽어라. 임시 해결이 아닌 근본 원인을 찾아라.
|
||||
|
||||
## Self-Improvement
|
||||
|
||||
- 사용자가 접근 방식을 수정하면, 같은 실수를 반복하지 않도록 CLAUDE.md에 규칙을 추가하라.
|
||||
|
||||
@@ -2,13 +2,13 @@ import Link from 'next/link';
|
||||
|
||||
export default function ForbiddenPage() {
|
||||
return (
|
||||
<div className="mx-auto max-w-md px-4 py-16 text-center">
|
||||
<h1 className="mb-4 text-6xl font-bold text-gray-300">403</h1>
|
||||
<h2 className="mb-4 text-xl font-bold">접근 권한이 없습니다</h2>
|
||||
<p className="mb-8 text-gray-600">이 페이지에 접근할 수 있는 권한이 없습니다.</p>
|
||||
<div className="mx-auto max-w-md px-6 py-24 text-center animate-fade-up">
|
||||
<p className="font-display text-8xl font-black text-ink/[0.06] select-none">403</p>
|
||||
<h1 className="mt-2 font-display text-2xl font-bold text-ink">접근 권한이 없습니다</h1>
|
||||
<p className="mt-3 text-sm text-ink-muted">이 페이지에 접근할 수 있는 권한이 없습니다.</p>
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-block rounded-md bg-blue-600 px-6 py-2 text-white hover:bg-blue-700"
|
||||
className="mt-8 inline-block rounded-full bg-ink px-8 py-3 text-sm font-medium text-warm-50 hover:bg-warm-800 transition-colors"
|
||||
>
|
||||
홈으로
|
||||
</Link>
|
||||
|
||||
@@ -29,13 +29,13 @@ export function AuthButtons({ session }: AuthButtonsProps) {
|
||||
<div className="flex items-center gap-2">
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="rounded-md px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-100"
|
||||
className="text-sm text-ink-light hover:text-warm-600 transition-colors"
|
||||
>
|
||||
로그인
|
||||
</Link>
|
||||
<Link
|
||||
href="/auth/register"
|
||||
className="rounded-md bg-blue-600 px-3 py-1.5 text-sm text-white hover:bg-blue-700"
|
||||
className="rounded-full bg-ink px-4 py-1.5 text-sm text-warm-50 hover:bg-warm-800 transition-colors"
|
||||
>
|
||||
회원가입
|
||||
</Link>
|
||||
@@ -45,15 +45,15 @@ export function AuthButtons({ session }: AuthButtonsProps) {
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm text-gray-600">
|
||||
<span className="text-sm text-ink-light">
|
||||
{session.user.name}
|
||||
<span className="ml-1 text-xs text-gray-400">
|
||||
<span className="ml-1 text-xs text-ink-muted">
|
||||
({ROLE_LABELS[session.user.primaryRole] || session.user.primaryRole})
|
||||
</span>
|
||||
</span>
|
||||
<button
|
||||
onClick={() => signOut({ callbackUrl: '/' })}
|
||||
className="rounded-md px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-100"
|
||||
className="rounded-full px-3 py-1.5 text-sm text-ink-light hover:bg-warm-100 transition-colors"
|
||||
>
|
||||
로그아웃
|
||||
</button>
|
||||
|
||||
@@ -49,65 +49,73 @@ export default function CompleteProfilePage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-md px-4 py-16">
|
||||
<h1 className="mb-2 text-center text-2xl font-bold">프로필 완성</h1>
|
||||
<p className="mb-8 text-center text-sm text-gray-500">
|
||||
<div className="mx-auto max-w-md px-6 py-20">
|
||||
<div className="animate-fade-up">
|
||||
<h1 className="mb-2 text-center font-display text-3xl font-bold text-ink">프로필 완성</h1>
|
||||
<p className="mb-8 text-center text-sm text-ink-muted">
|
||||
소셜 로그인으로 가입하셨습니다. 역할을 선택해주세요.
|
||||
</p>
|
||||
|
||||
{error && <div className="mb-4 rounded-md bg-red-50 p-3 text-sm text-red-600">{error}</div>}
|
||||
{error && (
|
||||
<div className="mb-4 rounded-xl border border-red-200 bg-red-50/80 p-4 text-sm text-red-700">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
<div>
|
||||
<label className="mb-2 block text-sm font-medium text-gray-700">역할 선택</label>
|
||||
<label className="mb-1.5 block text-sm font-medium text-ink-light">역할 선택</label>
|
||||
<div className="space-y-2">
|
||||
{ROLES.map((role) => (
|
||||
<label
|
||||
key={role.value}
|
||||
className="flex cursor-pointer items-center gap-3 rounded-md border border-gray-200 p-3 hover:bg-gray-50"
|
||||
className="flex cursor-pointer items-center gap-3 rounded-xl border-2 border-ink/5 p-4 transition-all hover:border-warm-400 hover:bg-warm-50"
|
||||
>
|
||||
<input type="radio" name="role" value={role.value} required className="h-4 w-4" />
|
||||
<div>
|
||||
<div className="font-medium">{role.label}</div>
|
||||
<div className="text-sm text-gray-500">{role.desc}</div>
|
||||
<div className="font-medium text-ink">{role.label}</div>
|
||||
<div className="text-sm text-ink-muted">{role.desc}</div>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 rounded-md border border-gray-200 p-4">
|
||||
<p className="text-sm font-medium text-gray-700">약관 동의</p>
|
||||
<div className="rounded-xl border border-ink/5 bg-warm-50/50 p-5">
|
||||
<p className="mb-3 text-sm font-medium text-ink-light">약관 동의</p>
|
||||
<div className="space-y-3">
|
||||
<label className="flex items-center gap-2">
|
||||
<input type="checkbox" name="termsOfService" required className="h-4 w-4" />
|
||||
<span className="text-sm">
|
||||
<span className="text-red-500">[필수]</span> 이용약관 동의
|
||||
<span className="text-sm text-ink">
|
||||
<span className="text-red-600">[필수]</span> 이용약관 동의
|
||||
</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2">
|
||||
<input type="checkbox" name="privacyPolicy" required className="h-4 w-4" />
|
||||
<span className="text-sm">
|
||||
<span className="text-red-500">[필수]</span> 개인정보 처리방침 동의
|
||||
<span className="text-sm text-ink">
|
||||
<span className="text-red-600">[필수]</span> 개인정보 처리방침 동의
|
||||
</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2">
|
||||
<input type="checkbox" name="marketingConsent" className="h-4 w-4" />
|
||||
<span className="text-sm">[선택] 마케팅 정보 수신 동의</span>
|
||||
<span className="text-sm text-ink">[선택] 마케팅 정보 수신 동의</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2">
|
||||
<input type="checkbox" name="kakaoNotification" className="h-4 w-4" />
|
||||
<span className="text-sm">[선택] 카카오 알림톡 수신 동의</span>
|
||||
<span className="text-sm text-ink">[선택] 카카오 알림톡 수신 동의</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
className="w-full rounded-md bg-blue-600 py-2.5 text-white hover:bg-blue-700 disabled:opacity-50"
|
||||
className="w-full rounded-full bg-ink py-3 text-sm font-medium text-warm-50 transition-colors hover:bg-warm-800 disabled:opacity-50"
|
||||
>
|
||||
{isPending ? '처리 중...' : '완료'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -49,17 +49,22 @@ export default function InviteAcceptPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-md px-4 py-16">
|
||||
<h1 className="mb-2 text-center text-2xl font-bold">운영자 초대</h1>
|
||||
<p className="mb-8 text-center text-sm text-gray-500">
|
||||
<div className="mx-auto max-w-md px-6 py-20">
|
||||
<div className="animate-fade-up">
|
||||
<h1 className="mb-2 text-center font-display text-3xl font-bold text-ink">운영자 초대</h1>
|
||||
<p className="mb-8 text-center text-sm text-ink-muted">
|
||||
비밀번호를 설정하여 가입을 완료하세요.
|
||||
</p>
|
||||
|
||||
{error && <div className="mb-4 rounded-md bg-red-50 p-3 text-sm text-red-600">{error}</div>}
|
||||
{error && (
|
||||
<div className="mb-4 rounded-xl border border-red-200 bg-red-50/80 p-4 text-sm text-red-700">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="name" className="mb-1 block text-sm font-medium text-gray-700">
|
||||
<label htmlFor="name" className="mb-1.5 block text-sm font-medium text-ink-light">
|
||||
이름
|
||||
</label>
|
||||
<input
|
||||
@@ -67,11 +72,11 @@ export default function InviteAcceptPage() {
|
||||
name="name"
|
||||
type="text"
|
||||
required
|
||||
className="w-full rounded-md border border-gray-300 px-3 py-2 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink placeholder:text-ink-muted focus:border-warm-500 focus:outline-none focus:ring-2 focus:ring-warm-500/20"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="password" className="mb-1 block text-sm font-medium text-gray-700">
|
||||
<label htmlFor="password" className="mb-1.5 block text-sm font-medium text-ink-light">
|
||||
비밀번호
|
||||
</label>
|
||||
<input
|
||||
@@ -80,12 +85,15 @@ export default function InviteAcceptPage() {
|
||||
type="password"
|
||||
required
|
||||
minLength={8}
|
||||
className="w-full rounded-md border border-gray-300 px-3 py-2 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink placeholder:text-ink-muted focus:border-warm-500 focus:outline-none focus:ring-2 focus:ring-warm-500/20"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-gray-500">8자 이상, 영문+숫자 포함</p>
|
||||
<p className="mt-1 text-xs text-ink-muted">8자 이상, 영문+숫자 포함</p>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="confirmPassword" className="mb-1 block text-sm font-medium text-gray-700">
|
||||
<label
|
||||
htmlFor="confirmPassword"
|
||||
className="mb-1.5 block text-sm font-medium text-ink-light"
|
||||
>
|
||||
비밀번호 확인
|
||||
</label>
|
||||
<input
|
||||
@@ -94,17 +102,18 @@ export default function InviteAcceptPage() {
|
||||
type="password"
|
||||
required
|
||||
minLength={8}
|
||||
className="w-full rounded-md border border-gray-300 px-3 py-2 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink placeholder:text-ink-muted focus:border-warm-500 focus:outline-none focus:ring-2 focus:ring-warm-500/20"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
className="w-full rounded-md bg-blue-600 py-2.5 text-white hover:bg-blue-700 disabled:opacity-50"
|
||||
className="w-full rounded-full bg-ink py-3 text-sm font-medium text-warm-50 transition-colors hover:bg-warm-800 disabled:opacity-50"
|
||||
>
|
||||
{isPending ? '처리 중...' : '가입 완료'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,26 +36,32 @@ function LoginForm() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-md px-4 py-16">
|
||||
<h1 className="mb-8 text-center text-2xl font-bold">로그인</h1>
|
||||
<div className="mx-auto max-w-md px-6 py-20">
|
||||
<div className="animate-fade-up">
|
||||
<h1 className="mb-2 text-center font-display text-3xl font-bold text-ink">로그인</h1>
|
||||
<p className="mb-8 text-center text-sm text-ink-muted">다시 만나서 반갑습니다</p>
|
||||
|
||||
{verified && (
|
||||
<div className="mb-4 rounded-md bg-green-50 p-3 text-sm text-green-600">
|
||||
<div className="mb-4 rounded-xl border border-sage-500/20 bg-sage-500/5 p-4 text-sm text-sage-600">
|
||||
이메일 인증이 완료되었습니다. 로그인해주세요.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{invited && (
|
||||
<div className="mb-4 rounded-md bg-green-50 p-3 text-sm text-green-600">
|
||||
<div className="mb-4 rounded-xl border border-sage-500/20 bg-sage-500/5 p-4 text-sm text-sage-600">
|
||||
가입이 완료되었습니다. 로그인해주세요.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && <div className="mb-4 rounded-md bg-red-50 p-3 text-sm text-red-600">{error}</div>}
|
||||
{error && (
|
||||
<div className="mb-4 rounded-xl border border-red-200 bg-red-50/80 p-4 text-sm text-red-700">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="email" className="mb-1 block text-sm font-medium text-gray-700">
|
||||
<label htmlFor="email" className="mb-1.5 block text-sm font-medium text-ink-light">
|
||||
이메일
|
||||
</label>
|
||||
<input
|
||||
@@ -63,12 +69,12 @@ function LoginForm() {
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
className="w-full rounded-md border border-gray-300 px-3 py-2 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink placeholder:text-ink-muted focus:border-warm-500 focus:outline-none focus:ring-2 focus:ring-warm-500/20"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="mb-1 block text-sm font-medium text-gray-700">
|
||||
<label htmlFor="password" className="mb-1.5 block text-sm font-medium text-ink-light">
|
||||
비밀번호
|
||||
</label>
|
||||
<input
|
||||
@@ -76,40 +82,41 @@ function LoginForm() {
|
||||
name="password"
|
||||
type="password"
|
||||
required
|
||||
className="w-full rounded-md border border-gray-300 px-3 py-2 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink placeholder:text-ink-muted focus:border-warm-500 focus:outline-none focus:ring-2 focus:ring-warm-500/20"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
className="w-full rounded-md bg-blue-600 py-2.5 text-white hover:bg-blue-700 disabled:opacity-50"
|
||||
className="w-full rounded-full bg-ink py-3 text-sm font-medium text-warm-50 transition-colors hover:bg-warm-800 disabled:opacity-50"
|
||||
>
|
||||
{isPending ? '로그인 중...' : '로그인'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="my-6 flex items-center gap-4">
|
||||
<div className="h-px flex-1 bg-gray-200" />
|
||||
<span className="text-sm text-gray-400">또는</span>
|
||||
<div className="h-px flex-1 bg-gray-200" />
|
||||
<div className="h-px flex-1 bg-ink/10" />
|
||||
<span className="text-sm text-ink-muted">또는</span>
|
||||
<div className="h-px flex-1 bg-ink/10" />
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => signIn('kakao', { callbackUrl })}
|
||||
className="flex w-full items-center justify-center gap-2 rounded-md bg-[#FEE500] py-2.5 text-sm font-medium text-[#191919] hover:bg-[#FDD800]"
|
||||
className="flex w-full items-center justify-center gap-2 rounded-full bg-[#FEE500] py-3 text-sm font-medium text-[#191919] transition-colors hover:bg-[#FDD800]"
|
||||
>
|
||||
카카오로 로그인
|
||||
</button>
|
||||
|
||||
<p className="mt-6 text-center text-sm text-gray-500">
|
||||
<p className="mt-6 text-center text-sm text-ink-muted">
|
||||
계정이 없으신가요?{' '}
|
||||
<Link href="/auth/register" className="text-blue-600 hover:underline">
|
||||
<Link href="/auth/register" className="text-warm-600 hover:text-warm-700">
|
||||
회원가입
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,55 +17,61 @@ export default function RegisterPage() {
|
||||
|
||||
if (state.success) {
|
||||
return (
|
||||
<div className="mx-auto max-w-md px-4 py-16 text-center">
|
||||
<h1 className="mb-4 text-2xl font-bold text-green-600">가입 완료!</h1>
|
||||
<p className="mb-6 text-gray-600">
|
||||
<div className="mx-auto max-w-md px-6 py-20 text-center">
|
||||
<div className="animate-fade-up">
|
||||
<h1 className="mb-4 font-display text-3xl font-bold text-ink">가입 완료!</h1>
|
||||
<p className="mb-6 text-sm text-ink-muted">
|
||||
입력하신 이메일로 인증 링크를 발송했습니다.
|
||||
<br />
|
||||
이메일을 확인하여 인증을 완료해주세요.
|
||||
</p>
|
||||
<Link
|
||||
href="/auth/login"
|
||||
className="inline-block rounded-md bg-blue-600 px-6 py-2 text-white hover:bg-blue-700"
|
||||
className="inline-block rounded-full bg-ink px-8 py-3 text-sm font-medium text-warm-50 transition-colors hover:bg-warm-800"
|
||||
>
|
||||
로그인하기
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-md px-4 py-16">
|
||||
<h1 className="mb-8 text-center text-2xl font-bold">회원가입</h1>
|
||||
<div className="mx-auto max-w-md px-6 py-20">
|
||||
<div className="animate-fade-up">
|
||||
<h1 className="mb-2 text-center font-display text-3xl font-bold text-ink">회원가입</h1>
|
||||
<p className="mb-8 text-center text-sm text-ink-muted">스타트오버와 함께 시작하세요</p>
|
||||
|
||||
{state.error && (
|
||||
<div className="mb-4 rounded-md bg-red-50 p-3 text-sm text-red-600">{state.error}</div>
|
||||
<div className="mb-4 rounded-xl border border-red-200 bg-red-50/80 p-4 text-sm text-red-700">
|
||||
{state.error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form action={formAction} className="space-y-6">
|
||||
<div>
|
||||
<label className="mb-2 block text-sm font-medium text-gray-700">역할 선택</label>
|
||||
<label className="mb-1.5 block text-sm font-medium text-ink-light">역할 선택</label>
|
||||
<div className="space-y-2">
|
||||
{ROLES.map((role) => (
|
||||
<label
|
||||
key={role.value}
|
||||
className="flex cursor-pointer items-center gap-3 rounded-md border border-gray-200 p-3 hover:bg-gray-50"
|
||||
className="flex cursor-pointer items-center gap-3 rounded-xl border-2 border-ink/5 p-4 transition-all hover:border-warm-400 hover:bg-warm-50"
|
||||
>
|
||||
<input type="radio" name="role" value={role.value} required className="h-4 w-4" />
|
||||
<div>
|
||||
<div className="font-medium">{role.label}</div>
|
||||
<div className="text-sm text-gray-500">{role.desc}</div>
|
||||
<div className="font-medium text-ink">{role.label}</div>
|
||||
<div className="text-sm text-ink-muted">{role.desc}</div>
|
||||
</div>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
{state.fieldErrors?.role && (
|
||||
<p className="mt-1 text-sm text-red-500">{state.fieldErrors.role[0]}</p>
|
||||
<p className="mt-1 text-sm text-red-700">{state.fieldErrors.role[0]}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="name" className="mb-1 block text-sm font-medium text-gray-700">
|
||||
<label htmlFor="name" className="mb-1.5 block text-sm font-medium text-ink-light">
|
||||
이름
|
||||
</label>
|
||||
<input
|
||||
@@ -73,15 +79,15 @@ export default function RegisterPage() {
|
||||
name="name"
|
||||
type="text"
|
||||
required
|
||||
className="w-full rounded-md border border-gray-300 px-3 py-2 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink placeholder:text-ink-muted focus:border-warm-500 focus:outline-none focus:ring-2 focus:ring-warm-500/20"
|
||||
/>
|
||||
{state.fieldErrors?.name && (
|
||||
<p className="mt-1 text-sm text-red-500">{state.fieldErrors.name[0]}</p>
|
||||
<p className="mt-1 text-sm text-red-700">{state.fieldErrors.name[0]}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="phone" className="mb-1 block text-sm font-medium text-gray-700">
|
||||
<label htmlFor="phone" className="mb-1.5 block text-sm font-medium text-ink-light">
|
||||
휴대폰 번호
|
||||
</label>
|
||||
<input
|
||||
@@ -89,15 +95,15 @@ export default function RegisterPage() {
|
||||
name="phone"
|
||||
type="tel"
|
||||
placeholder="010-1234-5678"
|
||||
className="w-full rounded-md border border-gray-300 px-3 py-2 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink placeholder:text-ink-muted focus:border-warm-500 focus:outline-none focus:ring-2 focus:ring-warm-500/20"
|
||||
/>
|
||||
{state.fieldErrors?.phone && (
|
||||
<p className="mt-1 text-sm text-red-500">{state.fieldErrors.phone[0]}</p>
|
||||
<p className="mt-1 text-sm text-red-700">{state.fieldErrors.phone[0]}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="email" className="mb-1 block text-sm font-medium text-gray-700">
|
||||
<label htmlFor="email" className="mb-1.5 block text-sm font-medium text-ink-light">
|
||||
이메일
|
||||
</label>
|
||||
<input
|
||||
@@ -105,15 +111,15 @@ export default function RegisterPage() {
|
||||
name="email"
|
||||
type="email"
|
||||
required
|
||||
className="w-full rounded-md border border-gray-300 px-3 py-2 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink placeholder:text-ink-muted focus:border-warm-500 focus:outline-none focus:ring-2 focus:ring-warm-500/20"
|
||||
/>
|
||||
{state.fieldErrors?.email && (
|
||||
<p className="mt-1 text-sm text-red-500">{state.fieldErrors.email[0]}</p>
|
||||
<p className="mt-1 text-sm text-red-700">{state.fieldErrors.email[0]}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="password" className="mb-1 block text-sm font-medium text-gray-700">
|
||||
<label htmlFor="password" className="mb-1.5 block text-sm font-medium text-ink-light">
|
||||
비밀번호
|
||||
</label>
|
||||
<input
|
||||
@@ -122,59 +128,62 @@ export default function RegisterPage() {
|
||||
type="password"
|
||||
required
|
||||
minLength={8}
|
||||
className="w-full rounded-md border border-gray-300 px-3 py-2 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink placeholder:text-ink-muted focus:border-warm-500 focus:outline-none focus:ring-2 focus:ring-warm-500/20"
|
||||
/>
|
||||
<p className="mt-1 text-xs text-gray-500">8자 이상, 영문+숫자 포함</p>
|
||||
<p className="mt-1 text-xs text-ink-muted">8자 이상, 영문+숫자 포함</p>
|
||||
{state.fieldErrors?.password && (
|
||||
<p className="mt-1 text-sm text-red-500">{state.fieldErrors.password[0]}</p>
|
||||
<p className="mt-1 text-sm text-red-700">{state.fieldErrors.password[0]}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 rounded-md border border-gray-200 p-4">
|
||||
<p className="text-sm font-medium text-gray-700">약관 동의</p>
|
||||
<div className="rounded-xl border border-ink/5 bg-warm-50/50 p-5">
|
||||
<p className="mb-3 text-sm font-medium text-ink-light">약관 동의</p>
|
||||
<div className="space-y-3">
|
||||
<label className="flex items-center gap-2">
|
||||
<input type="checkbox" name="termsOfService" required className="h-4 w-4" />
|
||||
<span className="text-sm">
|
||||
<span className="text-red-500">[필수]</span> 이용약관 동의
|
||||
<span className="text-sm text-ink">
|
||||
<span className="text-red-600">[필수]</span> 이용약관 동의
|
||||
</span>
|
||||
</label>
|
||||
{state.fieldErrors?.termsOfService && (
|
||||
<p className="text-sm text-red-500">{state.fieldErrors.termsOfService[0]}</p>
|
||||
<p className="text-sm text-red-700">{state.fieldErrors.termsOfService[0]}</p>
|
||||
)}
|
||||
<label className="flex items-center gap-2">
|
||||
<input type="checkbox" name="privacyPolicy" required className="h-4 w-4" />
|
||||
<span className="text-sm">
|
||||
<span className="text-red-500">[필수]</span> 개인정보 처리방침 동의
|
||||
<span className="text-sm text-ink">
|
||||
<span className="text-red-600">[필수]</span> 개인정보 처리방침 동의
|
||||
</span>
|
||||
</label>
|
||||
{state.fieldErrors?.privacyPolicy && (
|
||||
<p className="text-sm text-red-500">{state.fieldErrors.privacyPolicy[0]}</p>
|
||||
<p className="text-sm text-red-700">{state.fieldErrors.privacyPolicy[0]}</p>
|
||||
)}
|
||||
<label className="flex items-center gap-2">
|
||||
<input type="checkbox" name="marketingConsent" className="h-4 w-4" />
|
||||
<span className="text-sm">[선택] 마케팅 정보 수신 동의</span>
|
||||
<span className="text-sm text-ink">[선택] 마케팅 정보 수신 동의</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2">
|
||||
<input type="checkbox" name="kakaoNotification" className="h-4 w-4" />
|
||||
<span className="text-sm">[선택] 카카오 알림톡 수신 동의</span>
|
||||
<span className="text-sm text-ink">[선택] 카카오 알림톡 수신 동의</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
className="w-full rounded-md bg-blue-600 py-2.5 text-white hover:bg-blue-700 disabled:opacity-50"
|
||||
className="w-full rounded-full bg-ink py-3 text-sm font-medium text-warm-50 transition-colors hover:bg-warm-800 disabled:opacity-50"
|
||||
>
|
||||
{isPending ? '처리 중...' : '가입하기'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p className="mt-6 text-center text-sm text-gray-500">
|
||||
<p className="mt-6 text-center text-sm text-ink-muted">
|
||||
이미 계정이 있으신가요?{' '}
|
||||
<Link href="/auth/login" className="text-blue-600 hover:underline">
|
||||
<Link href="/auth/login" className="text-warm-600 hover:text-warm-700">
|
||||
로그인
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,22 +2,24 @@ import Link from 'next/link';
|
||||
|
||||
export default function VerifyPendingPage() {
|
||||
return (
|
||||
<div className="mx-auto max-w-md px-4 py-16 text-center">
|
||||
<h1 className="mb-4 text-2xl font-bold">이메일 인증이 필요합니다</h1>
|
||||
<p className="mb-6 text-gray-600">
|
||||
<div className="mx-auto max-w-md px-6 py-20 text-center">
|
||||
<div className="animate-fade-up">
|
||||
<h1 className="mb-4 font-display text-3xl font-bold text-ink">이메일 인증이 필요합니다</h1>
|
||||
<p className="mb-6 text-sm text-ink-muted">
|
||||
가입 시 입력한 이메일로 인증 링크를 발송했습니다.
|
||||
<br />
|
||||
이메일을 확인하고 인증을 완료해주세요.
|
||||
</p>
|
||||
<p className="mb-8 text-sm text-gray-400">
|
||||
<p className="mb-8 text-sm text-ink-muted">
|
||||
이메일을 받지 못하셨나요? 스팸함을 확인하거나 잠시 후 다시 시도해주세요.
|
||||
</p>
|
||||
<Link
|
||||
href="/"
|
||||
className="inline-block rounded-md bg-gray-100 px-6 py-2 text-gray-700 hover:bg-gray-200"
|
||||
className="inline-block rounded-full bg-ink px-8 py-3 text-sm font-medium text-warm-50 transition-colors hover:bg-warm-800"
|
||||
>
|
||||
홈으로
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,9 +13,11 @@ export default async function VerifyPage({
|
||||
|
||||
if (!token) {
|
||||
return (
|
||||
<div className="mx-auto max-w-md px-4 py-16 text-center">
|
||||
<h1 className="mb-4 text-2xl font-bold text-red-600">잘못된 접근</h1>
|
||||
<p className="text-gray-600">인증 토큰이 없습니다.</p>
|
||||
<div className="mx-auto max-w-md px-6 py-20 text-center">
|
||||
<div className="animate-fade-up">
|
||||
<h1 className="mb-4 font-display text-3xl font-bold text-ink">잘못된 접근</h1>
|
||||
<p className="text-sm text-ink-muted">인증 토큰이 없습니다.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -26,13 +28,17 @@ export default async function VerifyPage({
|
||||
|
||||
if (!verificationToken) {
|
||||
return (
|
||||
<div className="mx-auto max-w-md px-4 py-16 text-center">
|
||||
<h1 className="mb-4 text-2xl font-bold text-red-600">유효하지 않은 토큰</h1>
|
||||
<p className="mb-6 text-gray-600">이미 사용되었거나 존재하지 않는 인증 토큰입니다.</p>
|
||||
<Link href="/auth/login" className="text-blue-600 hover:underline">
|
||||
<div className="mx-auto max-w-md px-6 py-20 text-center">
|
||||
<div className="animate-fade-up">
|
||||
<h1 className="mb-4 font-display text-3xl font-bold text-ink">유효하지 않은 토큰</h1>
|
||||
<p className="mb-6 text-sm text-ink-muted">
|
||||
이미 사용되었거나 존재하지 않는 인증 토큰입니다.
|
||||
</p>
|
||||
<Link href="/auth/login" className="text-warm-600 hover:text-warm-700">
|
||||
로그인으로 이동
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,15 +48,17 @@ export default async function VerifyPage({
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-md px-4 py-16 text-center">
|
||||
<h1 className="mb-4 text-2xl font-bold text-red-600">토큰 만료</h1>
|
||||
<p className="mb-6 text-gray-600">
|
||||
<div className="mx-auto max-w-md px-6 py-20 text-center">
|
||||
<div className="animate-fade-up">
|
||||
<h1 className="mb-4 font-display text-3xl font-bold text-ink">토큰 만료</h1>
|
||||
<p className="mb-6 text-sm text-ink-muted">
|
||||
인증 토큰이 만료되었습니다. 다시 로그인하여 인증 메일을 재발송해주세요.
|
||||
</p>
|
||||
<Link href="/auth/login" className="text-blue-600 hover:underline">
|
||||
<Link href="/auth/login" className="text-warm-600 hover:text-warm-700">
|
||||
로그인으로 이동
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -65,13 +73,17 @@ export default async function VerifyPage({
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-md px-4 py-16 text-center">
|
||||
<h1 className="mb-4 text-2xl font-bold text-red-600">사용자를 찾을 수 없습니다</h1>
|
||||
<p className="mb-6 text-gray-600">해당 계정이 존재하지 않습니다.</p>
|
||||
<Link href="/auth/register" className="text-blue-600 hover:underline">
|
||||
<div className="mx-auto max-w-md px-6 py-20 text-center">
|
||||
<div className="animate-fade-up">
|
||||
<h1 className="mb-4 font-display text-3xl font-bold text-ink">
|
||||
사용자를 찾을 수 없습니다
|
||||
</h1>
|
||||
<p className="mb-6 text-sm text-ink-muted">해당 계정이 존재하지 않습니다.</p>
|
||||
<Link href="/auth/register" className="text-warm-600 hover:text-warm-700">
|
||||
회원가입으로 이동
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,22 +24,22 @@ const CONTRACT_TYPE_LABELS: Record<string, string> = {
|
||||
};
|
||||
|
||||
const STATUS_MAP: Record<string, { label: string; color: string }> = {
|
||||
DRAFT: { label: '초안', color: 'bg-gray-100 text-gray-700' },
|
||||
GENERATED: { label: '생성됨', color: 'bg-blue-100 text-blue-700' },
|
||||
SIGNING: { label: '서명 중', color: 'bg-yellow-100 text-yellow-700' },
|
||||
SIGNED: { label: '서명 완료', color: 'bg-indigo-100 text-indigo-700' },
|
||||
ACTIVE: { label: '진행 중', color: 'bg-green-100 text-green-700' },
|
||||
COMPLETED: { label: '완료', color: 'bg-emerald-100 text-emerald-700' },
|
||||
DRAFT: { label: '초안', color: 'bg-warm-100 text-ink-muted' },
|
||||
GENERATED: { label: '생성됨', color: 'bg-warm-400/15 text-warm-700' },
|
||||
SIGNING: { label: '서명 중', color: 'bg-warm-400/15 text-warm-700' },
|
||||
SIGNED: { label: '서명 완료', color: 'bg-sage-500/10 text-sage-600' },
|
||||
ACTIVE: { label: '진행 중', color: 'bg-sage-500/10 text-sage-600' },
|
||||
COMPLETED: { label: '완료', color: 'bg-sage-500/10 text-sage-600' },
|
||||
CANCELLED: { label: '취소', color: 'bg-red-100 text-red-700' },
|
||||
};
|
||||
|
||||
const ESCROW_MAP: Record<string, { label: string; color: string }> = {
|
||||
NOT_STARTED: { label: '미시작', color: 'text-gray-500' },
|
||||
DEPOSIT_PENDING: { label: '입금 대기', color: 'text-yellow-600' },
|
||||
HOLDING: { label: '보관 중', color: 'text-blue-600' },
|
||||
RELEASE_REVIEW: { label: '정산 검토', color: 'text-purple-600' },
|
||||
RELEASED: { label: '정산 완료', color: 'text-green-600' },
|
||||
REFUNDED: { label: '환불됨', color: 'text-orange-600' },
|
||||
NOT_STARTED: { label: '미시작', color: 'text-ink-muted' },
|
||||
DEPOSIT_PENDING: { label: '입금 대기', color: 'text-warm-700' },
|
||||
HOLDING: { label: '보관 중', color: 'text-warm-700' },
|
||||
RELEASE_REVIEW: { label: '정산 검토', color: 'text-warm-700' },
|
||||
RELEASED: { label: '정산 완료', color: 'text-sage-600' },
|
||||
REFUNDED: { label: '환불됨', color: 'text-warm-700' },
|
||||
DISPUTED: { label: '분쟁 중', color: 'text-red-600' },
|
||||
};
|
||||
|
||||
@@ -70,12 +70,14 @@ export default async function ContractsPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="mx-auto max-w-4xl px-4 py-8">
|
||||
<h1 className="text-2xl font-bold text-gray-900">계약 관리</h1>
|
||||
<p className="mt-1 text-sm text-gray-500">계약, 에스크로, 검수 현황을 관리합니다</p>
|
||||
<main className="mx-auto max-w-4xl px-6 py-10 font-body">
|
||||
<div className="animate-fade-up">
|
||||
<h1 className="font-display text-3xl font-bold text-ink">계약 관리</h1>
|
||||
<p className="mt-1 text-sm text-ink-muted">계약, 에스크로, 검수 현황을 관리합니다</p>
|
||||
</div>
|
||||
|
||||
{/* 요약 */}
|
||||
<div className="mt-6 grid grid-cols-4 gap-4">
|
||||
<div className="mt-6 grid grid-cols-4 gap-4 animate-fade-up">
|
||||
<SummaryCard label="전체 계약" value={String(totalCount)} />
|
||||
<SummaryCard label="진행 중" value={String(activeCount)} />
|
||||
<SummaryCard label="에스크로 보관" value={String(escrowCount)} />
|
||||
@@ -83,33 +85,33 @@ export default async function ContractsPage() {
|
||||
</div>
|
||||
|
||||
{/* 계약 목록 */}
|
||||
<div className="mt-6 space-y-4">
|
||||
<div className="mt-6 space-y-4 animate-fade-up">
|
||||
{contracts.length === 0 ? (
|
||||
<p className="text-center text-sm text-gray-500">데이터가 없습니다</p>
|
||||
<p className="text-center text-sm text-ink-muted">데이터가 없습니다</p>
|
||||
) : (
|
||||
contracts.map((contract: (typeof contracts)[number]) => {
|
||||
const statusInfo = STATUS_MAP[contract.status] ?? {
|
||||
label: contract.status,
|
||||
color: 'bg-gray-100 text-gray-700',
|
||||
color: 'bg-warm-100 text-ink-muted',
|
||||
};
|
||||
const escrowInfo = ESCROW_MAP[contract.escrowStatus] ?? {
|
||||
label: contract.escrowStatus,
|
||||
color: 'text-gray-500',
|
||||
color: 'text-ink-muted',
|
||||
};
|
||||
return (
|
||||
<div
|
||||
key={contract.publicId}
|
||||
className="rounded-lg border border-gray-200 bg-white p-5"
|
||||
className="rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-5 card-lift"
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="font-semibold text-gray-900">{contract.store.listingTitle}</h3>
|
||||
<span className="rounded bg-gray-100 px-2 py-0.5 text-xs text-gray-600">
|
||||
<h3 className="font-display font-semibold text-ink">{contract.store.listingTitle}</h3>
|
||||
<span className="rounded-full bg-warm-100 px-2.5 py-0.5 text-xs text-ink-muted">
|
||||
{CONTRACT_TYPE_LABELS[contract.contractType] ?? contract.contractType}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-gray-400">
|
||||
<p className="mt-1 text-xs text-ink-muted">
|
||||
생성일: {new Date(contract.createdAt).toLocaleDateString('ko-KR')}
|
||||
</p>
|
||||
</div>
|
||||
@@ -122,15 +124,15 @@ export default async function ContractsPage() {
|
||||
|
||||
<div className="mt-3 flex items-center gap-6 text-sm">
|
||||
<div>
|
||||
<span className="text-gray-500">에스크로: </span>
|
||||
<span className="text-ink-muted">에스크로: </span>
|
||||
<span className={`font-medium ${escrowInfo.color}`}>{escrowInfo.label}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{contract.status === 'ACTIVE' && (
|
||||
<div className="mt-4 flex gap-3 border-t border-gray-100 pt-3">
|
||||
<div className="mt-4 flex gap-3 border-t border-ink/5 pt-3">
|
||||
<button
|
||||
className="text-sm text-gray-400 cursor-not-allowed"
|
||||
className="text-sm text-ink-muted cursor-not-allowed"
|
||||
disabled
|
||||
title="준비 중"
|
||||
>
|
||||
@@ -138,7 +140,7 @@ export default async function ContractsPage() {
|
||||
</button>
|
||||
<form action={handleOpenDispute}>
|
||||
<input type="hidden" name="contractPublicId" value={contract.publicId} />
|
||||
<button type="submit" className="text-sm text-red-600 hover:underline">
|
||||
<button type="submit" className="text-sm text-red-600 hover:text-red-700 transition-colors">
|
||||
분쟁 접수
|
||||
</button>
|
||||
</form>
|
||||
@@ -150,10 +152,10 @@ export default async function ContractsPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 rounded-lg border border-dashed border-gray-300 p-6 text-center">
|
||||
<p className="text-sm text-gray-500">
|
||||
<div className="mt-8 animate-fade-up rounded-2xl border-2 border-dashed border-ink/10 p-8 text-center">
|
||||
<p className="text-sm text-ink-muted">
|
||||
새 계약은{' '}
|
||||
<Link href="/matching" className="text-blue-600 hover:underline">
|
||||
<Link href="/matching" className="text-warm-600 hover:text-warm-700">
|
||||
수락된 매칭 요청
|
||||
</Link>
|
||||
에서 생성됩니다
|
||||
@@ -165,9 +167,9 @@ export default async function ContractsPage() {
|
||||
|
||||
function SummaryCard({ label, value }: { label: string; value: string }) {
|
||||
return (
|
||||
<div className="rounded-lg border border-gray-200 bg-white p-4 text-center">
|
||||
<p className="text-2xl font-bold text-gray-900">{value}</p>
|
||||
<p className="mt-1 text-xs text-gray-500">{label}</p>
|
||||
<div className="rounded-2xl border border-ink/5 bg-white/70 p-5 text-center">
|
||||
<p className="font-display text-3xl font-bold text-ink">{value}</p>
|
||||
<p className="mt-1 text-xs text-ink-muted">{label}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1 +1,108 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700;900&family=Playfair+Display:ital,wght@0,700;0,900;1,700&display=swap');
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
--font-display: 'Playfair Display', Georgia, serif;
|
||||
--font-body: 'Noto Sans KR', sans-serif;
|
||||
--color-warm-50: #fefcf9;
|
||||
--color-warm-100: #fdf6ec;
|
||||
--color-warm-200: #f9e8cf;
|
||||
--color-warm-300: #f2d1a5;
|
||||
--color-warm-400: #e8b06e;
|
||||
--color-warm-500: #d4874a;
|
||||
--color-warm-600: #b8622e;
|
||||
--color-warm-700: #8e4a22;
|
||||
--color-warm-800: #6b3a1e;
|
||||
--color-warm-900: #3d2213;
|
||||
--color-ink: #1a1410;
|
||||
--color-ink-light: #4a4035;
|
||||
--color-ink-muted: #8a7e72;
|
||||
--color-sage-500: #6b8f71;
|
||||
--color-sage-600: #527a58;
|
||||
}
|
||||
|
||||
/* Staggered fade-in-up animation */
|
||||
@keyframes fade-up {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(32px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes slide-in-right {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(24px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes mesh-float {
|
||||
0%, 100% { transform: translate(0, 0) scale(1); }
|
||||
33% { transform: translate(30px, -20px) scale(1.05); }
|
||||
66% { transform: translate(-20px, 15px) scale(0.95); }
|
||||
}
|
||||
|
||||
.animate-fade-up {
|
||||
animation: fade-up 0.8s cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
animation: fade-in 0.6s ease forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.animate-slide-right {
|
||||
animation: slide-in-right 0.7s cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Gradient mesh blobs */
|
||||
.mesh-blob {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
filter: blur(80px);
|
||||
animation: mesh-float 12s ease-in-out infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Card hover lift */
|
||||
.card-lift {
|
||||
transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.35s ease;
|
||||
}
|
||||
.card-lift:hover {
|
||||
transform: translateY(-6px);
|
||||
box-shadow: 0 20px 60px -12px rgba(26, 20, 16, 0.15);
|
||||
}
|
||||
|
||||
/* Link underline animation */
|
||||
.link-underline {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
.link-underline::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -2px;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
background: var(--color-warm-600);
|
||||
transition: width 0.3s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
.link-underline:hover::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
|
||||
<rect width="32" height="32" rx="6" fill="#2563EB"/>
|
||||
<text x="16" y="24" font-family="Arial, Helvetica, sans-serif" font-size="24" font-weight="bold" fill="white" text-anchor="middle">S</text>
|
||||
<rect width="32" height="32" rx="8" fill="#1a1410"/>
|
||||
<text x="16" y="23" font-family="Georgia, 'Playfair Display', serif" font-size="22" font-weight="bold" fill="#fefcf9" text-anchor="middle">S</text>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 288 B After Width: | Height: | Size: 296 B |
+10
-10
@@ -27,34 +27,34 @@ async function Navigation() {
|
||||
const isOperator = session?.user && OPERATOR_ROLES.includes(session.user.primaryRole);
|
||||
|
||||
return (
|
||||
<nav className="border-b border-gray-200 bg-white">
|
||||
<nav className="border-b border-ink/5 bg-warm-50/80 backdrop-blur-md">
|
||||
<div className="mx-auto flex h-16 max-w-7xl items-center justify-between px-4">
|
||||
<Link href="/" className="text-xl font-bold text-blue-600">
|
||||
<Link href="/" className="font-display text-xl font-bold text-ink">
|
||||
Startover
|
||||
</Link>
|
||||
<div className="flex items-center gap-6">
|
||||
<Link href="/stores" className="text-sm text-gray-700 hover:text-blue-600">
|
||||
<Link href="/stores" className="text-sm text-ink-light hover:text-warm-600 transition-colors">
|
||||
매장 검색
|
||||
</Link>
|
||||
<Link href="/stores/new" className="text-sm text-gray-700 hover:text-blue-600">
|
||||
<Link href="/stores/new" className="text-sm text-ink-light hover:text-warm-600 transition-colors">
|
||||
매장 등록
|
||||
</Link>
|
||||
<Link href="/matching" className="text-sm text-gray-700 hover:text-blue-600">
|
||||
<Link href="/matching" className="text-sm text-ink-light hover:text-warm-600 transition-colors">
|
||||
매칭
|
||||
</Link>
|
||||
<Link href="/subsidies" className="text-sm text-gray-700 hover:text-blue-600">
|
||||
<Link href="/subsidies" className="text-sm text-ink-light hover:text-warm-600 transition-colors">
|
||||
지원금
|
||||
</Link>
|
||||
<Link href="/vendors" className="text-sm text-gray-700 hover:text-blue-600">
|
||||
<Link href="/vendors" className="text-sm text-ink-light hover:text-warm-600 transition-colors">
|
||||
업체 인증
|
||||
</Link>
|
||||
<Link href="/contracts" className="text-sm text-gray-700 hover:text-blue-600">
|
||||
<Link href="/contracts" className="text-sm text-ink-light hover:text-warm-600 transition-colors">
|
||||
계약
|
||||
</Link>
|
||||
{isOperator && (
|
||||
<Link
|
||||
href="/admin"
|
||||
className="rounded-md bg-gray-100 px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-200"
|
||||
className="rounded-full bg-warm-100 px-3 py-1.5 text-sm text-ink-light hover:bg-warm-200"
|
||||
>
|
||||
운영자
|
||||
</Link>
|
||||
@@ -81,7 +81,7 @@ export default function RootLayout({
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
</head>
|
||||
<body className="min-h-screen bg-gray-50">
|
||||
<body className="min-h-screen bg-warm-50 font-body">
|
||||
<Navigation />
|
||||
{children}
|
||||
</body>
|
||||
|
||||
@@ -12,9 +12,9 @@ const MATCH_TYPE_LABELS: Record<string, string> = {
|
||||
};
|
||||
|
||||
const STATUS_LABELS: Record<string, { label: string; color: string }> = {
|
||||
OPEN: { label: '대기 중', color: 'bg-blue-100 text-blue-700' },
|
||||
REVIEWING: { label: '검토 중', color: 'bg-yellow-100 text-yellow-700' },
|
||||
ACCEPTED: { label: '수락됨', color: 'bg-green-100 text-green-700' },
|
||||
OPEN: { label: '대기 중', color: 'bg-warm-400/15 text-warm-700' },
|
||||
REVIEWING: { label: '검토 중', color: 'bg-warm-400/15 text-warm-700' },
|
||||
ACCEPTED: { label: '수락됨', color: 'bg-sage-500/10 text-sage-600' },
|
||||
REJECTED: { label: '거절됨', color: 'bg-red-100 text-red-700' },
|
||||
};
|
||||
|
||||
@@ -92,15 +92,17 @@ export default async function MatchingPage({
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="mx-auto max-w-4xl px-4 py-8">
|
||||
<h1 className="text-2xl font-bold text-gray-900">매칭 요청</h1>
|
||||
<p className="mt-1 text-sm text-gray-500">매장과의 매칭 요청 현황을 관리합니다</p>
|
||||
<main className="mx-auto max-w-4xl px-6 py-10 font-body">
|
||||
<div className="animate-fade-up">
|
||||
<h1 className="font-display text-3xl font-bold text-ink">매칭 요청</h1>
|
||||
<p className="mt-1 text-sm text-ink-muted">매장과의 매칭 요청 현황을 관리합니다</p>
|
||||
</div>
|
||||
|
||||
{targetStore ? (
|
||||
<div className="mt-6 rounded-lg border border-blue-200 bg-blue-50 p-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900">매칭 요청 보내기</h2>
|
||||
<p className="mt-1 text-sm text-gray-600">
|
||||
<span className="font-medium">{targetStore.listingTitle}</span> 매장에 매칭 요청을
|
||||
<div className="mt-6 animate-fade-up rounded-2xl border border-warm-200 bg-warm-100/50 p-5">
|
||||
<h2 className="font-display text-xl font-bold text-ink">매칭 요청 보내기</h2>
|
||||
<p className="mt-1 text-sm text-ink-muted">
|
||||
<span className="font-medium text-ink">{targetStore.listingTitle}</span> 매장에 매칭 요청을
|
||||
보냅니다.
|
||||
</p>
|
||||
<form action={handleCreateMatchRequest} className="mt-4 space-y-4">
|
||||
@@ -108,7 +110,7 @@ export default async function MatchingPage({
|
||||
<div>
|
||||
<label
|
||||
htmlFor="matchType"
|
||||
className="block text-sm font-medium text-gray-700"
|
||||
className="block text-sm font-medium text-ink"
|
||||
>
|
||||
요청 유형
|
||||
</label>
|
||||
@@ -116,7 +118,7 @@ export default async function MatchingPage({
|
||||
id="matchType"
|
||||
name="matchType"
|
||||
required
|
||||
className="mt-1 block w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
className="mt-1 block w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
>
|
||||
<option value="ACQUISITION">인수</option>
|
||||
<option value="DEMOLITION">철거</option>
|
||||
@@ -126,7 +128,7 @@ export default async function MatchingPage({
|
||||
<div>
|
||||
<label
|
||||
htmlFor="message"
|
||||
className="block text-sm font-medium text-gray-700"
|
||||
className="block text-sm font-medium text-ink"
|
||||
>
|
||||
메시지 (선택)
|
||||
</label>
|
||||
@@ -135,22 +137,22 @@ export default async function MatchingPage({
|
||||
name="message"
|
||||
rows={3}
|
||||
placeholder="매칭 요청 메시지를 입력하세요"
|
||||
className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
className="mt-1 block w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="inline-flex items-center rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
||||
className="rounded-full bg-ink px-6 py-3 text-sm font-medium text-warm-50 hover:bg-warm-800 transition-colors"
|
||||
>
|
||||
매칭 요청 보내기
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-6 rounded-lg border border-dashed border-gray-300 p-6 text-center">
|
||||
<p className="text-sm text-gray-500">
|
||||
<div className="mt-6 animate-fade-up rounded-2xl border-2 border-dashed border-ink/10 p-8 text-center">
|
||||
<p className="text-sm text-ink-muted">
|
||||
새로운 매칭 요청을 보내려면{' '}
|
||||
<Link href="/stores" className="text-blue-600 hover:underline">
|
||||
<Link href="/stores" className="text-warm-600 hover:text-warm-700">
|
||||
매장 검색
|
||||
</Link>
|
||||
에서 원하는 매장을 찾아보세요
|
||||
@@ -158,27 +160,27 @@ export default async function MatchingPage({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-8 space-y-4">
|
||||
<div className="mt-8 space-y-4 animate-fade-up">
|
||||
{requests.length === 0 ? (
|
||||
<p className="text-center text-sm text-gray-500">데이터가 없습니다</p>
|
||||
<p className="text-center text-sm text-ink-muted">데이터가 없습니다</p>
|
||||
) : (
|
||||
requests.map((req: (typeof requests)[number]) => {
|
||||
const statusInfo = STATUS_LABELS[req.status] ?? {
|
||||
label: req.status,
|
||||
color: 'bg-gray-100 text-gray-700',
|
||||
color: 'bg-warm-100 text-ink-muted',
|
||||
};
|
||||
return (
|
||||
<div key={req.publicId} className="rounded-lg border border-gray-200 bg-white p-5">
|
||||
<div key={req.publicId} className="rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-5 card-lift">
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="font-semibold text-gray-900">{req.store.listingTitle}</h3>
|
||||
<span className="rounded bg-gray-100 px-2 py-0.5 text-xs text-gray-600">
|
||||
<h3 className="font-display font-semibold text-ink">{req.store.listingTitle}</h3>
|
||||
<span className="rounded-full bg-warm-100 px-2.5 py-0.5 text-xs text-ink-muted">
|
||||
{MATCH_TYPE_LABELS[req.matchType] ?? req.matchType}
|
||||
</span>
|
||||
</div>
|
||||
{req.message && <p className="mt-1 text-sm text-gray-600">{req.message}</p>}
|
||||
<p className="mt-2 text-xs text-gray-400">
|
||||
{req.message && <p className="mt-1 text-sm text-ink-muted">{req.message}</p>}
|
||||
<p className="mt-2 text-xs text-ink-muted">
|
||||
요청일: {new Date(req.createdAt).toLocaleDateString('ko-KR')}
|
||||
</p>
|
||||
</div>
|
||||
@@ -189,10 +191,10 @@ export default async function MatchingPage({
|
||||
</span>
|
||||
</div>
|
||||
{req.status === 'ACCEPTED' && (
|
||||
<div className="mt-4 border-t border-gray-100 pt-3">
|
||||
<div className="mt-4 border-t border-ink/5 pt-3">
|
||||
<Link
|
||||
href={`/contracts?matchId=${req.publicId}`}
|
||||
className="text-sm text-blue-600 hover:underline"
|
||||
className="text-sm text-warm-600 hover:text-warm-700"
|
||||
>
|
||||
계약 진행하기 →
|
||||
</Link>
|
||||
|
||||
+187
-64
@@ -1,95 +1,218 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
const FEATURES = [
|
||||
{
|
||||
label: '폐업자',
|
||||
title: '깔끔한 마무리,\n새로운 시작',
|
||||
desc: '매장 정보를 등록하면 철거비 절감, 시설 처분, 지원금 신청까지 한 번에 해결됩니다.',
|
||||
href: '/stores/new',
|
||||
cta: '매장 등록',
|
||||
accent: 'bg-warm-500',
|
||||
num: '01',
|
||||
},
|
||||
{
|
||||
label: '창업자',
|
||||
title: '검증된 매장,\n합리적 시작',
|
||||
desc: '검증된 매장을 검색하고 매칭 요청을 보내 시설 인수와 인테리어 비용을 절감하세요.',
|
||||
href: '/stores',
|
||||
cta: '매장 검색',
|
||||
accent: 'bg-sage-500',
|
||||
num: '02',
|
||||
},
|
||||
{
|
||||
label: '철거·인테리어 업체',
|
||||
title: '안정적 수주,\n정확한 정산',
|
||||
desc: '인증 업체로 등록하면 안정적인 수주와 정산을 보장받을 수 있습니다.',
|
||||
href: '/vendors',
|
||||
cta: '업체 인증',
|
||||
accent: 'bg-warm-700',
|
||||
num: '03',
|
||||
},
|
||||
];
|
||||
|
||||
const SERVICES = [
|
||||
{
|
||||
title: '정부 지원금 가이드',
|
||||
desc: '폐업 관련 정부 지원금 자격을 확인하고, 체크리스트와 서류 준비를 도와드립니다.',
|
||||
href: '/subsidies',
|
||||
cta: '지원금 확인',
|
||||
icon: (
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 6v12m-3-2.818.879.659c1.171.879 3.07.879 4.242 0 1.172-.879 1.172-2.303 0-3.182C13.536 12.219 12.768 12 12 12c-.725 0-1.45-.22-2.003-.659-1.106-.879-1.106-2.303 0-3.182s2.9-.879 4.006 0l.415.33M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '안전한 거래',
|
||||
desc: '표준 계약서, 에스크로 결제, 검수 승인 시스템으로 안전한 거래를 보장합니다.',
|
||||
href: '/contracts',
|
||||
cta: '계약 관리',
|
||||
icon: (
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<main className="mx-auto max-w-7xl px-4 py-16">
|
||||
<div className="text-center">
|
||||
<h1 className="text-5xl font-bold text-gray-900">Startover</h1>
|
||||
<p className="mt-4 text-xl text-gray-600">폐업 · 양도 · 창업을 잇는 중개 플랫폼</p>
|
||||
<p className="mt-2 text-gray-500">
|
||||
매장 정보 1회 등록으로 창업자·철거업체·인테리어업체를 동시 연결합니다
|
||||
<main className="relative overflow-hidden bg-warm-50 font-body">
|
||||
{/* Gradient Mesh Background */}
|
||||
<div className="pointer-events-none absolute inset-0">
|
||||
<div
|
||||
className="mesh-blob bg-warm-200/60 absolute -top-32 -right-32 h-[500px] w-[500px]"
|
||||
style={{ animationDelay: '0s' }}
|
||||
/>
|
||||
<div
|
||||
className="mesh-blob bg-warm-300/40 absolute top-1/3 -left-24 h-[400px] w-[400px]"
|
||||
style={{ animationDelay: '-4s' }}
|
||||
/>
|
||||
<div
|
||||
className="mesh-blob absolute top-2/3 right-1/4 h-[350px] w-[350px] bg-sage-500/15"
|
||||
style={{ animationDelay: '-8s' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Hero Section */}
|
||||
<section className="relative mx-auto max-w-6xl px-6 pt-24 pb-20">
|
||||
<div className="grid grid-cols-1 items-end gap-12 lg:grid-cols-12">
|
||||
{/* Main headline — editorial style, large serif */}
|
||||
<div className="lg:col-span-7">
|
||||
<p
|
||||
className="animate-fade-up text-sm font-medium tracking-[0.2em] uppercase text-warm-600"
|
||||
style={{ animationDelay: '0.1s' }}
|
||||
>
|
||||
폐업 · 양도 · 창업을 잇다
|
||||
</p>
|
||||
<div className="mt-8 flex justify-center gap-4">
|
||||
<h1
|
||||
className="animate-fade-up mt-6 font-display text-6xl leading-[1.1] font-black tracking-tight text-ink sm:text-7xl lg:text-8xl"
|
||||
style={{ animationDelay: '0.25s' }}
|
||||
>
|
||||
Start
|
||||
<span className="relative">
|
||||
over
|
||||
<span className="absolute -bottom-2 left-0 h-3 w-full bg-warm-400/40" />
|
||||
</span>
|
||||
</h1>
|
||||
<p
|
||||
className="animate-fade-up mt-8 max-w-lg text-lg leading-relaxed font-light text-ink-light"
|
||||
style={{ animationDelay: '0.4s' }}
|
||||
>
|
||||
매장 정보 1회 등록으로
|
||||
<br />
|
||||
창업자 · 철거업체 · 인테리어업체를 동시에 연결합니다.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* CTA buttons — offset right for asymmetry */}
|
||||
<div className="flex flex-col gap-4 lg:col-span-5 lg:items-end lg:pb-4">
|
||||
<Link
|
||||
href="/stores"
|
||||
className="rounded-lg bg-blue-600 px-6 py-3 text-white hover:bg-blue-700"
|
||||
className="animate-slide-right group inline-flex items-center gap-3 rounded-full bg-ink px-8 py-4 text-base font-medium text-warm-50 transition-colors hover:bg-warm-800"
|
||||
style={{ animationDelay: '0.5s' }}
|
||||
>
|
||||
매장 둘러보기
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1">
|
||||
→
|
||||
</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/stores/new"
|
||||
className="rounded-lg border border-gray-300 px-6 py-3 text-gray-700 hover:bg-gray-100"
|
||||
className="animate-slide-right inline-flex items-center gap-3 rounded-full border-2 border-ink/15 px-8 py-4 text-base font-medium text-ink transition-colors hover:border-ink/40 hover:bg-warm-100"
|
||||
style={{ animationDelay: '0.65s' }}
|
||||
>
|
||||
매장 등록하기
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-20 grid grid-cols-1 gap-8 md:grid-cols-3">
|
||||
<div className="rounded-xl border border-gray-200 bg-white p-6">
|
||||
<div className="mb-3 text-2xl">🏪</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">폐업자</h3>
|
||||
<p className="mt-2 text-sm text-gray-600">
|
||||
매장 정보를 등록하면 철거비 절감, 시설 처분, 지원금 신청까지 한 번에 해결됩니다.
|
||||
</p>
|
||||
<Link
|
||||
href="/stores/new"
|
||||
className="mt-3 inline-block text-sm text-blue-600 hover:underline"
|
||||
{/* Decorative divider */}
|
||||
<div
|
||||
className="animate-fade-in mt-20 flex items-center gap-4"
|
||||
style={{ animationDelay: '0.8s' }}
|
||||
>
|
||||
매장 등록 →
|
||||
</Link>
|
||||
<div className="h-px flex-1 bg-ink/10" />
|
||||
<span className="text-xs tracking-[0.3em] uppercase text-ink-muted">
|
||||
누구를 위한 서비스인가요
|
||||
</span>
|
||||
<div className="h-px flex-1 bg-ink/10" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="rounded-xl border border-gray-200 bg-white p-6">
|
||||
<div className="mb-3 text-2xl">🔍</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">창업자</h3>
|
||||
<p className="mt-2 text-sm text-gray-600">
|
||||
검증된 매장을 검색하고 매칭 요청을 보내 시설 인수와 인테리어 비용을 절감하세요.
|
||||
</p>
|
||||
<Link href="/stores" className="mt-3 inline-block text-sm text-blue-600 hover:underline">
|
||||
매장 검색 →
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="rounded-xl border border-gray-200 bg-white p-6">
|
||||
<div className="mb-3 text-2xl">🏗️</div>
|
||||
<h3 className="text-lg font-semibold text-gray-900">철거·인테리어 업체</h3>
|
||||
<p className="mt-2 text-sm text-gray-600">
|
||||
인증 업체로 등록하면 안정적인 수주와 정산을 보장받을 수 있습니다.
|
||||
</p>
|
||||
<Link href="/vendors" className="mt-3 inline-block text-sm text-blue-600 hover:underline">
|
||||
업체 인증 →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-16 grid grid-cols-1 gap-8 md:grid-cols-2">
|
||||
<div className="rounded-xl border border-gray-200 bg-white p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900">정부 지원금 가이드</h3>
|
||||
<p className="mt-2 text-sm text-gray-600">
|
||||
폐업 관련 정부 지원금 자격을 확인하고, 체크리스트와 서류 준비를 도와드립니다.
|
||||
</p>
|
||||
{/* Feature Cards — asymmetric grid */}
|
||||
<section className="relative mx-auto max-w-6xl px-6 pb-24">
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-3">
|
||||
{FEATURES.map((f, i) => (
|
||||
<Link
|
||||
href="/subsidies"
|
||||
className="mt-3 inline-block text-sm text-blue-600 hover:underline"
|
||||
key={f.num}
|
||||
href={f.href}
|
||||
className="card-lift animate-fade-up group relative rounded-2xl border border-ink/5 bg-white/70 p-8 backdrop-blur-sm"
|
||||
style={{ animationDelay: `${0.9 + i * 0.12}s` }}
|
||||
>
|
||||
지원금 확인 →
|
||||
</Link>
|
||||
</div>
|
||||
{/* Number accent */}
|
||||
<span className="font-display text-5xl font-bold text-ink/[0.04]">
|
||||
{f.num}
|
||||
</span>
|
||||
|
||||
<div className="rounded-xl border border-gray-200 bg-white p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900">안전한 거래</h3>
|
||||
<p className="mt-2 text-sm text-gray-600">
|
||||
표준 계약서, 에스크로 결제, 검수 승인 시스템으로 안전한 거래를 보장합니다.
|
||||
{/* Accent bar */}
|
||||
<div className={`mt-4 h-1 w-10 rounded-full ${f.accent}`} />
|
||||
|
||||
<p className="mt-4 text-xs font-medium tracking-widest uppercase text-ink-muted">
|
||||
{f.label}
|
||||
</p>
|
||||
<Link
|
||||
href="/contracts"
|
||||
className="mt-3 inline-block text-sm text-blue-600 hover:underline"
|
||||
>
|
||||
계약 관리 →
|
||||
<h3 className="mt-3 font-display text-2xl leading-snug font-bold text-ink whitespace-pre-line">
|
||||
{f.title}
|
||||
</h3>
|
||||
<p className="mt-4 text-sm leading-relaxed text-ink-light">
|
||||
{f.desc}
|
||||
</p>
|
||||
|
||||
<span className="link-underline mt-6 inline-flex items-center gap-1 text-sm font-medium text-warm-700 transition-colors group-hover:text-warm-600">
|
||||
{f.cta}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1">
|
||||
→
|
||||
</span>
|
||||
</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Services section */}
|
||||
<section className="relative mx-auto max-w-6xl px-6 pb-32">
|
||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||
{SERVICES.map((s, i) => (
|
||||
<Link
|
||||
key={s.title}
|
||||
href={s.href}
|
||||
className="card-lift animate-fade-up group flex gap-6 rounded-2xl border border-ink/5 bg-white/50 p-8 backdrop-blur-sm"
|
||||
style={{ animationDelay: `${1.3 + i * 0.12}s` }}
|
||||
>
|
||||
<div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-warm-100 text-warm-700 transition-colors group-hover:bg-warm-200">
|
||||
{s.icon}
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-display text-xl font-bold text-ink">
|
||||
{s.title}
|
||||
</h3>
|
||||
<p className="mt-2 text-sm leading-relaxed text-ink-light">
|
||||
{s.desc}
|
||||
</p>
|
||||
<span className="link-underline mt-4 inline-flex items-center gap-1 text-sm font-medium text-warm-700 transition-colors group-hover:text-warm-600">
|
||||
{s.cta}
|
||||
<span className="inline-block transition-transform group-hover:translate-x-1">
|
||||
→
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Footer accent line */}
|
||||
<div className="mx-auto h-px max-w-6xl bg-gradient-to-r from-transparent via-ink/10 to-transparent" />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -72,24 +72,24 @@ export default async function StoreDetailPage({ params }: { params: Promise<{ id
|
||||
|
||||
const statusClass =
|
||||
store.dealStatus === 'OPEN'
|
||||
? 'bg-green-100 text-green-700'
|
||||
? 'bg-sage-500/10 text-sage-600'
|
||||
: store.dealStatus === 'MATCHING'
|
||||
? 'bg-yellow-100 text-yellow-700'
|
||||
: 'bg-gray-100 text-gray-700';
|
||||
? 'bg-warm-400/15 text-warm-700'
|
||||
: 'bg-ink/5 text-ink-muted';
|
||||
|
||||
return (
|
||||
<main className="mx-auto max-w-4xl px-4 py-8">
|
||||
<main className="mx-auto max-w-4xl px-6 py-10 font-body">
|
||||
<div className="mb-6">
|
||||
<Link href="/stores" className="text-sm text-blue-600 hover:underline">
|
||||
<Link href="/stores" className="text-sm text-warm-600 hover:text-warm-700">
|
||||
← 매장 목록으로
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-gray-200 bg-white p-8">
|
||||
<div className="animate-fade-up rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-8">
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">{store.listingTitle}</h1>
|
||||
<p className="mt-1 text-sm text-gray-500">매장 ID: {store.publicId}</p>
|
||||
<h1 className="font-display text-3xl font-bold text-ink">{store.listingTitle}</h1>
|
||||
<p className="mt-1 text-sm text-ink-muted">매장 ID: {store.publicId}</p>
|
||||
</div>
|
||||
<span className={`rounded-full px-3 py-1 text-sm font-medium ${statusClass}`}>
|
||||
{statusLabel}
|
||||
@@ -98,7 +98,7 @@ export default async function StoreDetailPage({ params }: { params: Promise<{ id
|
||||
|
||||
{/* 기본 정보 */}
|
||||
<section className="mt-8">
|
||||
<h2 className="mb-3 text-lg font-semibold text-gray-900">기본 정보</h2>
|
||||
<h2 className="font-display text-xl font-bold text-ink mb-4">기본 정보</h2>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<InfoItem label="지역" value={store.regionCluster?.nameKo ?? '-'} />
|
||||
<InfoItem label="업종" value={store.industryLeaf?.nameKo ?? '-'} />
|
||||
@@ -109,7 +109,7 @@ export default async function StoreDetailPage({ params }: { params: Promise<{ id
|
||||
|
||||
{/* 임대 정보 */}
|
||||
<section className="mt-8">
|
||||
<h2 className="mb-3 text-lg font-semibold text-gray-900">임대 정보</h2>
|
||||
<h2 className="font-display text-xl font-bold text-ink mb-4">임대 정보</h2>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<InfoItem
|
||||
label="보증금"
|
||||
@@ -148,7 +148,7 @@ export default async function StoreDetailPage({ params }: { params: Promise<{ id
|
||||
|
||||
{/* 시설 정보 */}
|
||||
<section className="mt-8">
|
||||
<h2 className="mb-3 text-lg font-semibold text-gray-900">시설 정보</h2>
|
||||
<h2 className="font-display text-xl font-bold text-ink mb-4">시설 정보</h2>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<InfoItem
|
||||
label="면적"
|
||||
@@ -164,22 +164,22 @@ export default async function StoreDetailPage({ params }: { params: Promise<{ id
|
||||
/>
|
||||
</div>
|
||||
{store.facility?.kitchenEquipmentSummary && (
|
||||
<div className="mt-3">
|
||||
<p className="text-sm text-gray-500">시설 설명</p>
|
||||
<p className="mt-1 text-sm text-gray-900">{store.facility.kitchenEquipmentSummary}</p>
|
||||
<div className="mt-4">
|
||||
<p className="text-sm text-ink-muted">시설 설명</p>
|
||||
<p className="mt-1 text-sm text-ink">{store.facility.kitchenEquipmentSummary}</p>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* 액션 버튼 */}
|
||||
<div className="mt-10 flex flex-wrap gap-3 border-t border-gray-200 pt-6">
|
||||
<div className="mt-10 flex flex-wrap gap-3 border-t border-ink/5 pt-6">
|
||||
{store.reviewStatus === 'DRAFT' && (
|
||||
<>
|
||||
<form action={handleSubmitForReview}>
|
||||
<input type="hidden" name="storePublicId" value={store.publicId} />
|
||||
<button
|
||||
type="submit"
|
||||
className="rounded-lg bg-blue-600 px-6 py-2.5 text-sm font-medium text-white hover:bg-blue-700"
|
||||
className="rounded-full bg-ink px-6 py-3 text-sm font-medium text-warm-50 hover:bg-warm-800 transition-colors"
|
||||
>
|
||||
검토 제출
|
||||
</button>
|
||||
@@ -188,7 +188,7 @@ export default async function StoreDetailPage({ params }: { params: Promise<{ id
|
||||
<input type="hidden" name="storePublicId" value={store.publicId} />
|
||||
<button
|
||||
type="submit"
|
||||
className="rounded-lg border border-red-300 px-6 py-2.5 text-sm text-red-600 hover:bg-red-50"
|
||||
className="rounded-full border-2 border-red-300 px-6 py-3 text-sm text-red-600 hover:bg-red-50"
|
||||
>
|
||||
삭제
|
||||
</button>
|
||||
@@ -199,20 +199,20 @@ export default async function StoreDetailPage({ params }: { params: Promise<{ id
|
||||
<>
|
||||
<Link
|
||||
href={`/matching?storeId=${id}`}
|
||||
className="rounded-lg bg-blue-600 px-6 py-2.5 text-sm font-medium text-white hover:bg-blue-700"
|
||||
className="rounded-full bg-ink px-6 py-3 text-sm font-medium text-warm-50 hover:bg-warm-800 transition-colors"
|
||||
>
|
||||
매칭 요청 보내기
|
||||
</Link>
|
||||
<Link
|
||||
href={`/subsidies?storeId=${id}`}
|
||||
className="rounded-lg border border-gray-300 px-6 py-2.5 text-sm text-gray-700 hover:bg-gray-50"
|
||||
className="rounded-full border-2 border-ink/15 px-6 py-3 text-sm font-medium text-ink hover:border-ink/40 transition-colors"
|
||||
>
|
||||
지원금 확인
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
{store.reviewStatus === 'SUBMITTED' && (
|
||||
<p className="text-sm text-yellow-600">검토 대기 중입니다</p>
|
||||
<p className="text-sm text-ink-muted">검토 대기 중입니다</p>
|
||||
)}
|
||||
{store.reviewStatus === 'REJECTED' && (
|
||||
<p className="text-sm text-red-600">반려되었습니다. 수정 후 다시 제출해주세요.</p>
|
||||
@@ -226,8 +226,8 @@ export default async function StoreDetailPage({ params }: { params: Promise<{ id
|
||||
function InfoItem({ label, value }: { label: string; value: string }) {
|
||||
return (
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">{label}</p>
|
||||
<p className="mt-0.5 text-sm font-medium text-gray-900">{value}</p>
|
||||
<p className="text-sm text-ink-muted">{label}</p>
|
||||
<p className="mt-0.5 text-sm font-medium text-ink">{value}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,48 +10,50 @@ export default function NewStorePage() {
|
||||
const [state, formAction, isPending] = useActionState(createStoreDraftAction, initialState);
|
||||
|
||||
return (
|
||||
<main className="mx-auto max-w-3xl px-4 py-8">
|
||||
<main className="mx-auto max-w-3xl px-6 py-10 font-body">
|
||||
<div className="mb-6">
|
||||
<Link href="/stores" className="text-sm text-blue-600 hover:underline">
|
||||
← 매장 목록으로
|
||||
<Link href="/stores" className="text-sm text-warm-600 hover:text-warm-700">
|
||||
← 매장 목록으로
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<h1 className="text-2xl font-bold text-gray-900">매장 등록</h1>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
<div className="animate-fade-up">
|
||||
<h1 className="font-display text-3xl font-bold text-ink">매장 등록</h1>
|
||||
<p className="mt-1 text-sm text-ink-muted">
|
||||
매장 정보를 등록하면 창업자, 철거업체, 인테리어업체와 매칭됩니다
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{state.error && (
|
||||
<div className="mt-4 rounded-md border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
|
||||
<div className="mt-4 rounded-2xl border border-red-200/60 bg-red-50 px-5 py-4 text-sm text-red-700">
|
||||
{state.error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form action={formAction} className="mt-8 space-y-8">
|
||||
<form action={formAction} className="mt-8 space-y-8 animate-fade-up">
|
||||
{/* 기본 정보 */}
|
||||
<section>
|
||||
<h2 className="mb-4 text-lg font-semibold text-gray-900">기본 정보</h2>
|
||||
<div className="space-y-4 rounded-lg border border-gray-200 bg-white p-6">
|
||||
<h2 className="font-display text-xl font-bold text-ink mb-4">기본 정보</h2>
|
||||
<div className="space-y-4 rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">매장명 *</label>
|
||||
<label className="block text-sm text-ink-muted mb-1">매장명 *</label>
|
||||
<input
|
||||
type="text"
|
||||
name="listingTitle"
|
||||
placeholder="예: 강남역 카페 양도"
|
||||
required
|
||||
defaultValue={state.fieldValues?.listingTitle}
|
||||
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">지역 *</label>
|
||||
<label className="block text-sm text-ink-muted mb-1">지역 *</label>
|
||||
<select
|
||||
name="regionClusterCode"
|
||||
required
|
||||
defaultValue={state.fieldValues?.regionClusterCode}
|
||||
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
>
|
||||
<option value="">지역 선택</option>
|
||||
<option value="KR.BETA.GANGNAM_CORE">강남권 (역삼/선릉/논현)</option>
|
||||
@@ -59,12 +61,12 @@ export default function NewStorePage() {
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">업종 *</label>
|
||||
<label className="block text-sm text-ink-muted mb-1">업종 *</label>
|
||||
<select
|
||||
name="industryLeafCode"
|
||||
required
|
||||
defaultValue={state.fieldValues?.industryLeafCode}
|
||||
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
>
|
||||
<option value="">업종 선택</option>
|
||||
<option value="FNB.CAFE">카페</option>
|
||||
@@ -75,14 +77,14 @@ export default function NewStorePage() {
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">도로명 주소 *</label>
|
||||
<label className="block text-sm text-ink-muted mb-1">도로명 주소 *</label>
|
||||
<input
|
||||
type="text"
|
||||
name="roadAddress"
|
||||
placeholder="예: 서울시 강남구 테헤란로 123"
|
||||
required
|
||||
defaultValue={state.fieldValues?.roadAddress}
|
||||
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,51 +92,49 @@ export default function NewStorePage() {
|
||||
|
||||
{/* 임대 정보 */}
|
||||
<section>
|
||||
<h2 className="mb-4 text-lg font-semibold text-gray-900">임대 정보</h2>
|
||||
<div className="space-y-4 rounded-lg border border-gray-200 bg-white p-6">
|
||||
<h2 className="font-display text-xl font-bold text-ink mb-4">임대 정보</h2>
|
||||
<div className="space-y-4 rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-6">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">보증금 (원)</label>
|
||||
<label className="block text-sm text-ink-muted mb-1">보증금 (원)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="depositAmount"
|
||||
placeholder="50000000"
|
||||
defaultValue={state.fieldValues?.depositAmount}
|
||||
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">월세 (원)</label>
|
||||
<label className="block text-sm text-ink-muted mb-1">월세 (원)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="monthlyRentAmount"
|
||||
placeholder="3000000"
|
||||
defaultValue={state.fieldValues?.monthlyRentAmount}
|
||||
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">권리금 (원)</label>
|
||||
<label className="block text-sm text-ink-muted mb-1">권리금 (원)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="premiumAmount"
|
||||
placeholder="30000000"
|
||||
defaultValue={state.fieldValues?.premiumAmount}
|
||||
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">
|
||||
임대 잔여 기간 (개월)
|
||||
</label>
|
||||
<label className="block text-sm text-ink-muted mb-1">임대 잔여 기간 (개월)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="remainingLeaseMonths"
|
||||
placeholder="18"
|
||||
defaultValue={state.fieldValues?.remainingLeaseMonths}
|
||||
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -143,39 +143,39 @@ export default function NewStorePage() {
|
||||
|
||||
{/* 시설 정보 */}
|
||||
<section>
|
||||
<h2 className="mb-4 text-lg font-semibold text-gray-900">시설 정보</h2>
|
||||
<div className="space-y-4 rounded-lg border border-gray-200 bg-white p-6">
|
||||
<h2 className="font-display text-xl font-bold text-ink mb-4">시설 정보</h2>
|
||||
<div className="space-y-4 rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-6">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">면적 (㎡)</label>
|
||||
<label className="block text-sm text-ink-muted mb-1">면적 (㎡)</label>
|
||||
<input
|
||||
type="number"
|
||||
name="exclusiveAreaSqm"
|
||||
placeholder="82.6"
|
||||
step="0.01"
|
||||
defaultValue={state.fieldValues?.exclusiveAreaSqm}
|
||||
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">층수</label>
|
||||
<label className="block text-sm text-ink-muted mb-1">층수</label>
|
||||
<input
|
||||
type="number"
|
||||
name="floorLevel"
|
||||
placeholder="1"
|
||||
defaultValue={state.fieldValues?.floorLevel}
|
||||
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">시설 설명</label>
|
||||
<label className="block text-sm text-ink-muted mb-1">시설 설명</label>
|
||||
<textarea
|
||||
rows={3}
|
||||
name="kitchenEquipmentSummary"
|
||||
placeholder="주방 시설, 인테리어 상태, 포함 장비 등을 자유롭게 작성해주세요"
|
||||
defaultValue={state.fieldValues?.kitchenEquipmentSummary}
|
||||
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
||||
className="w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -186,13 +186,13 @@ export default function NewStorePage() {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
className="rounded-lg bg-blue-600 px-6 py-2.5 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50"
|
||||
className="rounded-full bg-ink px-6 py-3 text-sm font-medium text-warm-50 hover:bg-warm-800 transition-colors disabled:opacity-50"
|
||||
>
|
||||
{isPending ? '등록 중...' : '매장 등록'}
|
||||
</button>
|
||||
<Link
|
||||
href="/stores"
|
||||
className="rounded-lg border border-gray-300 px-6 py-2.5 text-sm text-gray-700 hover:bg-gray-50"
|
||||
className="rounded-full border-2 border-ink/15 px-6 py-3 text-sm font-medium text-ink hover:border-ink/40 transition-colors"
|
||||
>
|
||||
취소
|
||||
</Link>
|
||||
|
||||
@@ -35,17 +35,17 @@ export default async function StoresPage({
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="mx-auto max-w-7xl px-4 py-8">
|
||||
<div className="flex items-center justify-between">
|
||||
<main className="mx-auto max-w-7xl px-6 py-10 font-body">
|
||||
<div className="animate-fade-up flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">매장 검색</h1>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
<h1 className="font-display text-3xl font-bold text-ink">매장 검색</h1>
|
||||
<p className="mt-1 text-sm text-ink-muted">
|
||||
공개된 매장을 검색하고 매칭 요청을 보내보세요
|
||||
</p>
|
||||
</div>
|
||||
<Link
|
||||
href="/stores/new"
|
||||
className="rounded-lg bg-blue-600 px-4 py-2 text-sm text-white hover:bg-blue-700"
|
||||
className="rounded-full bg-ink px-6 py-3 text-sm font-medium text-warm-50 hover:bg-warm-800 transition-colors"
|
||||
>
|
||||
매장 등록
|
||||
</Link>
|
||||
@@ -55,9 +55,9 @@ export default async function StoresPage({
|
||||
<StoreFilters />
|
||||
|
||||
{/* 매장 목록 */}
|
||||
<div className="mt-6 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
<div className="mt-6 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 animate-fade-up">
|
||||
{stores.length === 0 ? (
|
||||
<div className="col-span-full py-16 text-center text-sm text-gray-400">
|
||||
<div className="col-span-full py-16 text-center text-sm text-ink-muted">
|
||||
등록된 매장이 없습니다
|
||||
</div>
|
||||
) : (
|
||||
@@ -65,17 +65,17 @@ export default async function StoresPage({
|
||||
<Link
|
||||
key={store.publicId}
|
||||
href={`/stores/${store.publicId}`}
|
||||
className="block rounded-lg border border-gray-200 bg-white p-5 transition hover:border-blue-300 hover:shadow-md"
|
||||
className="block rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-5 card-lift"
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<h3 className="font-semibold text-gray-900">{store.listingTitle}</h3>
|
||||
<h3 className="font-display font-semibold text-ink">{store.listingTitle}</h3>
|
||||
<span
|
||||
className={`rounded-full px-2 py-0.5 text-xs font-medium ${
|
||||
store.dealStatus === 'OPEN'
|
||||
? 'bg-green-100 text-green-700'
|
||||
? 'bg-sage-500/10 text-sage-600'
|
||||
: store.dealStatus === 'MATCHING'
|
||||
? 'bg-yellow-100 text-yellow-700'
|
||||
: 'bg-gray-100 text-gray-700'
|
||||
? 'bg-warm-400/15 text-warm-700'
|
||||
: 'bg-ink/5 text-ink-muted'
|
||||
}`}
|
||||
>
|
||||
{store.dealStatus === 'OPEN'
|
||||
@@ -85,29 +85,33 @@ export default async function StoresPage({
|
||||
: store.dealStatus}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
<p className="mt-1 text-sm text-ink-muted">
|
||||
{store.regionCluster?.nameKo ?? '-'} · {store.industryLeaf?.nameKo ?? '-'}
|
||||
</p>
|
||||
<div className="mt-3 flex gap-4 text-sm text-gray-600">
|
||||
<div className="mt-3 flex gap-4 text-sm text-ink-muted">
|
||||
<span>
|
||||
보증금{' '}
|
||||
<span className="font-medium text-ink">
|
||||
{store.lease?.depositAmount != null
|
||||
? `${Number(store.lease.depositAmount).toLocaleString('ko-KR')}원`
|
||||
: '-'}
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
월세{' '}
|
||||
<span className="font-medium text-ink">
|
||||
{store.lease?.monthlyRentAmount != null
|
||||
? `${Number(store.lease.monthlyRentAmount).toLocaleString('ko-KR')}원`
|
||||
: '-'}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-8 text-center text-sm text-gray-400">
|
||||
<div className="mt-8 text-center text-sm text-ink-muted">
|
||||
베타 서비스: 강남권·마포권 F&B 매장만 지원합니다
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -48,12 +48,12 @@ export function StoreFilters() {
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="mt-6 flex flex-wrap gap-3 rounded-lg border border-gray-200 bg-white p-4"
|
||||
className="mt-6 flex flex-wrap gap-3 rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-5"
|
||||
>
|
||||
<select
|
||||
name="region"
|
||||
defaultValue={searchParams.get('region') ?? ''}
|
||||
className="rounded-md border border-gray-300 px-3 py-2 text-sm"
|
||||
className="rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
>
|
||||
{REGION_OPTIONS.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
@@ -64,7 +64,7 @@ export function StoreFilters() {
|
||||
<select
|
||||
name="industry"
|
||||
defaultValue={searchParams.get('industry') ?? ''}
|
||||
className="rounded-md border border-gray-300 px-3 py-2 text-sm"
|
||||
className="rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
>
|
||||
{INDUSTRY_OPTIONS.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
@@ -75,7 +75,7 @@ export function StoreFilters() {
|
||||
<select
|
||||
name="status"
|
||||
defaultValue={searchParams.get('status') ?? ''}
|
||||
className="rounded-md border border-gray-300 px-3 py-2 text-sm"
|
||||
className="rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
>
|
||||
{STATUS_OPTIONS.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
@@ -85,7 +85,7 @@ export function StoreFilters() {
|
||||
</select>
|
||||
<button
|
||||
type="submit"
|
||||
className="rounded-md bg-gray-100 px-4 py-2 text-sm text-gray-700 hover:bg-gray-200"
|
||||
className="rounded-full bg-ink px-6 py-3 text-sm font-medium text-warm-50 hover:bg-warm-800 transition-colors"
|
||||
>
|
||||
검색
|
||||
</button>
|
||||
|
||||
@@ -7,11 +7,11 @@ import { createSubsidyCaseService } from '@/services/subsidy-case-service';
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
const STATUS_MAP: Record<string, { label: string; color: string }> = {
|
||||
DOCUMENTS_PENDING: { label: '서류 준비 중', color: 'bg-yellow-100 text-yellow-700' },
|
||||
READY_TO_SUBMIT: { label: '제출 준비 완료', color: 'bg-blue-100 text-blue-700' },
|
||||
SUBMITTED: { label: '제출됨', color: 'bg-purple-100 text-purple-700' },
|
||||
REVIEWING: { label: '검토 중', color: 'bg-orange-100 text-orange-700' },
|
||||
APPROVED: { label: '승인', color: 'bg-green-100 text-green-700' },
|
||||
DOCUMENTS_PENDING: { label: '서류 준비 중', color: 'bg-warm-400/15 text-warm-700' },
|
||||
READY_TO_SUBMIT: { label: '제출 준비 완료', color: 'bg-sage-500/10 text-sage-600' },
|
||||
SUBMITTED: { label: '제출됨', color: 'bg-warm-400/15 text-warm-700' },
|
||||
REVIEWING: { label: '검토 중', color: 'bg-warm-400/15 text-warm-700' },
|
||||
APPROVED: { label: '승인', color: 'bg-sage-500/10 text-sage-600' },
|
||||
REJECTED: { label: '반려', color: 'bg-red-100 text-red-700' },
|
||||
};
|
||||
|
||||
@@ -76,16 +76,18 @@ export default async function SubsidiesPage({
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="mx-auto max-w-4xl px-4 py-8">
|
||||
<h1 className="text-2xl font-bold text-gray-900">정부 지원금</h1>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
<main className="mx-auto max-w-4xl px-6 py-10 font-body">
|
||||
<div className="animate-fade-up">
|
||||
<h1 className="font-display text-3xl font-bold text-ink">정부 지원금</h1>
|
||||
<p className="mt-1 text-sm text-ink-muted">
|
||||
폐업 관련 정부 지원금 자격 확인과 서류 제출을 도와드립니다
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 안내 배너 */}
|
||||
<div className="mt-6 rounded-lg border border-blue-200 bg-blue-50 p-4">
|
||||
<h3 className="text-sm font-semibold text-blue-800">가이드형 지원금 대행 서비스</h3>
|
||||
<p className="mt-1 text-sm text-blue-700">
|
||||
<div className="mt-6 animate-fade-up rounded-2xl border border-warm-200 bg-warm-100/50 p-5">
|
||||
<h3 className="font-display text-sm font-semibold text-ink">가이드형 지원금 대행 서비스</h3>
|
||||
<p className="mt-1 text-sm text-ink-muted">
|
||||
Startover은 지원금 신청 보조 파트너로서, 체크리스트·서류 업로드·진행 상태·운영자 피드백을
|
||||
제공합니다.
|
||||
</p>
|
||||
@@ -93,32 +95,32 @@ export default async function SubsidiesPage({
|
||||
|
||||
{/* 지원금 신청 폼 또는 안내 */}
|
||||
{params.storeId ? (
|
||||
<div className="mt-6 rounded-lg border border-gray-200 bg-white p-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900">지원금 신청</h2>
|
||||
<div className="mt-6 animate-fade-up rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-5">
|
||||
<h2 className="font-display text-xl font-bold text-ink">지원금 신청</h2>
|
||||
{serviceError ? (
|
||||
<p className="mt-2 text-sm text-red-600">{serviceError}</p>
|
||||
) : store ? (
|
||||
<form action={handleCreateSubsidyCase} className="mt-4 space-y-4">
|
||||
<div>
|
||||
<p className="text-sm text-gray-500">매장</p>
|
||||
<p className="mt-0.5 font-medium text-gray-900">{store.listingTitle}</p>
|
||||
<p className="text-sm text-ink-muted">매장</p>
|
||||
<p className="mt-0.5 font-medium text-ink">{store.listingTitle}</p>
|
||||
<input type="hidden" name="storePublicId" value={store.publicId} />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="programCode" className="block text-sm font-medium text-gray-700">
|
||||
<label htmlFor="programCode" className="block text-sm font-medium text-ink">
|
||||
지원 프로그램
|
||||
</label>
|
||||
<select
|
||||
id="programCode"
|
||||
name="programCode"
|
||||
className="mt-1 block w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
|
||||
className="mt-1 block w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
>
|
||||
<option value="SMALL_BIZ_CLOSURE_2024">소상공인 폐업지원 2024</option>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full rounded-md bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
||||
className="w-full rounded-full bg-ink px-6 py-3 text-sm font-medium text-warm-50 hover:bg-warm-800 transition-colors"
|
||||
>
|
||||
지원금 신청
|
||||
</button>
|
||||
@@ -126,10 +128,10 @@ export default async function SubsidiesPage({
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<div className="mt-8 rounded-lg border border-dashed border-gray-300 p-6 text-center">
|
||||
<p className="text-sm text-gray-500">
|
||||
<div className="mt-8 animate-fade-up rounded-2xl border-2 border-dashed border-ink/10 p-8 text-center">
|
||||
<p className="text-sm text-ink-muted">
|
||||
새 지원금 케이스를 시작하려면{' '}
|
||||
<Link href="/stores" className="text-blue-600 hover:underline">
|
||||
<Link href="/stores" className="text-warm-600 hover:text-warm-700">
|
||||
등록된 매장
|
||||
</Link>
|
||||
이 필요합니다
|
||||
@@ -138,23 +140,23 @@ export default async function SubsidiesPage({
|
||||
)}
|
||||
|
||||
{/* 케이스 목록 */}
|
||||
<div className="mt-6 space-y-4">
|
||||
<div className="mt-6 space-y-4 animate-fade-up">
|
||||
{cases.length === 0 ? (
|
||||
<p className="text-center text-sm text-gray-500">데이터가 없습니다</p>
|
||||
<p className="text-center text-sm text-ink-muted">데이터가 없습니다</p>
|
||||
) : (
|
||||
cases.map((c: (typeof cases)[number]) => {
|
||||
const statusInfo = STATUS_MAP[c.status] ?? {
|
||||
label: c.status,
|
||||
color: 'bg-gray-100 text-gray-700',
|
||||
color: 'bg-warm-100 text-ink-muted',
|
||||
};
|
||||
const total = c.checklistItems.length;
|
||||
const checked = c.checklistItems.filter((item: (typeof c.checklistItems)[number]) => item.status === 'CHECKED').length;
|
||||
return (
|
||||
<div key={c.publicId} className="rounded-lg border border-gray-200 bg-white p-5">
|
||||
<div key={c.publicId} className="rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-5 card-lift">
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900">{c.store.listingTitle}</h3>
|
||||
<p className="mt-1 text-xs text-gray-400">
|
||||
<h3 className="font-display font-semibold text-ink">{c.store.listingTitle}</h3>
|
||||
<p className="mt-1 text-xs text-ink-muted">
|
||||
신청일: {new Date(c.createdAt).toLocaleDateString('ko-KR')}
|
||||
</p>
|
||||
</div>
|
||||
@@ -168,14 +170,14 @@ export default async function SubsidiesPage({
|
||||
{/* 체크리스트 진행률 */}
|
||||
<div className="mt-4">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-600">체크리스트 진행</span>
|
||||
<span className="text-gray-900">
|
||||
<span className="text-ink-muted">체크리스트 진행</span>
|
||||
<span className="font-medium text-ink">
|
||||
{checked}/{total}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-1 h-2 rounded-full bg-gray-200">
|
||||
<div className="mt-1 h-2 rounded-full bg-warm-100">
|
||||
<div
|
||||
className="h-2 rounded-full bg-blue-500"
|
||||
className="h-2 rounded-full bg-warm-500 transition-all"
|
||||
style={{ width: total > 0 ? `${(checked / total) * 100}%` : '0%' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Vendored
+41
-27
@@ -6,10 +6,10 @@ export const dynamic = 'force-dynamic';
|
||||
const prisma = createPrismaClient();
|
||||
|
||||
const STATUS_BADGE: Record<string, { label: string; className: string }> = {
|
||||
APPLIED: { label: '심사 대기', className: 'bg-yellow-100 text-yellow-700' },
|
||||
APPROVED: { label: '인증됨', className: 'bg-green-100 text-green-700' },
|
||||
APPLIED: { label: '심사 대기', className: 'bg-warm-400/15 text-warm-700' },
|
||||
APPROVED: { label: '인증됨', className: 'bg-sage-500/10 text-sage-600' },
|
||||
SUSPENDED: { label: '중지', className: 'bg-red-100 text-red-700' },
|
||||
REJECTED: { label: '반려', className: 'bg-gray-100 text-gray-700' },
|
||||
REJECTED: { label: '반려', className: 'bg-warm-100 text-ink-muted' },
|
||||
};
|
||||
|
||||
const VENDOR_TYPE_LABEL: Record<string, string> = {
|
||||
@@ -36,28 +36,42 @@ export default async function VendorsPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="mx-auto max-w-4xl px-4 py-8">
|
||||
<h1 className="text-2xl font-bold text-gray-900">업체 인증</h1>
|
||||
<p className="mt-1 text-sm text-gray-500">
|
||||
<main className="mx-auto max-w-4xl px-6 py-10 font-body">
|
||||
<div className="animate-fade-up">
|
||||
<h1 className="font-display text-3xl font-bold text-ink">업체 인증</h1>
|
||||
<p className="mt-1 text-sm text-ink-muted">
|
||||
인증 업체로 등록하여 안정적인 수주와 정산을 보장받으세요
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 인증 혜택 */}
|
||||
<div className="mt-6 grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||
<div className="rounded-lg border border-gray-200 bg-white p-5">
|
||||
<div className="mb-2 text-xl">✅</div>
|
||||
<h3 className="font-semibold text-gray-900">신뢰 배지</h3>
|
||||
<p className="mt-1 text-sm text-gray-600">인증 업체 마크로 고객 신뢰를 확보합니다</p>
|
||||
<div className="mt-6 grid grid-cols-1 gap-4 md:grid-cols-3 animate-fade-up">
|
||||
<div className="rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-5 card-lift">
|
||||
<div className="mb-3 flex h-10 w-10 items-center justify-center rounded-xl bg-sage-500/10 text-sage-600">
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className="rounded-lg border border-gray-200 bg-white p-5">
|
||||
<div className="mb-2 text-xl">📋</div>
|
||||
<h3 className="font-semibold text-gray-900">우선 매칭</h3>
|
||||
<p className="mt-1 text-sm text-gray-600">인증 업체가 매칭 후보로 우선 노출됩니다</p>
|
||||
<h3 className="font-display font-semibold text-ink">신뢰 배지</h3>
|
||||
<p className="mt-1 text-sm text-ink-muted">인증 업체 마크로 고객 신뢰를 확보합니다</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-gray-200 bg-white p-5">
|
||||
<div className="mb-2 text-xl">💰</div>
|
||||
<h3 className="font-semibold text-gray-900">안전 정산</h3>
|
||||
<p className="mt-1 text-sm text-gray-600">에스크로 기반 정산으로 대금을 보호합니다</p>
|
||||
<div className="rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-5 card-lift">
|
||||
<div className="mb-3 flex h-10 w-10 items-center justify-center rounded-xl bg-warm-400/15 text-warm-700">
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M3.75 12h16.5m-16.5 3.75h16.5M3.75 19.5h16.5M5.625 4.5h12.75a1.875 1.875 0 0 1 0 3.75H5.625a1.875 1.875 0 0 1 0-3.75Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="font-display font-semibold text-ink">우선 매칭</h3>
|
||||
<p className="mt-1 text-sm text-ink-muted">인증 업체가 매칭 후보로 우선 노출됩니다</p>
|
||||
</div>
|
||||
<div className="rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-5 card-lift">
|
||||
<div className="mb-3 flex h-10 w-10 items-center justify-center rounded-xl bg-warm-400/15 text-warm-700">
|
||||
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="font-display font-semibold text-ink">안전 정산</h3>
|
||||
<p className="mt-1 text-sm text-ink-muted">에스크로 기반 정산으로 대금을 보호합니다</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -65,16 +79,16 @@ export default async function VendorsPage() {
|
||||
<VendorApplicationForm />
|
||||
|
||||
{/* 인증 현황 */}
|
||||
<div className="mt-8">
|
||||
<h2 className="mb-4 text-lg font-semibold text-gray-900">인증 현황</h2>
|
||||
<div className="mt-8 animate-fade-up">
|
||||
<h2 className="mb-4 font-display text-xl font-bold text-ink">인증 현황</h2>
|
||||
{vendors.length === 0 ? (
|
||||
<p className="text-sm text-gray-500">등록된 업체가 없습니다.</p>
|
||||
<p className="text-sm text-ink-muted">등록된 업체가 없습니다.</p>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{vendors.map((vendor: (typeof vendors)[number]) => {
|
||||
const badge = STATUS_BADGE[vendor.certificationStatus] ?? {
|
||||
label: '심사 대기',
|
||||
className: 'bg-yellow-100 text-yellow-700',
|
||||
className: 'bg-warm-400/15 text-warm-700',
|
||||
};
|
||||
const regionNames = vendor.coverageRegions.map((cr: (typeof vendor.coverageRegions)[number]) => cr.region.nameKo).join(', ');
|
||||
const typeLabel = VENDOR_TYPE_LABEL[vendor.vendorType] ?? vendor.vendorType;
|
||||
@@ -82,12 +96,12 @@ export default async function VendorsPage() {
|
||||
return (
|
||||
<div
|
||||
key={vendor.publicId}
|
||||
className="rounded-lg border border-gray-200 bg-white p-5"
|
||||
className="rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-5 card-lift"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900">{vendor.businessName}</h3>
|
||||
<p className="mt-0.5 text-sm text-gray-500">
|
||||
<h3 className="font-display font-semibold text-ink">{vendor.businessName}</h3>
|
||||
<p className="mt-0.5 text-sm text-ink-muted">
|
||||
{typeLabel}
|
||||
{regionNames ? ` · ${regionNames}` : ''}
|
||||
</p>
|
||||
@@ -98,7 +112,7 @@ export default async function VendorsPage() {
|
||||
{badge.label}
|
||||
</span>
|
||||
</div>
|
||||
<p className="mt-2 text-xs text-gray-400">
|
||||
<p className="mt-2 text-xs text-ink-muted">
|
||||
신청일: {vendor.createdAt.toISOString().slice(0, 10)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
+18
-18
@@ -19,39 +19,39 @@ export default function VendorApplicationForm() {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mt-8">
|
||||
<h2 className="mb-4 text-lg font-semibold text-gray-900">인증 신청</h2>
|
||||
<div className="mt-8 animate-fade-up">
|
||||
<h2 className="mb-4 font-display text-xl font-bold text-ink">인증 신청</h2>
|
||||
|
||||
{state && (
|
||||
<div
|
||||
className={`mb-4 rounded-md px-4 py-3 text-sm ${
|
||||
className={`mb-4 rounded-2xl px-4 py-3 text-sm ${
|
||||
state.success
|
||||
? 'bg-green-50 text-green-700'
|
||||
: 'bg-red-50 text-red-700'
|
||||
? 'bg-sage-500/10 text-sage-600'
|
||||
: 'bg-red-100 text-red-700'
|
||||
}`}
|
||||
>
|
||||
{state.message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form action={formAction} className="space-y-4 rounded-lg border border-gray-200 bg-white p-6">
|
||||
<form action={formAction} className="space-y-4 rounded-2xl border border-ink/5 bg-white/70 backdrop-blur-sm p-6">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">업체명 *</label>
|
||||
<label className="block text-sm font-medium text-ink">업체명 *</label>
|
||||
<input
|
||||
name="businessName"
|
||||
type="text"
|
||||
placeholder="예: (주)클린철거"
|
||||
required
|
||||
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
||||
className="mt-1 w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">업체 유형 *</label>
|
||||
<label className="block text-sm font-medium text-ink">업체 유형 *</label>
|
||||
<select
|
||||
name="vendorType"
|
||||
required
|
||||
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm"
|
||||
className="mt-1 w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
>
|
||||
<option value="">선택</option>
|
||||
<option value="DEMOLITION">철거</option>
|
||||
@@ -63,36 +63,36 @@ export default function VendorApplicationForm() {
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">담당자명 *</label>
|
||||
<label className="block text-sm font-medium text-ink">담당자명 *</label>
|
||||
<input
|
||||
name="contactName"
|
||||
type="text"
|
||||
placeholder="담당자 이름"
|
||||
required
|
||||
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
||||
className="mt-1 w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">사업자등록번호 *</label>
|
||||
<label className="block text-sm font-medium text-ink">사업자등록번호 *</label>
|
||||
<input
|
||||
name="businessRegistrationNumber"
|
||||
type="text"
|
||||
placeholder="123-45-67890"
|
||||
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none"
|
||||
className="mt-1 w-full rounded-xl border border-ink/10 bg-white/70 px-4 py-3 text-sm text-ink focus:border-warm-500 focus:ring-2 focus:ring-warm-500/20 focus:outline-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700">서비스 가능 지역 *</label>
|
||||
<label className="block text-sm font-medium text-ink">서비스 가능 지역 *</label>
|
||||
<div className="mt-2 flex flex-wrap gap-2">
|
||||
{REGIONS.map((region) => (
|
||||
<label key={region} className="flex items-center gap-1.5 text-sm text-gray-700">
|
||||
<label key={region} className="flex items-center gap-1.5 text-sm text-ink-muted">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="coverageRegions"
|
||||
value={REGION_MAP[region]}
|
||||
className="rounded border-gray-300"
|
||||
className="rounded border-ink/10 accent-warm-500"
|
||||
/>
|
||||
{region}
|
||||
</label>
|
||||
@@ -104,7 +104,7 @@ export default function VendorApplicationForm() {
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
className="rounded-lg bg-blue-600 px-6 py-2.5 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50"
|
||||
className="rounded-full bg-ink px-6 py-3 text-sm font-medium text-warm-50 hover:bg-warm-800 transition-colors disabled:opacity-50"
|
||||
>
|
||||
{isPending ? '신청 중...' : '인증 신청'}
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user