reel-studio-2 / web /src /steps /SetupStep.jsx
xvepkj's picture
initial commit
3d2098f
Raw
History Blame
5.47 kB
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>
)
}