Initial commit for project Spoon!
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
'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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user