import {useState, type FormEvent} from 'react'; import {Link, Navigate, useNavigate} from 'react-router'; import {UserPlus} from 'lucide-react'; import {googleLogin, signup} from '@/api'; import {GoogleSignInButton} from '@/components/auth/GoogleSignInButton'; import {Button} from '@/components/ui/button'; import {Input} from '@/components/ui/input'; import {isGoogleLoginEnabled} from '@/lib/googleIdentity'; import {notify, notifyApiError} from '@/lib/notify'; import {establishSession} from '@/lib/session'; import {useAuthStore} from '@/stores/auth'; /** * 회원가입. 가입 = **새 회사(테넌트) 1개 + 그 회사의 첫 계정 1개** 다(백엔드 auth_service.signup). * * ★ 여기 검사는 서버 규칙(services/auth_service.py 의 _LOGIN_ID_RE·_MIN_PASSWORD_LEN)의 사본이다. * 두 벌이라 어긋날 수 있지만, 서버가 마지막 방어선이고 여기는 "제출 전에 알려주는" 역할이다. * 규칙을 바꾸면 두 곳을 같이 고친다. */ const ID_RE = /^[a-zA-Z][a-zA-Z0-9._-]{3,19}$/; const EMAIL_RE = /^[^@\s]+@[^@\s]+\.[^@\s]+$/; const MIN_PASSWORD_LEN = 8; export function SignupPage() { const navigate = useNavigate(); const user = useAuthStore((s) => s.user); const [form, setForm] = useState({ id: '', password: '', passwordConfirm: '', name: '', email: '', companyName: '', }); const [isSubmitting, setIsSubmitting] = useState(false); if (user) return ; const set = (key: keyof typeof form) => (e: {target: {value: string}}) => setForm((prev) => ({...prev, [key]: e.target.value})); const validate = (): string | null => { if (!ID_RE.test(form.id)) return '아이디는 영문으로 시작하는 4~20자입니다(영문·숫자·. _ - 사용).'; if (form.password.length < MIN_PASSWORD_LEN) return `비밀번호는 ${MIN_PASSWORD_LEN}자 이상이어야 합니다.`; if (form.password !== form.passwordConfirm) return '비밀번호가 서로 다릅니다.'; if (!EMAIL_RE.test(form.email)) return '이메일 형식을 확인해 주세요.'; return null; }; const handleSubmit = async (event: FormEvent) => { event.preventDefault(); const invalid = validate(); if (invalid) { notify.error(invalid); return; } setIsSubmitting(true); try { const res = await signup({ id: form.id.trim(), password: form.password, name: form.name.trim() || null, email: form.email.trim(), company_name: form.companyName.trim() || null, }); if (res.result?.success === false) { notifyApiError({data: res}, '가입하지 못했습니다.'); return; } // 가입 응답에 토큰이 실려 온다 — 방금 정한 비밀번호를 다시 치게 하지 않는다. if (!(await establishSession(res, form.id.trim()))) { notifyApiError({data: res}, '가입은 됐지만 로그인 토큰이 오지 않았습니다. 다시 로그인해 주세요.'); navigate('/login', {replace: true}); return; } notify.success('가입이 완료되었습니다.'); navigate('/', {replace: true}); } catch (error) { notifyApiError(error, '가입하지 못했습니다.'); } finally { setIsSubmitting(false); } }; // 구글은 가입과 로그인이 같은 동작이다 — 처음 온 계정이면 백엔드가 그 자리에서 만든다. const handleGoogle = async (credential: string) => { setIsSubmitting(true); try { const res = await googleLogin({credential}); if (res.result?.success === false) { notifyApiError({data: res}, '구글로 가입하지 못했습니다.'); return; } if (!(await establishSession(res, ''))) { notifyApiError({data: res}, '로그인 응답에 토큰이 없습니다.'); return; } navigate('/', {replace: true}); } catch (error) { notifyApiError(error, '구글로 가입하지 못했습니다.'); } finally { setIsSubmitting(false); } }; return (
Web4Ai

회원가입

내 가게 사이트를 만들려면 계정이 필요합니다.

{isGoogleLoginEnabled() && ( <>
또는
)}

이미 계정이 있으신가요?{' '} 로그인

); }