115 lines
3.4 KiB
TypeScript
115 lines
3.4 KiB
TypeScript
|
|
'use client';
|
||
|
|
|
||
|
|
import { useState } from 'react';
|
||
|
|
import {
|
||
|
|
Dialog,
|
||
|
|
DialogContent,
|
||
|
|
DialogHeader,
|
||
|
|
DialogTitle,
|
||
|
|
DialogTrigger,
|
||
|
|
DialogFooter,
|
||
|
|
DialogDescription,
|
||
|
|
} from '@/components/ui/dialog';
|
||
|
|
import { Button } from '@/components/ui/button';
|
||
|
|
import { Input } from '@/components/ui/input';
|
||
|
|
import { Label } from '@/components/ui/label';
|
||
|
|
import { Textarea } from '@/components/ui/textarea';
|
||
|
|
import { Share2 } from 'lucide-react';
|
||
|
|
import { toast } from 'sonner';
|
||
|
|
|
||
|
|
interface ShareModalProps {
|
||
|
|
analysisId: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function ShareModal({ analysisId }: ShareModalProps) {
|
||
|
|
const [open, setOpen] = useState(false);
|
||
|
|
const [recipientEmail, setRecipientEmail] = useState('');
|
||
|
|
const [note, setNote] = useState('');
|
||
|
|
const [submitting, setSubmitting] = useState(false);
|
||
|
|
|
||
|
|
async function handleSubmit(e: React.FormEvent) {
|
||
|
|
e.preventDefault();
|
||
|
|
setSubmitting(true);
|
||
|
|
try {
|
||
|
|
const res = await fetch(`/api/analyzer/analyses/${analysisId}/share`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
body: JSON.stringify({
|
||
|
|
recipientEmail,
|
||
|
|
note: note.trim() || undefined,
|
||
|
|
}),
|
||
|
|
});
|
||
|
|
if (!res.ok) {
|
||
|
|
const data = (await res.json().catch(() => ({}))) as { error?: string; message?: string };
|
||
|
|
throw new Error(data.message ?? data.error ?? `Request failed: ${res.status}`);
|
||
|
|
}
|
||
|
|
toast.success(`Shared with ${recipientEmail}`);
|
||
|
|
setOpen(false);
|
||
|
|
setRecipientEmail('');
|
||
|
|
setNote('');
|
||
|
|
} catch (err) {
|
||
|
|
const msg = err instanceof Error ? err.message : 'Unknown error';
|
||
|
|
toast.error(msg);
|
||
|
|
} finally {
|
||
|
|
setSubmitting(false);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Dialog open={open} onOpenChange={setOpen}>
|
||
|
|
<DialogTrigger asChild>
|
||
|
|
<Button variant="outline" size="sm">
|
||
|
|
<Share2 className="w-4 h-4 mr-2" />
|
||
|
|
Share
|
||
|
|
</Button>
|
||
|
|
</DialogTrigger>
|
||
|
|
<DialogContent>
|
||
|
|
<DialogHeader>
|
||
|
|
<DialogTitle>Share this analysis</DialogTitle>
|
||
|
|
<DialogDescription>
|
||
|
|
Recipient must be on an allowed domain (set via
|
||
|
|
ALLOWED_SHARE_DOMAINS).
|
||
|
|
</DialogDescription>
|
||
|
|
</DialogHeader>
|
||
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label htmlFor="recipient">Recipient email</Label>
|
||
|
|
<Input
|
||
|
|
id="recipient"
|
||
|
|
type="email"
|
||
|
|
required
|
||
|
|
value={recipientEmail}
|
||
|
|
onChange={(e) => setRecipientEmail(e.target.value)}
|
||
|
|
placeholder="colleague@wulfconsulting.com"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
<div className="space-y-2">
|
||
|
|
<Label htmlFor="note">Note (optional)</Label>
|
||
|
|
<Textarea
|
||
|
|
id="note"
|
||
|
|
value={note}
|
||
|
|
onChange={(e) => setNote(e.target.value)}
|
||
|
|
placeholder="Why you're sharing this…"
|
||
|
|
rows={3}
|
||
|
|
maxLength={2000}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
<DialogFooter>
|
||
|
|
<Button
|
||
|
|
type="button"
|
||
|
|
variant="outline"
|
||
|
|
onClick={() => setOpen(false)}
|
||
|
|
disabled={submitting}
|
||
|
|
>
|
||
|
|
Cancel
|
||
|
|
</Button>
|
||
|
|
<Button type="submit" disabled={submitting || !recipientEmail}>
|
||
|
|
{submitting ? 'Sharing…' : 'Share'}
|
||
|
|
</Button>
|
||
|
|
</DialogFooter>
|
||
|
|
</form>
|
||
|
|
</DialogContent>
|
||
|
|
</Dialog>
|
||
|
|
);
|
||
|
|
}
|