"use client";
import { useState } from "react";
import {
UserPlus,
Mail,
X,
Send,
Clock,
RotateCcw,
Check,
Loader2,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
type Role = "admin" | "member" | "viewer";
type PendingInvite = {
id: string;
email: string;
role: Role;
sentAt: Date;
};
const roles = [
{ id: "admin" as Role, label: "Admin", description: "Full access to all features" },
{ id: "member" as Role, label: "Member", description: "Can edit and create content" },
{ id: "viewer" as Role, label: "Viewer", description: "Read-only access" },
];
const initialPendingInvites: PendingInvite[] = [
{ id: "1", email: "[email protected]", role: "member", sentAt: new Date(Date.now() - 86400000) },
{ id: "2", email: "[email protected]", role: "viewer", sentAt: new Date(Date.now() - 172800000) },
];
export const title = "React Dialog Block Invite Team";
export default function DialogInviteTeam() {
const [open, setOpen] = useState(false);
const [email, setEmail] = useState("");
const [role, setRole] = useState<Role>("member");
const [pendingInvites, setPendingInvites] = useState<PendingInvite[]>(initialPendingInvites);
const [isSending, setIsSending] = useState(false);
const [sentSuccess, setSentSuccess] = useState(false);
const [error, setError] = useState("");
const isValidEmail = (email: string) => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
};
const isDuplicate = (email: string) => {
return pendingInvites.some((invite) => invite.email.toLowerCase() === email.toLowerCase());
};
const handleSendInvite = async () => {
setError("");
if (!email) {
setError("Please enter an email address");
return;
}
if (!isValidEmail(email)) {
setError("Please enter a valid email address");
return;
}
if (isDuplicate(email)) {
setError("This email has already been invited");
return;
}
setIsSending(true);
await new Promise((resolve) => setTimeout(resolve, 1000));
const newInvite: PendingInvite = {
id: Date.now().toString(),
email,
role,
sentAt: new Date(),
};
setPendingInvites((prev) => [newInvite, ...prev]);
setEmail("");
setIsSending(false);
setSentSuccess(true);
setTimeout(() => setSentSuccess(false), 2000);
};
const handleResendInvite = async (inviteId: string) => {
setPendingInvites((prev) =>
prev.map((invite) =>
invite.id === inviteId ? { ...invite, sentAt: new Date() } : invite
)
);
};
const handleCancelInvite = (inviteId: string) => {
setPendingInvites((prev) => prev.filter((invite) => invite.id !== inviteId));
};
const formatDate = (date: Date) => {
const now = new Date();
const diff = now.getTime() - date.getTime();
const days = Math.floor(diff / 86400000);
if (days === 0) return "Today";
if (days === 1) return "Yesterday";
return days + " days ago";
};
const getInitials = (email: string) => {
return email.substring(0, 2).toUpperCase();
};
const getRoleBadgeVariant = (role: Role) => {
switch (role) {
case "admin":
return "default";
case "member":
return "secondary";
case "viewer":
return "outline";
}
};
return (
<Dialog open={open} onOpenChange={setOpen}>
<div className="flex min-h-[350px] items-center justify-center">
<DialogTrigger asChild>
<Button variant="outline">
<UserPlus className="mr-2 h-4 w-4" />
Invite Team
</Button>
</DialogTrigger>
</div>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<UserPlus className="h-5 w-5" />
Invite Team Members
</DialogTitle>
<DialogDescription>
Invite colleagues to collaborate on this project.
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-3">
<div className="space-y-2">
<Label htmlFor="email">Email address</Label>
<div className="flex gap-2">
<div className="relative flex-1">
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="email"
type="email"
placeholder="[email protected]"
value={email}
onChange={(e) => {
setEmail(e.target.value);
setError("");
}}
className="pl-9"
onKeyDown={(e) => e.key === "Enter" && handleSendInvite()}
/>
</div>
</div>
{error && (
<p className="text-sm text-destructive">{error}</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="role">Role</Label>
<Select value={role} onValueChange={(v) => setRole(v as Role)}>
<SelectTrigger id="role">
<SelectValue />
</SelectTrigger>
<SelectContent>
{roles.map((r) => (
<SelectItem key={r.id} value={r.id}>
{r.label}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
{roles.find((r) => r.id === role)?.description}
</p>
</div>
<Button
className="w-full"
onClick={handleSendInvite}
disabled={isSending || !email}
>
{isSending ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Sending...
</>
) : sentSuccess ? (
<>
<Check className="mr-2 h-4 w-4" />
Invite Sent!
</>
) : (
<>
<Send className="mr-2 h-4 w-4" />
Send Invite
</>
)}
</Button>
</div>
{pendingInvites.length > 0 && (
<>
<Separator />
<div className="space-y-3">
<div className="flex items-center justify-between">
<Label className="text-sm">Pending Invitations</Label>
<Badge variant="secondary">{pendingInvites.length}</Badge>
</div>
<div className="space-y-2">
{pendingInvites.map((invite) => (
<div
key={invite.id}
className="flex items-center justify-between rounded-lg border p-3"
>
<div className="flex items-center gap-3">
<Avatar className="h-8 w-8">
<AvatarFallback className="text-xs">
{getInitials(invite.email)}
</AvatarFallback>
</Avatar>
<div className="space-y-1">
<p className="text-sm font-medium leading-none">
{invite.email}
</p>
<div className="flex items-center gap-2">
<Badge variant={getRoleBadgeVariant(invite.role)} className="text-xs">
{invite.role}
</Badge>
<span className="flex items-center text-xs text-muted-foreground">
<Clock className="mr-1 h-3 w-3" />
{formatDate(invite.sentAt)}
</span>
</div>
</div>
</div>
<div className="flex items-center gap-1">
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={() => handleResendInvite(invite.id)}
title="Resend invite"
>
<RotateCcw className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={() => handleCancelInvite(invite.id)}
title="Cancel invite"
>
<X className="h-4 w-4" />
</Button>
</div>
</div>
))}
</div>
</div>
</>
)}
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setOpen(false)}>
Done
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}