246 lines
6.6 KiB
TypeScript
246 lines
6.6 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import { useMutation } from 'convex/react';
|
|
import { toast } from 'sonner';
|
|
|
|
import { api } from '@spoon/backend/convex/_generated/api.js';
|
|
import {
|
|
Button,
|
|
Input,
|
|
Label,
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
Textarea,
|
|
} from '@spoon/ui';
|
|
|
|
const options = {
|
|
provider: ['github', 'gitea', 'gitlab', 'other'],
|
|
visibility: ['unknown', 'public', 'private', 'internal'],
|
|
maintenanceMode: ['watch', 'auto_pr', 'paused'],
|
|
syncCadence: ['daily', 'weekly', 'manual'],
|
|
productionRefStrategy: ['default_branch', 'latest_release', 'tag_pattern'],
|
|
} as const;
|
|
|
|
type FormState = {
|
|
name: string;
|
|
description: string;
|
|
provider: (typeof options.provider)[number];
|
|
upstreamOwner: string;
|
|
upstreamRepo: string;
|
|
upstreamDefaultBranch: string;
|
|
upstreamUrl: string;
|
|
forkOwner: string;
|
|
forkRepo: string;
|
|
forkUrl: string;
|
|
visibility: (typeof options.visibility)[number];
|
|
maintenanceMode: (typeof options.maintenanceMode)[number];
|
|
syncCadence: (typeof options.syncCadence)[number];
|
|
productionRefStrategy: (typeof options.productionRefStrategy)[number];
|
|
};
|
|
|
|
const initialState: FormState = {
|
|
name: '',
|
|
description: '',
|
|
provider: 'github',
|
|
upstreamOwner: '',
|
|
upstreamRepo: '',
|
|
upstreamDefaultBranch: 'main',
|
|
upstreamUrl: '',
|
|
forkOwner: '',
|
|
forkRepo: '',
|
|
forkUrl: '',
|
|
visibility: 'unknown',
|
|
maintenanceMode: 'watch',
|
|
syncCadence: 'daily',
|
|
productionRefStrategy: 'default_branch',
|
|
};
|
|
|
|
const TextField = ({
|
|
id,
|
|
label,
|
|
value,
|
|
onChange,
|
|
required,
|
|
}: {
|
|
id: keyof FormState;
|
|
label: string;
|
|
value: string;
|
|
onChange: (value: string) => void;
|
|
required?: boolean;
|
|
}) => (
|
|
<div className='grid gap-2'>
|
|
<Label htmlFor={id}>{label}</Label>
|
|
<Input
|
|
id={id}
|
|
value={value}
|
|
required={required}
|
|
onChange={(event) => onChange(event.target.value)}
|
|
/>
|
|
</div>
|
|
);
|
|
|
|
export const NewSpoonForm = () => {
|
|
const router = useRouter();
|
|
const createManual = useMutation(api.spoons.createManual);
|
|
const [form, setForm] = useState<FormState>(initialState);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
|
|
const update = <K extends keyof FormState>(key: K, value: FormState[K]) => {
|
|
setForm((current) => ({ ...current, [key]: value }));
|
|
};
|
|
|
|
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
event.preventDefault();
|
|
setSubmitting(true);
|
|
try {
|
|
await createManual({
|
|
...form,
|
|
description: form.description || undefined,
|
|
forkOwner: form.forkOwner || undefined,
|
|
forkRepo: form.forkRepo || undefined,
|
|
forkUrl: form.forkUrl || undefined,
|
|
});
|
|
toast.success('Spoon created.');
|
|
router.push('/spoons');
|
|
} catch (error) {
|
|
console.error(error);
|
|
toast.error('Could not create Spoon.');
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<form
|
|
onSubmit={handleSubmit}
|
|
className='border-border bg-card grid gap-6 border p-5'
|
|
>
|
|
<div className='grid gap-4 md:grid-cols-2'>
|
|
<TextField
|
|
id='name'
|
|
label='Spoon name'
|
|
value={form.name}
|
|
required
|
|
onChange={(value) => update('name', value)}
|
|
/>
|
|
<Select
|
|
value={form.provider}
|
|
onValueChange={(value) =>
|
|
update('provider', value as FormState['provider'])
|
|
}
|
|
>
|
|
<div className='grid gap-2'>
|
|
<Label>Provider</Label>
|
|
<SelectTrigger className='w-full'>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
</div>
|
|
<SelectContent>
|
|
{options.provider.map((value) => (
|
|
<SelectItem key={value} value={value}>
|
|
{value}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className='grid gap-2'>
|
|
<Label htmlFor='description'>Description</Label>
|
|
<Textarea
|
|
id='description'
|
|
value={form.description}
|
|
onChange={(event) => update('description', event.target.value)}
|
|
/>
|
|
</div>
|
|
<div className='grid gap-4 md:grid-cols-2'>
|
|
<TextField
|
|
id='upstreamOwner'
|
|
label='Upstream owner'
|
|
value={form.upstreamOwner}
|
|
required
|
|
onChange={(value) => update('upstreamOwner', value)}
|
|
/>
|
|
<TextField
|
|
id='upstreamRepo'
|
|
label='Upstream repository'
|
|
value={form.upstreamRepo}
|
|
required
|
|
onChange={(value) => update('upstreamRepo', value)}
|
|
/>
|
|
<TextField
|
|
id='upstreamDefaultBranch'
|
|
label='Upstream default branch'
|
|
value={form.upstreamDefaultBranch}
|
|
required
|
|
onChange={(value) => update('upstreamDefaultBranch', value)}
|
|
/>
|
|
<TextField
|
|
id='upstreamUrl'
|
|
label='Upstream URL'
|
|
value={form.upstreamUrl}
|
|
required
|
|
onChange={(value) => update('upstreamUrl', value)}
|
|
/>
|
|
<TextField
|
|
id='forkOwner'
|
|
label='Fork owner'
|
|
value={form.forkOwner}
|
|
onChange={(value) => update('forkOwner', value)}
|
|
/>
|
|
<TextField
|
|
id='forkRepo'
|
|
label='Fork repository'
|
|
value={form.forkRepo}
|
|
onChange={(value) => update('forkRepo', value)}
|
|
/>
|
|
<TextField
|
|
id='forkUrl'
|
|
label='Fork URL'
|
|
value={form.forkUrl}
|
|
onChange={(value) => update('forkUrl', value)}
|
|
/>
|
|
</div>
|
|
<div className='grid gap-4 md:grid-cols-4'>
|
|
{(
|
|
[
|
|
'visibility',
|
|
'maintenanceMode',
|
|
'syncCadence',
|
|
'productionRefStrategy',
|
|
] as const
|
|
).map((key) => (
|
|
<Select
|
|
key={key}
|
|
value={form[key]}
|
|
onValueChange={(value) => update(key, value as never)}
|
|
>
|
|
<div className='grid gap-2'>
|
|
<Label>{key.replace(/([A-Z])/g, ' $1')}</Label>
|
|
<SelectTrigger className='w-full'>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
</div>
|
|
<SelectContent>
|
|
{options[key].map((value) => (
|
|
<SelectItem key={value} value={value}>
|
|
{value.replaceAll('_', ' ')}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
))}
|
|
</div>
|
|
<div className='flex justify-end'>
|
|
<Button type='submit' disabled={submitting}>
|
|
{submitting ? 'Creating...' : 'Create Spoon'}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
);
|
|
};
|