o2o-site-AEO/solution/frontend/src/features/onboarding/Step4Template.tsx
Mina Choi c85c577349 이름: solution/front → solution/frontend
`backend` 옆에 `front` 가 있을 이유가 없었다. negosium 의 negodata/front 를 그대로
베꼈고 그게 왜 front 인지는 따져보지 않았다 — 근거 없이 들여온 이름이라 바로잡는다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019uYhHQdssRubirPirrdJJC
2026-08-31 15:27:16 +09:00

131 lines
5.7 KiB
TypeScript

import {Check} from 'lucide-react';
import {INDUSTRY_CONFIGS} from '@/data/industryData';
import {queueSiteTemplateSave} from '@/features/publish/siteTemplate';
import {cn} from '@/lib/utils';
import {useBuilderStore} from '@/stores/builder';
import {WizardFooter} from './WizardFooter';
import {WizardSteps} from './WizardSteps';
export function Step4Template() {
const industry = useBuilderStore((s) => s.industry);
const templateId = useBuilderStore((s) => s.templateId);
const placeId = useBuilderStore((s) => s.placeId);
const selectTemplate = useBuilderStore((s) => s.selectTemplate);
const goToStep = useBuilderStore((s) => s.goToStep);
const startGenerating = useBuilderStore((s) => s.startGenerating);
const config = INDUSTRY_CONFIGS[industry];
/**
* 고른 순간 서버에도 남긴다.
*
* ★ 화면 상태로만 두면 발행 잡이 읽을 곳이 없어 업종 기본 템플릿으로 굽는다 —
* 사장님이 고른 디자인과 실제 발행본이 갈린다. 저장은 화면을 기다리게 하지 않는다.
*/
const chooseTemplate = (id: string) => {
selectTemplate(id);
queueSiteTemplateSave(placeId, id);
};
return (
<div className="flex flex-1 flex-col justify-between bg-muted/30">
<div className="flex flex-1 flex-col items-center justify-center px-4 py-10 sm:px-6 sm:py-14 lg:px-8">
<div className="mx-auto flex w-full max-w-5xl flex-col">
<WizardSteps current={4} />
<div className="mb-8 text-center">
<h1 className="text-2xl font-bold tracking-tight sm:text-3xl">
원하시는 디자인 스타일을 선택해주세요
</h1>
<p className="mx-auto mt-2 max-w-lg text-xs text-muted-foreground sm:text-sm">
{config.name}에 맞춘 템플릿입니다. 색상과 서체는 에디터에서 언제든 바꿀 수 있고,
어떤 템플릿을 골라도 구조화 데이터와 필수 섹션은 동일하게 들어갑니다.
</p>
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-5 lg:grid-cols-3">
{config.templates.map((template) => {
const isSelected = templateId === template.id;
return (
<button
key={template.id}
type="button"
onClick={() => chooseTemplate(template.id)}
aria-pressed={isSelected}
className={cn(
'flex cursor-pointer flex-col justify-between rounded-2xl border bg-card p-5 text-left transition-all',
isSelected
? 'border-primary ring-1 ring-primary'
: 'border-border hover:border-muted-foreground/30',
)}
>
<div className="mb-3 flex items-start justify-between">
<div>
<h2 className="text-base font-bold">{template.name}</h2>
<span className="text-xs font-medium text-muted-foreground">
{template.toneLabel} · {template.fontStyle}
</span>
</div>
<span
className={cn(
'flex size-5 shrink-0 items-center justify-center rounded-full transition-all',
isSelected
? 'bg-primary text-primary-foreground'
: 'border border-border bg-card',
)}
>
{isSelected && <Check className="size-3" strokeWidth={3} />}
</span>
</div>
{/* 색·레이아웃 미리보기 와이어프레임 */}
<div className="my-3 space-y-2.5 rounded-xl border border-border bg-muted/50 p-3.5">
<div className="flex items-center gap-1.5">
{(['primary', 'secondary', 'card'] as const).map((key) => (
<span
key={key}
className="size-3.5 rounded-full border border-black/10"
style={{backgroundColor: template.colors[key]}}
title={key}
/>
))}
<span className="ml-1 font-mono text-[10px] text-muted-foreground">
{template.colors.primary}
</span>
</div>
<div className="space-y-1.5 pt-1">
<div
className="h-2.5 w-2/3 rounded"
style={{backgroundColor: template.colors.primary, opacity: 0.85}}
/>
<div className="h-1.5 w-full rounded bg-border" />
<div className="h-1.5 w-4/5 rounded bg-border" />
</div>
</div>
<p className="text-xs leading-relaxed text-muted-foreground">
{template.description}
</p>
</button>
);
})}
</div>
</div>
</div>
<WizardFooter
onPrev={() => goToStep(3)}
onNext={() => {
// ★ 아무것도 안 누르고 넘어가는 경우(첫 템플릿이 이미 선택돼 있다)도 서버에 남긴다 —
// 화면이 보여준 그 템플릿이 발행본이 되어야 한다. 같은 값이면 서버가 재빌드 표시도 찍지 않는다.
queueSiteTemplateSave(placeId, templateId);
startGenerating();
}}
nextLabel="이 템플릿으로 사이트 생성하기"
/>
</div>
);
}