Seth
update
89a3828
import React, { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { X, Mail, Send, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
export default function ShareModal({ isOpen, onClose, onShare, extractionId }) {
const [email, setEmail] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState("");
const [success, setSuccess] = useState(false);
const [successMessage, setSuccessMessage] = useState("");
const handleSubmit = async (e) => {
e.preventDefault();
setError("");
setSuccess(false);
// Parse and validate multiple emails (comma or semicolon separated)
if (!email.trim()) {
setError("Please enter at least one recipient email address");
return;
}
// Split by comma or semicolon, trim each email, and filter out empty strings
const emailList = email
.split(/[,;]/)
.map((e) => e.trim())
.filter((e) => e.length > 0);
if (emailList.length === 0) {
setError("Please enter at least one recipient email address");
return;
}
// Validate each email
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const invalidEmails = emailList.filter((e) => !emailRegex.test(e));
if (invalidEmails.length > 0) {
setError(`Invalid email address(es): ${invalidEmails.join(", ")}`);
return;
}
setIsLoading(true);
try {
const result = await onShare(extractionId, emailList);
setSuccessMessage(result?.message || `Successfully shared with ${emailList.length} recipient(s)`);
setSuccess(true);
setEmail("");
// Close modal after 2 seconds
setTimeout(() => {
setSuccess(false);
setSuccessMessage("");
onClose();
}, 2000);
} catch (err) {
setError(err.message || "Failed to share extraction. Please try again.");
} finally {
setIsLoading(false);
}
};
const handleClose = () => {
if (!isLoading) {
setEmail("");
setError("");
setSuccess(false);
onClose();
}
};
if (!isOpen) return null;
return (
<AnimatePresence>
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
onClick={handleClose}
/>
{/* Modal */}
<motion.div
initial={{ opacity: 0, scale: 0.95, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95, y: 20 }}
className="relative z-10 w-full max-w-md mx-4 bg-white rounded-2xl shadow-2xl overflow-hidden"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="px-6 py-4 border-b border-slate-200 flex items-center justify-between">
<h2 className="text-xl font-semibold text-slate-900">Share Output</h2>
<button
onClick={handleClose}
disabled={isLoading}
className="p-2 rounded-lg hover:bg-slate-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<X className="h-5 w-5 text-slate-500" />
</button>
</div>
{/* Content */}
<div className="px-6 py-6">
{success ? (
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
className="text-center py-8"
>
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-emerald-100 flex items-center justify-center">
<Send className="h-8 w-8 text-emerald-600" />
</div>
<h3 className="text-lg font-semibold text-slate-900 mb-2">
Share Sent Successfully!
</h3>
<p className="text-sm text-slate-600">
{successMessage || "The recipient(s) will receive an email with a link to view the extraction."}
</p>
</motion.div>
) : (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label
htmlFor="recipient-email"
className="block text-sm font-medium text-slate-700 mb-2"
>
Recipient Email(s)
</label>
<p className="text-xs text-slate-500 mb-2">
Separate multiple emails with commas or semicolons
</p>
<div className="relative">
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-slate-400" />
<Input
id="recipient-email"
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Enter email addresses (comma or semicolon separated)"
className="pl-10 h-12 rounded-xl border-slate-200 focus:border-indigo-500 focus:ring-indigo-500"
disabled={isLoading}
autoFocus
/>
</div>
{error && (
<motion.p
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
className="mt-2 text-sm text-red-600"
>
{error}
</motion.p>
)}
</div>
<div className="pt-4 flex gap-3">
<Button
type="button"
variant="outline"
onClick={handleClose}
disabled={isLoading}
className="flex-1 h-11 rounded-xl"
>
Cancel
</Button>
<Button
type="submit"
disabled={isLoading || !email.trim()}
className="flex-1 h-11 rounded-xl bg-gradient-to-r from-indigo-600 to-violet-600 hover:from-indigo-700 hover:to-violet-700"
>
{isLoading ? (
<>
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
Sending...
</>
) : (
<>
<Send className="h-4 w-4 mr-2" />
Send
</>
)}
</Button>
</div>
</form>
)}
</div>
</motion.div>
</div>
</AnimatePresence>
);
}