tahamajs/IE / Projects /front /src /components /groups /InstantGroupCreateModal.tsx
tahamajs's picture
download
raw
5.76 kB
// src/components/groups/InstantGroupCreateModal.tsx
import { useState } from "react";
import api from "@/lib/api";
import { toast } from "react-toastify";
import { Plus, X, Users, Mail, Loader2, Check } from "lucide-react";
interface InstantGroupCreateModalProps {
isOpen: boolean;
onClose: () => void;
onCreate: (groupId: string) => void;
}
export function InstantGroupCreateModal({ isOpen, onClose, onCreate }: InstantGroupCreateModalProps) {
const [name, setName] = useState("");
const [emails, setEmails] = useState<string[]>([]);
const [emailInput, setEmailInput] = useState("");
const [creating, setCreating] = useState(false);
const [created, setCreated] = useState(false);
const addEmail = () => {
const email = emailInput.trim();
if (email && !emails.includes(email) && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
setEmails(prev => [...prev, email]);
setEmailInput("");
}
};
const removeEmail = (index: number) => {
setEmails(prev => prev.filter((_, i) => i !== index));
};
const handleCreate = async () => {
if (!name.trim()) {
toast.warning("Group name is required");
return;
}
setCreating(true);
try {
const res = await api.post("/groups/instant-create", {
name: name.trim(),
inviteEmails: emails,
});
toast.success("Lab group created! Invitations sent.");
setCreated(true);
onCreate(res.data.groupId);
// Auto-close after 1.5 seconds
setTimeout(() => onClose(), 1500);
} catch (err: any) {
toast.error(err.response?.data?.error || "Failed to create group");
} finally {
setCreating(false);
}
};
if (!isOpen) return null;
if (created) {
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4" onClick={onClose}>
<div className="bg-white dark:bg-gray-800 rounded-2xl max-w-sm w-full p-8 text-center shadow-xl" onClick={e => e.stopPropagation()}>
<div className="w-16 h-16 mx-auto rounded-full bg-green-100 dark:bg-green-900/30 flex items-center justify-center mb-4">
<Check className="w-8 h-8 text-green-600" />
</div>
<h3 className="text-lg font-bold mb-1">Group Created!</h3>
<p className="text-sm text-gray-500">Invitations have been sent to the provided emails.</p>
</div>
</div>
);
}
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4" onClick={onClose}>
<div className="bg-white dark:bg-gray-800 rounded-2xl max-w-md w-full p-6 shadow-xl max-h-[90vh] overflow-y-auto" onClick={e => e.stopPropagation()}>
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-bold">Create Lab Group</h2>
<button onClick={onClose} className="text-gray-500 hover:text-gray-700">
<X className="w-5 h-5" />
</button>
</div>
<div className="space-y-4">
<div>
<label className="text-sm font-semibold mb-1 block">Group Name *</label>
<input
type="text"
placeholder="e.g., Smith Lab – CRISPR Screening"
value={name}
onChange={e => setName(e.target.value)}
className="w-full px-3 py-2.5 border border-gray-200 dark:border-gray-700 rounded-xl bg-transparent focus:ring-2 focus:ring-primary focus:outline-none text-sm"
autoFocus
/>
</div>
<div>
<label className="text-sm font-semibold mb-1 block flex items-center gap-1">
<Mail className="w-4 h-4" /> Invite Members by Email
</label>
<div className="flex gap-2 mb-2">
<input
type="email"
placeholder="colleague@university.edu"
value={emailInput}
onChange={e => setEmailInput(e.target.value)}
onKeyDown={e => {
if (e.key === "Enter") {
e.preventDefault();
addEmail();
}
}}
className="flex-1 px-3 py-2 border border-gray-200 dark:border-gray-700 rounded-xl bg-transparent focus:ring-2 focus:ring-primary focus:outline-none text-sm"
/>
<button onClick={addEmail} className="btn btn-secondary btn-sm gap-1">
<Plus className="w-4 h-4" /> Add
</button>
</div>
{emails.length > 0 && (
<div className="flex flex-wrap gap-1.5 mb-2">
{emails.map((email, i) => (
<span
key={i}
className="inline-flex items-center gap-1.5 text-xs bg-primary/10 text-primary px-2.5 py-1 rounded-full"
>
{email}
<button onClick={() => removeEmail(i)} className="hover:text-red-500">
<X className="w-3 h-3" />
</button>
</span>
))}
</div>
)}
<p className="text-xs text-gray-400">
Each email will receive an invitation to join this group.
</p>
</div>
<button
onClick={handleCreate}
disabled={!name.trim() || creating}
className="btn btn-primary w-full gap-2"
>
{creating ? (
<Loader2 className="w-4 h-4 animate-spin" />
) : (
<Users className="w-4 h-4" />
)}
{creating ? "Creating..." : "Create Group & Invite"}
</button>
</div>
</div>
</div>
);
}

Xet Storage Details

Size:
5.76 kB
·
Xet hash:
dae920c9a51d565ebafa49cfcc2113269e0a7941dbfa93efe4ade1c40c6d54bc

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.