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:
Johngreen
2026-03-30 08:13:47 +09:00
parent b4639e00cc
commit e674b90d5a
22 changed files with 1001 additions and 685 deletions
+15
View File
@@ -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에 규칙을 추가하라.
+5 -5
View File
@@ -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>
+5 -5
View File
@@ -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>
+63 -55
View File
@@ -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">
. .
</p>
<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>}
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label className="mb-2 block text-sm font-medium text-gray-700"> </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"
>
<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>
</label>
))}
{error && (
<div className="mb-4 rounded-xl border border-red-200 bg-red-50/80 p-4 text-sm text-red-700">
{error}
</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>
<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>
</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>
</label>
<label className="flex items-center gap-2">
<input type="checkbox" name="marketingConsent" className="h-4 w-4" />
<span className="text-sm">[] </span>
</label>
<label className="flex items-center gap-2">
<input type="checkbox" name="kakaoNotification" className="h-4 w-4" />
<span className="text-sm">[] </span>
</label>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<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-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 text-ink">{role.label}</div>
<div className="text-sm text-ink-muted">{role.desc}</div>
</div>
</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"
>
{isPending ? '처리 중...' : '완료'}
</button>
</form>
<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 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 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 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 text-ink">[] </span>
</label>
</div>
</div>
<button
type="submit"
disabled={isPending}
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>
);
}
+63 -54
View File
@@ -49,62 +49,71 @@ 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">
.
</p>
<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>
<input
id="name"
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"
/>
</div>
<div>
<label htmlFor="password" className="mb-1 block text-sm font-medium text-gray-700">
</label>
<input
id="password"
name="password"
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"
/>
<p className="mt-1 text-xs text-gray-500">8 , + </p>
</div>
<div>
<label htmlFor="confirmPassword" className="mb-1 block text-sm font-medium text-gray-700">
</label>
<input
id="confirmPassword"
name="confirmPassword"
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"
/>
</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"
>
{isPending ? '처리 중...' : '가입 완료'}
</button>
</form>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="name" className="mb-1.5 block text-sm font-medium text-ink-light">
</label>
<input
id="name"
name="name"
type="text"
required
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.5 block text-sm font-medium text-ink-light">
</label>
<input
id="password"
name="password"
type="password"
required
minLength={8}
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-ink-muted">8 , + </p>
</div>
<div>
<label
htmlFor="confirmPassword"
className="mb-1.5 block text-sm font-medium text-ink-light"
>
</label>
<input
id="confirmPassword"
name="confirmPassword"
type="password"
required
minLength={8}
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-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>
);
}
+68 -61
View File
@@ -36,79 +36,86 @@ 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>
)}
{verified && (
<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>
)}
{invited && (
<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>
<input
id="email"
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"
/>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="email" className="mb-1.5 block text-sm font-medium text-ink-light">
</label>
<input
id="email"
name="email"
type="email"
required
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>
<input
id="password"
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"
/>
<div>
<label htmlFor="password" className="mb-1.5 block text-sm font-medium text-ink-light">
</label>
<input
id="password"
name="password"
type="password"
required
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-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-ink/10" />
<span className="text-sm text-ink-muted"></span>
<div className="h-px flex-1 bg-ink/10" />
</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"
type="button"
onClick={() => signIn('kakao', { callbackUrl })}
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]"
>
{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" />
<p className="mt-6 text-center text-sm text-ink-muted">
?{' '}
<Link href="/auth/register" className="text-warm-600 hover:text-warm-700">
</Link>
</p>
</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]"
>
</button>
<p className="mt-6 text-center text-sm text-gray-500">
?{' '}
<Link href="/auth/register" className="text-blue-600 hover:underline">
</Link>
</p>
</div>
);
}
+153 -144
View File
@@ -17,164 +17,173 @@ 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">
.
<br />
.
</p>
<Link
href="/auth/login"
className="inline-block rounded-md bg-blue-600 px-6 py-2 text-white hover:bg-blue-700"
>
</Link>
<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-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>
)}
<form action={formAction} className="space-y-6">
<div>
<label className="mb-2 block text-sm font-medium text-gray-700"> </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"
>
<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>
</label>
))}
{state.error && (
<div className="mb-4 rounded-xl border border-red-200 bg-red-50/80 p-4 text-sm text-red-700">
{state.error}
</div>
{state.fieldErrors?.role && (
<p className="mt-1 text-sm text-red-500">{state.fieldErrors.role[0]}</p>
)}
</div>
)}
<div>
<label htmlFor="name" className="mb-1 block text-sm font-medium text-gray-700">
</label>
<input
id="name"
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"
/>
{state.fieldErrors?.name && (
<p className="mt-1 text-sm text-red-500">{state.fieldErrors.name[0]}</p>
)}
</div>
<form action={formAction} className="space-y-6">
<div>
<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-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 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-700">{state.fieldErrors.role[0]}</p>
)}
</div>
<div>
<label htmlFor="phone" className="mb-1 block text-sm font-medium text-gray-700">
</label>
<input
id="phone"
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"
/>
{state.fieldErrors?.phone && (
<p className="mt-1 text-sm text-red-500">{state.fieldErrors.phone[0]}</p>
)}
</div>
<div>
<label htmlFor="name" className="mb-1.5 block text-sm font-medium text-ink-light">
</label>
<input
id="name"
name="name"
type="text"
required
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-700">{state.fieldErrors.name[0]}</p>
)}
</div>
<div>
<label htmlFor="email" className="mb-1 block text-sm font-medium text-gray-700">
</label>
<input
id="email"
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"
/>
{state.fieldErrors?.email && (
<p className="mt-1 text-sm text-red-500">{state.fieldErrors.email[0]}</p>
)}
</div>
<div>
<label htmlFor="phone" className="mb-1.5 block text-sm font-medium text-ink-light">
</label>
<input
id="phone"
name="phone"
type="tel"
placeholder="010-1234-5678"
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-700">{state.fieldErrors.phone[0]}</p>
)}
</div>
<div>
<label htmlFor="password" className="mb-1 block text-sm font-medium text-gray-700">
</label>
<input
id="password"
name="password"
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"
/>
<p className="mt-1 text-xs text-gray-500">8 , + </p>
{state.fieldErrors?.password && (
<p className="mt-1 text-sm text-red-500">{state.fieldErrors.password[0]}</p>
)}
</div>
<div>
<label htmlFor="email" className="mb-1.5 block text-sm font-medium text-ink-light">
</label>
<input
id="email"
name="email"
type="email"
required
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-700">{state.fieldErrors.email[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>
<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>
</label>
{state.fieldErrors?.termsOfService && (
<p className="text-sm text-red-500">{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>
</label>
{state.fieldErrors?.privacyPolicy && (
<p className="text-sm text-red-500">{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>
</label>
<label className="flex items-center gap-2">
<input type="checkbox" name="kakaoNotification" className="h-4 w-4" />
<span className="text-sm">[] </span>
</label>
</div>
<div>
<label htmlFor="password" className="mb-1.5 block text-sm font-medium text-ink-light">
</label>
<input
id="password"
name="password"
type="password"
required
minLength={8}
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-ink-muted">8 , + </p>
{state.fieldErrors?.password && (
<p className="mt-1 text-sm text-red-700">{state.fieldErrors.password[0]}</p>
)}
</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"
>
{isPending ? '처리 중...' : '가입하기'}
</button>
</form>
<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 text-ink">
<span className="text-red-600">[]</span>
</span>
</label>
{state.fieldErrors?.termsOfService && (
<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 text-ink">
<span className="text-red-600">[]</span>
</span>
</label>
{state.fieldErrors?.privacyPolicy && (
<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 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 text-ink">[] </span>
</label>
</div>
</div>
<p className="mt-6 text-center text-sm text-gray-500">
?{' '}
<Link href="/auth/login" className="text-blue-600 hover:underline">
</Link>
</p>
<button
type="submit"
disabled={isPending}
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-ink-muted">
?{' '}
<Link href="/auth/login" className="text-warm-600 hover:text-warm-700">
</Link>
</p>
</div>
</div>
);
}
+18 -16
View File
@@ -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">
.
<br />
.
</p>
<p className="mb-8 text-sm text-gray-400">
? .
</p>
<Link
href="/"
className="inline-block rounded-md bg-gray-100 px-6 py-2 text-gray-700 hover:bg-gray-200"
>
</Link>
<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-ink-muted">
? .
</p>
<Link
href="/"
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>
);
}
+35 -23
View File
@@ -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,12 +28,16 @@ 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">
</Link>
<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,14 +48,16 @@ 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/login" className="text-blue-600 hover:underline">
</Link>
<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>
);
}
@@ -65,12 +73,16 @@ 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">
</Link>
<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>
);
}
+36 -34
View File
@@ -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>
);
}
+107
View File
@@ -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%;
}
+2 -2
View File
@@ -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
View File
@@ -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>
+30 -28
View File
@@ -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>
+202 -79
View File
@@ -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 ··
</p>
<div className="mt-8 flex justify-center gap-4">
<Link
href="/stores"
className="rounded-lg bg-blue-600 px-6 py-3 text-white hover:bg-blue-700"
>
</Link>
<Link
href="/stores/new"
className="rounded-lg border border-gray-300 px-6 py-3 text-gray-700 hover:bg-gray-100"
>
</Link>
</div>
<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>
<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"
>
</Link>
{/* 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>
<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="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">
&rarr;
</span>
</Link>
<Link
href="/stores/new"
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="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>
{/* Decorative divider */}
<div
className="animate-fade-in mt-20 flex items-center gap-4"
style={{ animationDelay: '0.8s' }}
>
<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="/vendors" className="mt-3 inline-block text-sm text-blue-600 hover:underline">
</Link>
</div>
</div>
{/* 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
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` }}
>
{/* Number accent */}
<span className="font-display text-5xl font-bold text-ink/[0.04]">
{f.num}
</span>
<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>
<Link
href="/subsidies"
className="mt-3 inline-block text-sm text-blue-600 hover:underline"
>
</Link>
</div>
{/* Accent bar */}
<div className={`mt-4 h-1 w-10 rounded-full ${f.accent}`} />
<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>
<Link
href="/contracts"
className="mt-3 inline-block text-sm text-blue-600 hover:underline"
>
</Link>
<p className="mt-4 text-xs font-medium tracking-widest uppercase text-ink-muted">
{f.label}
</p>
<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">
&rarr;
</span>
</span>
</Link>
))}
</div>
</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">
&rarr;
</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>
);
}
+22 -22
View File
@@ -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>
);
}
+41 -41
View File
@@ -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">
&larr;
<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">
, ,
</p>
<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>
+25 -21
View File
@@ -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,21 +85,25 @@ 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>
{' '}
{store.lease?.depositAmount != null
? `${Number(store.lease.depositAmount).toLocaleString('ko-KR')}`
: '-'}
<span className="font-medium text-ink">
{store.lease?.depositAmount != null
? `${Number(store.lease.depositAmount).toLocaleString('ko-KR')}`
: '-'}
</span>
</span>
<span>
{' '}
{store.lease?.monthlyRentAmount != null
? `${Number(store.lease.monthlyRentAmount).toLocaleString('ko-KR')}`
: '-'}
<span className="font-medium text-ink">
{store.lease?.monthlyRentAmount != null
? `${Number(store.lease.monthlyRentAmount).toLocaleString('ko-KR')}`
: '-'}
</span>
</span>
</div>
</Link>
@@ -107,7 +111,7 @@ export default async function StoresPage({
)}
</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>
+5 -5
View File
@@ -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>
+35 -33
View File
@@ -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">
</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 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>
+43 -29
View File
@@ -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">
</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-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>
<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-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="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
View File
@@ -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>