import {useEffect, useState} from 'react'; import {Check, Loader2, TriangleAlert} from 'lucide-react'; import {Badge} from '@/components/ui/badge'; import {Button} from '@/components/ui/button'; import {Input} from '@/components/ui/input'; import {cn} from '@/lib/utils'; /** * 발행 주소(네임스페이스) 입력. * * ★ 왜 사장님이 직접 고르는가 * 주소는 한 번 발행되면 AI 검색이 색인하는 영구 식별자다. 상호에서 자동 생성하면 * ① 한글이 그대로 들어가 URL 이 퍼센트 인코딩 범벅이 되고 * ② 음차하면 규칙이 사람마다 달라 같은 가게가 두 주소를 갖는다. * 사장님이 고르면 두 문제가 동시에 사라진다 — 대신 **중복 여부를 먼저 확인해야** 한다. * * 서버 규칙과 같은 정규식을 쓴다. 다만 최종 판정은 항상 서버다(여기 통과는 왕복을 줄이는 용도). */ const SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{1,48}[a-z0-9])$/; export type SlugStatus = | {kind: 'idle'} | {kind: 'invalid'; reason: string} | {kind: 'checking'} | {kind: 'available'} | {kind: 'taken'; suggestion?: string}; export function localValidate(slug: string): string | null { const s = slug.trim(); if (!s) return '주소를 입력해 주세요.'; if (/[^\x00-\x7F]/.test(s)) return '영문 소문자·숫자·하이픈만 쓸 수 있습니다 (한글 불가).'; if (s !== s.toLowerCase()) return '대문자는 쓸 수 없습니다.'; if (s.length < 3 || s.length > 50) return '3자 이상 50자 이하로 입력해 주세요.'; if (!SLUG_RE.test(s)) return '영문 소문자·숫자·하이픈만, 하이픈으로 시작·끝날 수 없습니다.'; if (s.includes('--')) return '하이픈을 연달아 쓸 수 없습니다.'; return null; } export function SlugField({ value, host, status, disabled, onChange, onCheck, }: { value: string; host: string; status: SlugStatus; disabled?: boolean; onChange: (next: string) => void; onCheck: () => void; }) { const [touched, setTouched] = useState(false); const localError = touched ? localValidate(value) : null; // 입력이 바뀌면 이전 확인 결과는 무효다 — 부모가 idle 로 되돌리도록 알린다. useEffect(() => setTouched(Boolean(value)), [value]); return (
사이트 주소 {status.kind === 'available' && ( 사용 가능 )} {status.kind === 'taken' && ( 이미 사용 중 )}
{/* 주소는 경로형이다(/s/) — 접두사를 앞에 붙여 실제 모양 그대로 보여준다. 서브도메인처럼 보이면 사장님이 열리지 않는 주소를 기대하게 된다. */} {host}/s/ onChange(e.target.value.toLowerCase())} onKeyDown={(e) => { if (e.key === 'Enter' && !localValidate(value)) onCheck(); }} placeholder="my-store" disabled={disabled} className="border-0 focus-visible:ring-0" />

{localError ?? (status.kind === 'invalid' ? status.reason : status.kind === 'taken' ? `이미 사용 중인 주소입니다.${status.suggestion ? ` ‘${status.suggestion}’ 는 어떠세요?` : ''}` : status.kind === 'available' ? `https://${host}/s/${value} 로 발행됩니다.` : '영문 소문자·숫자·하이픈. 발행 후에는 바꿀 수 없습니다 — AI 검색이 이 주소로 색인합니다.')}

{status.kind === 'taken' && status.suggestion && ( )}
); }