161 lines
4.7 KiB
TypeScript
161 lines
4.7 KiB
TypeScript
import { useState } from 'react'
|
|
import { useNavigate } from 'react-router-dom'
|
|
import { useAnalysis } from '@/hooks/useAnalysis'
|
|
|
|
/**
|
|
* AnalysisStartPage — MVP 핵심 페이지.
|
|
*
|
|
* 사용자가 병원 URL + 유튜브/IG/FB/네이버블로그/강남언니 핸들을 직접 입력.
|
|
* 원본 프로토타입은 URL만 받고 자동 발견했지만, MVP는 핸들 수동 입력.
|
|
*
|
|
* TODO (D2 frontend):
|
|
* - 각 입력 필드에 `POST /api/channels/verify` 실시간 검증 (debounce 500ms)
|
|
* - 핸들 정규화 UI (사용자가 URL 전체 붙여넣어도 @handle 추출)
|
|
* - 최소 1개 채널 필수 검증
|
|
*/
|
|
export default function AnalysisStartPage() {
|
|
const navigate = useNavigate()
|
|
const { start, error } = useAnalysis()
|
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
const [form, setForm] = useState({
|
|
url: '',
|
|
name: '',
|
|
youtube: '',
|
|
instagram: '',
|
|
facebook: '',
|
|
naver_blog: '',
|
|
gangnam_unni: '',
|
|
})
|
|
|
|
async function handleSubmit(e: React.FormEvent) {
|
|
e.preventDefault()
|
|
setIsSubmitting(true)
|
|
try {
|
|
const runId = await start({
|
|
url: form.url,
|
|
channels: {
|
|
youtube: form.youtube || undefined,
|
|
instagram: form.instagram ? [form.instagram] : [],
|
|
facebook: form.facebook || undefined,
|
|
naver_blog: form.naver_blog || undefined,
|
|
gangnam_unni: form.gangnam_unni || undefined,
|
|
},
|
|
})
|
|
navigate(`/analysis/${runId}/loading`)
|
|
} catch {
|
|
setIsSubmitting(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen px-6 py-16 flex justify-center">
|
|
<form onSubmit={handleSubmit} className="w-full max-w-2xl glass-card p-10">
|
|
<h1 className="font-serif text-3xl font-bold mb-8">병원 분석 시작</h1>
|
|
|
|
<Field label="병원 홈페이지 URL *" required>
|
|
<input
|
|
required
|
|
type="url"
|
|
placeholder="https://www.example.com"
|
|
value={form.url}
|
|
onChange={(e) => setForm({ ...form, url: e.target.value })}
|
|
className="input"
|
|
/>
|
|
</Field>
|
|
|
|
<h2 className="text-lg font-semibold mt-8 mb-4 text-white/80">소셜 채널 (최소 1개)</h2>
|
|
|
|
<Field label="YouTube 핸들">
|
|
<input
|
|
placeholder="@viewclinic"
|
|
value={form.youtube}
|
|
onChange={(e) => setForm({ ...form, youtube: e.target.value })}
|
|
className="input"
|
|
/>
|
|
</Field>
|
|
|
|
<Field label="Instagram 핸들">
|
|
<input
|
|
placeholder="@clinic_official"
|
|
value={form.instagram}
|
|
onChange={(e) => setForm({ ...form, instagram: e.target.value })}
|
|
className="input"
|
|
/>
|
|
</Field>
|
|
|
|
<Field label="Facebook 페이지">
|
|
<input
|
|
placeholder="clinicofficial"
|
|
value={form.facebook}
|
|
onChange={(e) => setForm({ ...form, facebook: e.target.value })}
|
|
className="input"
|
|
/>
|
|
</Field>
|
|
|
|
<Field label="네이버 블로그 URL">
|
|
<input
|
|
type="url"
|
|
placeholder="https://blog.naver.com/clinic"
|
|
value={form.naver_blog}
|
|
onChange={(e) => setForm({ ...form, naver_blog: e.target.value })}
|
|
className="input"
|
|
/>
|
|
</Field>
|
|
|
|
<Field label="강남언니 URL">
|
|
<input
|
|
type="url"
|
|
placeholder="https://www.gangnamunni.com/hospital/..."
|
|
value={form.gangnam_unni}
|
|
onChange={(e) => setForm({ ...form, gangnam_unni: e.target.value })}
|
|
className="input"
|
|
/>
|
|
</Field>
|
|
|
|
{error && <p className="text-status-critical mt-4 text-sm">{error.message}</p>}
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={isSubmitting}
|
|
className="w-full mt-8 bg-accent hover:bg-accent-dark disabled:opacity-50 text-white font-semibold py-4 rounded-xl transition"
|
|
>
|
|
{isSubmitting ? '분석 요청 중…' : '분석 시작'}
|
|
</button>
|
|
</form>
|
|
|
|
<style>{`
|
|
.input {
|
|
width: 100%;
|
|
padding: 12px 16px;
|
|
background: rgba(255,255,255,0.05);
|
|
border: 1px solid rgba(255,255,255,0.1);
|
|
border-radius: 12px;
|
|
color: white;
|
|
outline: none;
|
|
}
|
|
.input:focus { border-color: var(--color-accent); }
|
|
`}</style>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function Field({
|
|
label,
|
|
required,
|
|
children,
|
|
}: {
|
|
label: string
|
|
required?: boolean
|
|
children: React.ReactNode
|
|
}) {
|
|
return (
|
|
<label className="block mb-4">
|
|
<span className="block text-sm text-white/70 mb-2">
|
|
{label}
|
|
{required && <span className="text-status-critical ml-1">*</span>}
|
|
</span>
|
|
{children}
|
|
</label>
|
|
)
|
|
}
|