Add agent workflows & stuff
This commit is contained in:
1 parent
cf7ff2ee4e
commit
2dfa97ee4f
102 files changed
+8488
-161
No files matched your search
@@ -0,0 +1,211 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useMutation, useQuery } from 'convex/react';
|
||||
import { Check, Copy, ExternalLink, Plus, Trash2 } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import type { Doc } from '@spoon/backend/convex/_generated/dataModel.js';
|
||||
import { api } from '@spoon/backend/convex/_generated/api.js';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Label,
|
||||
} from '@spoon/ui';
|
||||
|
||||
const RemoteRow = ({
|
||||
label,
|
||||
url,
|
||||
remoteName,
|
||||
onRemove,
|
||||
}: {
|
||||
label: string;
|
||||
url: string;
|
||||
remoteName?: string;
|
||||
onRemove?: () => Promise<void>;
|
||||
}) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const copy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(url);
|
||||
setCopied(true);
|
||||
toast.success(`${label} URL copied.`);
|
||||
window.setTimeout(() => setCopied(false), 1800);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error('Could not copy remote URL.');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='border-border space-y-2 border-t pt-3 first:border-t-0 first:pt-0'>
|
||||
<div className='flex flex-wrap items-center justify-between gap-2'>
|
||||
<div>
|
||||
<p className='text-sm font-medium'>{label}</p>
|
||||
{remoteName ? (
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
git remote: {remoteName}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
<div className='flex gap-2'>
|
||||
<Button type='button' variant='outline' size='icon' onClick={copy}>
|
||||
{copied ? (
|
||||
<Check className='size-4' />
|
||||
) : (
|
||||
<Copy className='size-4' />
|
||||
)}
|
||||
</Button>
|
||||
<Button type='button' variant='outline' size='icon' asChild>
|
||||
<a
|
||||
href={url}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
aria-label={`Open ${label} repository`}
|
||||
>
|
||||
<ExternalLink className='size-4' />
|
||||
</a>
|
||||
</Button>
|
||||
{onRemove ? (
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
size='icon'
|
||||
onClick={() => void onRemove()}
|
||||
>
|
||||
<Trash2 className='size-4' />
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<Input
|
||||
readOnly
|
||||
value={url}
|
||||
className='font-mono text-xs'
|
||||
onFocus={(event) => event.currentTarget.select()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const SpoonClonePanel = ({ spoon }: { spoon: Doc<'spoons'> }) => {
|
||||
const remotes =
|
||||
useQuery(api.spoonRemotes.listForSpoon, { spoonId: spoon._id }) ?? [];
|
||||
const createRemote = useMutation(api.spoonRemotes.create);
|
||||
const removeRemote = useMutation(api.spoonRemotes.remove);
|
||||
const [label, setLabel] = useState('');
|
||||
const [remoteName, setRemoteName] = useState('');
|
||||
const [url, setUrl] = useState('');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const cloneUrl = spoon.forkUrl;
|
||||
|
||||
const addRemote = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
setSubmitting(true);
|
||||
try {
|
||||
await createRemote({
|
||||
spoonId: spoon._id,
|
||||
label,
|
||||
url,
|
||||
remoteName: remoteName || undefined,
|
||||
});
|
||||
setLabel('');
|
||||
setRemoteName('');
|
||||
setUrl('');
|
||||
toast.success('Remote added.');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error('Could not add remote.');
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className='shadow-none'>
|
||||
<CardHeader className='pb-3'>
|
||||
<CardTitle className='text-base'>Clone your fork</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className='space-y-3'>
|
||||
{cloneUrl ? (
|
||||
<>
|
||||
<RemoteRow
|
||||
label='Primary fork'
|
||||
remoteName='origin'
|
||||
url={cloneUrl}
|
||||
/>
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
This GitHub fork remains Spoon's source of truth for upstream
|
||||
maintenance.
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<p className='text-muted-foreground text-sm'>
|
||||
Add fork metadata before Spoon can show a clone URL.
|
||||
</p>
|
||||
)}
|
||||
{remotes.length ? (
|
||||
<div className='space-y-3 pt-2'>
|
||||
{remotes.map((remote) => (
|
||||
<RemoteRow
|
||||
key={remote._id}
|
||||
label={remote.label}
|
||||
remoteName={remote.remoteName}
|
||||
url={remote.url}
|
||||
onRemove={async () => {
|
||||
await removeRemote({ remoteId: remote._id });
|
||||
toast.success('Remote removed.');
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
<form
|
||||
onSubmit={addRemote}
|
||||
className='border-border space-y-3 border-t pt-4'
|
||||
>
|
||||
<div className='grid gap-3 md:grid-cols-[1fr_0.7fr]'>
|
||||
<div className='grid gap-2'>
|
||||
<Label htmlFor='remote-label'>Label</Label>
|
||||
<Input
|
||||
id='remote-label'
|
||||
value={label}
|
||||
placeholder='Gitea mirror'
|
||||
required
|
||||
onChange={(event) => setLabel(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='grid gap-2'>
|
||||
<Label htmlFor='remote-name'>Git remote name</Label>
|
||||
<Input
|
||||
id='remote-name'
|
||||
value={remoteName}
|
||||
placeholder='gitea'
|
||||
onChange={(event) => setRemoteName(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='grid gap-2'>
|
||||
<Label htmlFor='remote-url'>Repository URL</Label>
|
||||
<Input
|
||||
id='remote-url'
|
||||
value={url}
|
||||
placeholder='https://git.example.com/you/project.git'
|
||||
required
|
||||
onChange={(event) => setUrl(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Button type='submit' variant='outline' disabled={submitting}>
|
||||
<Plus className='size-4' />
|
||||
{submitting ? 'Adding...' : 'Add remote'}
|
||||
</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
Reference in new issue
Block a user