Spaces:
Paused
Paused
| import { useState } from 'react' | |
| import { api } from '../api' | |
| import { ErrorBox } from '../hooks' | |
| const csv = (s) => s.split(',').map((x) => x.trim()).filter(Boolean) | |
| // mode="create": standalone form on the home screen. | |
| // mode="edit": step 1 of the wizard, edits the existing project. | |
| export default function SetupStep({ mode, data, refreshProject, setStep, onCreated, onCancel }) { | |
| const refresh = refreshProject | |
| const project = data?.project | |
| const [form, setForm] = useState(() => ({ | |
| name: project?.name || '', | |
| topic: project?.niche.topic || '', | |
| keywords: project?.niche.keywords.join(', ') || '', | |
| subreddits: project?.niche.subreddits.join(', ') || '', | |
| audience: project?.niche.audience || '', | |
| tone: project?.niche.tone || '', | |
| format: project?.format || 'youtube_short', | |
| })) | |
| const [busy, setBusy] = useState(false) | |
| const [filling, setFilling] = useState(false) | |
| const [error, setError] = useState('') | |
| const set = (k) => (e) => setForm({ ...form, [k]: e.target.value }) | |
| // Ask the LLM to derive the remaining fields from the niche topic. | |
| // Only fills columns the user left blank, so it never overwrites their input. | |
| const autofill = async () => { | |
| if (!form.topic.trim()) { setError('Enter a niche topic first.'); return } | |
| setFilling(true) | |
| setError('') | |
| try { | |
| const s = await api.post('/api/niche/suggest', { topic: form.topic.trim() }) | |
| setForm((f) => ({ | |
| ...f, | |
| name: f.name.trim() || s.name || '', | |
| keywords: f.keywords.trim() || (s.keywords || []).join(', '), | |
| subreddits: f.subreddits.trim() || (s.subreddits || []).join(', '), | |
| audience: f.audience.trim() || s.audience || '', | |
| tone: f.tone.trim() || s.tone || '', | |
| })) | |
| } catch (e) { | |
| setError(e.message) | |
| } finally { | |
| setFilling(false) | |
| } | |
| } | |
| const body = () => ({ | |
| name: form.name || form.topic, | |
| niche: { | |
| topic: form.topic, | |
| keywords: csv(form.keywords), | |
| subreddits: csv(form.subreddits), | |
| audience: form.audience, | |
| tone: form.tone, | |
| }, | |
| format: form.format, | |
| references: project ? project.references : [], | |
| }) | |
| const submit = async () => { | |
| if (!form.topic.trim()) { setError('A niche topic is required.'); return } | |
| setBusy(true) | |
| setError('') | |
| try { | |
| if (mode === 'create') { | |
| const created = await api.post('/api/projects', body()) | |
| onCreated(created) | |
| } else { | |
| await api.patch(`/api/projects/${project.id}`, body()) | |
| await refresh() | |
| setStep(1) | |
| } | |
| } catch (e) { | |
| setError(e.message) | |
| } finally { | |
| setBusy(false) | |
| } | |
| } | |
| return ( | |
| <div> | |
| <h2>{mode === 'create' ? 'New project' : 'Project setup'}</h2> | |
| <p className="subtitle">Define the niche. Nothing here is hardcoded — every later stage builds on these fields.</p> | |
| <ErrorBox error={error} /> | |
| <div className="card"> | |
| <label className="field"><span className="lbl">Project name</span> | |
| <input type="text" value={form.name} onChange={set('name')} placeholder="e.g. Deep Sea Shorts" /> | |
| </label> | |
| <label className="field"><span className="lbl">Niche topic *</span> | |
| <div className="row"> | |
| <input className="grow" type="text" value={form.topic} onChange={set('topic')} | |
| placeholder="e.g. deep sea creatures" | |
| onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); autofill() } }} /> | |
| <button className="btn secondary" onClick={autofill} disabled={filling || !form.topic.trim()} | |
| title="Suggest keywords, subreddits, audience and tone from this niche (only fills blank fields)"> | |
| {filling ? 'Filling…' : '✨ Autofill'} | |
| </button> | |
| </div> | |
| </label> | |
| <div className="row"> | |
| <label className="field grow"><span className="lbl">Keywords (comma separated)</span> | |
| <input type="text" value={form.keywords} onChange={set('keywords')} placeholder="ocean, marine biology" /> | |
| </label> | |
| <label className="field grow"><span className="lbl">Subreddits (comma separated)</span> | |
| <input type="text" value={form.subreddits} onChange={set('subreddits')} placeholder="thalassophobia, ocean" /> | |
| </label> | |
| </div> | |
| <div className="row"> | |
| <label className="field grow"><span className="lbl">Audience</span> | |
| <input type="text" value={form.audience} onChange={set('audience')} placeholder="curious adults" /> | |
| </label> | |
| <label className="field grow"><span className="lbl">Tone</span> | |
| <input type="text" value={form.tone} onChange={set('tone')} placeholder="playful, awe-inspiring" /> | |
| </label> | |
| </div> | |
| <label className="field"><span className="lbl">Output format</span> | |
| <select value={form.format} onChange={set('format')}> | |
| <option value="youtube_short">YouTube Short (9:16)</option> | |
| <option value="instagram_reel">Instagram Reel (9:16)</option> | |
| </select> | |
| </label> | |
| <div className="row"> | |
| <button className="btn" onClick={submit} disabled={busy}> | |
| {busy ? 'Saving…' : mode === 'create' ? 'Create project' : 'Save & continue'} | |
| </button> | |
| {onCancel && <button className="btn secondary" onClick={onCancel}>Cancel</button>} | |
| </div> | |
| </div> | |
| </div> | |
| ) | |
| } | |