/* Team accounts. Admins assign work and confirm ads; members do the work on their own tasks. Client accounts exist in the schema but are not handed out yet — that is the next phase. */ function UserModal({ user, clients, onClose, onSaved }) { const [form, setForm] = React.useState({ email: user ? user.email : '', name: (user && user.name) || '', job_title: (user && user.job_title) || '', slack_email: (user && user.slack_email) || '', role: user ? user.role : 'member', password: '', client_id: (user && user.client_id) || '', weekly_capacity_days: user ? user.weekly_capacity_days : 0, is_active: user ? user.is_active : true, }); const [saving, setSaving] = React.useState(false); const set = (k, v) => setForm((f) => ({ ...f, [k]: v })); const save = () => { if (!form.email.trim()) { toast('Email required', 'error'); return; } if (!user && !form.password) { toast('Set a password for the new account', 'error'); return; } setSaving(true); const body = { ...form, client_id: form.role === 'client' ? (form.client_id || null) : null, weekly_capacity_days: Number(form.weekly_capacity_days) || 0 }; if (!body.password) delete body.password; const call = user ? api.patch(`/api/users/${user.id}`, body) : api.post('/api/users', body); call.then(() => { toast('Saved'); onSaved(); onClose(); }) .catch((e) => toast(e.message, 'error')) .finally(() => setSaving(false)); }; return ( }>
set('name', e.target.value)} autoFocus />
set('job_title', e.target.value)} />
set('email', e.target.value)} />
set('slack_email', e.target.value)} />

The address this person uses on Slack, if it is not the one above. It is how Studio finds them to send a direct message. Leave empty to use the login email.

set('password', e.target.value)} />
set('weekly_capacity_days', e.target.value)} />

6 means Monday to Saturday. Leave 0 for anyone who does not cut video — they stay out of the workload view.

{form.role === 'client' && (
)}
); } function TeamScreen({ directory }) { const [editing, setEditing] = React.useState(null); const [creating, setCreating] = React.useState(false); const [disk, setDisk] = React.useState(null); React.useEffect(() => { api.get('/api/admin/disk').then(setDisk).catch(() => {}); }, []); return ( <>
{directory.users.map((u) => ( setEditing(u)}> ))}
PersonRoleStatus
{u.name || u.email}
{u.email}
{u.job_title || u.role} {u.weekly_capacity_days > 0 && ( {u.weekly_capacity_days}d/week )} {u.role === 'admin' && admin} {u.is_active ? active : disabled}
{disk && (
Server storage {bytes(disk.media_bytes)} of media · {bytes(disk.free_bytes)} free
80 ? 'var(--red)' : 'var(--blue)' }} />

{disk.originals_on_disk} source file{disk.originals_on_disk === 1 ? '' : 's'} still on the server. Confirmed ads are pushed to Drive and their source is removed automatically.

)} {(creating || editing) && ( { setCreating(false); setEditing(null); }} onSaved={directory.reload} /> )} ); }