{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "settings-team-members",
  "title": "Settings Team Members",
  "description": "Manage team members, roles, and invitations.",
  "registryDependencies": [
    "avatar",
    "badge",
    "button",
    "card",
    "dialog",
    "dropdown-menu",
    "field",
    "input-group",
    "select",
    "textarea"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/settings/settings-team-members.tsx",
      "content": "\"use client\";\n\nimport {\n  Crown,\n  Loader2,\n  MoreVertical,\n  Send,\n  Trash2,\n  UserPlus,\n} from \"lucide-react\";\nimport { useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  Avatar,\n  AvatarFallback,\n  AvatarImage,\n} from \"@/registry/new-york/ui/avatar\";\nimport { Badge } from \"@/registry/new-york/ui/badge\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/registry/new-york/ui/card\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogFooter,\n  DialogHeader,\n  DialogTitle,\n  DialogTrigger,\n} from \"@/registry/new-york/ui/dialog\";\nimport {\n  DropdownMenu,\n  DropdownMenuContent,\n  DropdownMenuItem,\n  DropdownMenuSeparator,\n  DropdownMenuTrigger,\n} from \"@/registry/new-york/ui/dropdown-menu\";\nimport {\n  Field,\n  FieldContent,\n  FieldDescription,\n  FieldError,\n  FieldLabel,\n} from \"@/registry/new-york/ui/field\";\nimport {\n  InputGroup,\n  InputGroupInput,\n} from \"@/registry/new-york/ui/input-group\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/registry/new-york/ui/select\";\nimport { Textarea } from \"@/registry/new-york/ui/textarea\";\n\nexport interface TeamMember {\n  id: string;\n  name: string;\n  email: string;\n  avatar?: string;\n  role: \"owner\" | \"admin\" | \"member\" | \"viewer\";\n  status: \"active\" | \"invited\" | \"suspended\";\n  lastActive?: Date;\n  joinedAt: Date;\n}\n\nexport interface SettingsTeamMembersProps {\n  members?: TeamMember[];\n  currentUserId?: string;\n  onInvite?: (data: {\n    email: string;\n    role: \"admin\" | \"member\" | \"viewer\";\n    message?: string;\n  }) => Promise<void>;\n  onRemove?: (memberId: string) => Promise<void>;\n  onChangeRole?: (\n    memberId: string,\n    role: \"admin\" | \"member\" | \"viewer\"\n  ) => Promise<void>;\n  onTransferOwnership?: (memberId: string) => Promise<void>;\n  className?: string;\n}\n\nconst roleDescriptions = {\n  owner: \"Full access to all settings and billing\",\n  admin: \"Can manage team members and settings\",\n  member: \"Can create and edit content\",\n  viewer: \"Read-only access\",\n};\n\nfunction formatDate(date: Date): string {\n  return new Intl.DateTimeFormat(\"en-US\", {\n    month: \"short\",\n    day: \"numeric\",\n    year: \"numeric\",\n  }).format(date);\n}\n\nfunction formatLastActive(date?: Date): string {\n  if (!date) return \"Never\";\n  const now = Date.now();\n  const diff = now - date.getTime();\n  const minutes = Math.floor(diff / 60_000);\n  const hours = Math.floor(minutes / 60);\n  const days = Math.floor(hours / 24);\n\n  if (minutes < 1) return \"Just now\";\n  if (minutes < 60) return `${minutes}m ago`;\n  if (hours < 24) return `${hours}h ago`;\n  if (days < 7) return `${days}d ago`;\n  return formatDate(date);\n}\n\nexport default function SettingsTeamMembers({\n  members = [],\n  currentUserId,\n  onInvite,\n  onRemove,\n  onChangeRole,\n  onTransferOwnership,\n  className,\n}: SettingsTeamMembersProps) {\n  const [isInviting, setIsInviting] = useState(false);\n  const [inviteDialogOpen, setInviteDialogOpen] = useState(false);\n  const [errors, setErrors] = useState<Record<string, string>>({});\n  const [inviteData, setInviteData] = useState({\n    email: \"\",\n    role: \"member\" as \"admin\" | \"member\" | \"viewer\",\n    message: \"\",\n  });\n\n  const handleInvite = async () => {\n    setErrors({});\n\n    if (!inviteData.email.trim()) {\n      setErrors({ email: \"Email is required\" });\n      return;\n    }\n\n    const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n    if (!emailRegex.test(inviteData.email)) {\n      setErrors({ email: \"Please enter a valid email address\" });\n      return;\n    }\n\n    setIsInviting(true);\n    try {\n      await onInvite?.(inviteData);\n      setInviteData({ email: \"\", role: \"member\", message: \"\" });\n      setInviteDialogOpen(false);\n    } catch (error) {\n      setErrors({\n        _general:\n          error instanceof Error ? error.message : \"Failed to send invitation\",\n      });\n    } finally {\n      setIsInviting(false);\n    }\n  };\n\n  const getInitials = (name: string): string =>\n    name\n      .split(\" \")\n      .map((n) => n[0])\n      .join(\"\")\n      .toUpperCase()\n      .slice(0, 2);\n\n  return (\n    <Card className={cn(\"w-full shadow-xs\", className)}>\n      <CardHeader>\n        <div className=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\">\n          <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n            <CardTitle className=\"wrap-break-word\">Team Members</CardTitle>\n            <CardDescription className=\"wrap-break-word\">\n              Manage team members and their roles\n            </CardDescription>\n          </div>\n          <Dialog onOpenChange={setInviteDialogOpen} open={inviteDialogOpen}>\n            <DialogTrigger asChild>\n              <Button className=\"w-full shrink-0 sm:w-auto\" type=\"button\">\n                <UserPlus className=\"size-4\" />\n                <span className=\"whitespace-nowrap\">Invite Member</span>\n              </Button>\n            </DialogTrigger>\n            <DialogContent className=\"sm:max-w-md\">\n              <DialogHeader>\n                <DialogTitle>Invite Team Member</DialogTitle>\n                <DialogDescription>\n                  Send an invitation to join your team\n                </DialogDescription>\n              </DialogHeader>\n              <div className=\"flex flex-col gap-4\">\n                {errors._general && (\n                  <div className=\"rounded-lg border border-destructive/50 bg-destructive/10 p-3\">\n                    <p className=\"text-destructive text-sm\">\n                      {errors._general}\n                    </p>\n                  </div>\n                )}\n\n                <Field>\n                  <FieldLabel htmlFor=\"invite-email\">\n                    Email <span className=\"text-destructive\">*</span>\n                  </FieldLabel>\n                  <FieldContent>\n                    <InputGroup>\n                      <InputGroupInput\n                        id=\"invite-email\"\n                        onChange={(e) =>\n                          setInviteData((prev) => ({\n                            ...prev,\n                            email: e.target.value,\n                          }))\n                        }\n                        placeholder=\"colleague@example.com\"\n                        type=\"email\"\n                        value={inviteData.email}\n                      />\n                    </InputGroup>\n                    {errors.email && <FieldError>{errors.email}</FieldError>}\n                  </FieldContent>\n                </Field>\n\n                <Field>\n                  <FieldLabel htmlFor=\"invite-role\">Role</FieldLabel>\n                  <FieldContent>\n                    <Select\n                      onValueChange={(value: \"admin\" | \"member\" | \"viewer\") =>\n                        setInviteData((prev) => ({ ...prev, role: value }))\n                      }\n                      value={inviteData.role}\n                    >\n                      <SelectTrigger id=\"invite-role\">\n                        <SelectValue />\n                      </SelectTrigger>\n                      <SelectContent>\n                        <SelectItem value=\"admin\">Admin</SelectItem>\n                        <SelectItem value=\"member\">Member</SelectItem>\n                        <SelectItem value=\"viewer\">Viewer</SelectItem>\n                      </SelectContent>\n                    </Select>\n                    <FieldDescription>\n                      {roleDescriptions[inviteData.role]}\n                    </FieldDescription>\n                  </FieldContent>\n                </Field>\n\n                <Field>\n                  <FieldLabel htmlFor=\"invite-message\">\n                    Message (Optional)\n                  </FieldLabel>\n                  <FieldContent>\n                    <Textarea\n                      id=\"invite-message\"\n                      onChange={(e) =>\n                        setInviteData((prev) => ({\n                          ...prev,\n                          message: e.target.value,\n                        }))\n                      }\n                      placeholder=\"Add a personal message...\"\n                      rows={3}\n                      value={inviteData.message}\n                    />\n                  </FieldContent>\n                </Field>\n              </div>\n              <DialogFooter>\n                <Button\n                  onClick={() => setInviteDialogOpen(false)}\n                  type=\"button\"\n                  variant=\"outline\"\n                >\n                  Cancel\n                </Button>\n                <Button\n                  disabled={isInviting}\n                  onClick={handleInvite}\n                  type=\"button\"\n                >\n                  {isInviting ? (\n                    <>\n                      <Loader2 className=\"size-4 animate-spin\" />\n                      Sending…\n                    </>\n                  ) : (\n                    <>\n                      <Send className=\"size-4\" />\n                      Send Invitation\n                    </>\n                  )}\n                </Button>\n              </DialogFooter>\n            </DialogContent>\n          </Dialog>\n        </div>\n      </CardHeader>\n      <CardContent>\n        {members.length === 0 ? (\n          <div className=\"flex flex-col items-center justify-center gap-4 py-12 text-center\">\n            <div className=\"flex size-12 items-center justify-center rounded-full bg-muted\">\n              <UserPlus className=\"size-6 text-muted-foreground\" />\n            </div>\n            <div className=\"flex flex-col gap-2\">\n              <p className=\"font-medium text-sm\">No team members</p>\n              <p className=\"text-muted-foreground text-sm\">\n                Invite team members to collaborate\n              </p>\n            </div>\n          </div>\n        ) : (\n          <div className=\"flex flex-col gap-4\">\n            {members.map((member) => {\n              const isCurrentUser = member.id === currentUserId;\n              const isOwner = member.role === \"owner\";\n              const canManage = !(isOwner || isCurrentUser);\n\n              return (\n                <div\n                  className=\"flex flex-col gap-4 rounded-lg border p-4 sm:flex-row sm:items-center\"\n                  key={member.id}\n                >\n                  <div className=\"flex min-w-0 flex-1 items-center gap-3\">\n                    <Avatar className=\"size-10\">\n                      <AvatarImage alt={member.name} src={member.avatar} />\n                      <AvatarFallback>\n                        {getInitials(member.name)}\n                      </AvatarFallback>\n                    </Avatar>\n                    <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n                      <div className=\"flex flex-wrap items-center gap-2\">\n                        <h4 className=\"font-medium text-sm\">{member.name}</h4>\n                        {isOwner && (\n                          <Badge\n                            className=\"flex items-center gap-1 text-xs\"\n                            variant=\"default\"\n                          >\n                            <Crown className=\"size-3\" />\n                            <span>Owner</span>\n                          </Badge>\n                        )}\n                        {member.status === \"invited\" && (\n                          <Badge className=\"text-xs\" variant=\"secondary\">\n                            Invited\n                          </Badge>\n                        )}\n                        {member.status === \"suspended\" && (\n                          <Badge className=\"text-xs\" variant=\"destructive\">\n                            Suspended\n                          </Badge>\n                        )}\n                        {isCurrentUser && (\n                          <Badge className=\"text-xs\" variant=\"outline\">\n                            You\n                          </Badge>\n                        )}\n                      </div>\n                      <p className=\"text-muted-foreground text-xs\">\n                        {member.email}\n                      </p>\n                      <div className=\"flex flex-wrap items-center gap-3 text-muted-foreground text-xs\">\n                        <span className=\"capitalize\">{member.role}</span>\n                        <span>•</span>\n                        <span>Joined {formatDate(member.joinedAt)}</span>\n                        {member.lastActive && (\n                          <>\n                            <span>•</span>\n                            <span>\n                              Active {formatLastActive(member.lastActive)}\n                            </span>\n                          </>\n                        )}\n                      </div>\n                    </div>\n                  </div>\n                  {canManage && (\n                    <DropdownMenu>\n                      <DropdownMenuTrigger asChild>\n                        <Button\n                          aria-label={`More options for ${member.name}`}\n                          size=\"icon-sm\"\n                          type=\"button\"\n                          variant=\"ghost\"\n                        >\n                          <MoreVertical className=\"size-4\" />\n                        </Button>\n                      </DropdownMenuTrigger>\n                      <DropdownMenuContent align=\"end\">\n                        {onChangeRole && (\n                          <>\n                            <DropdownMenuItem\n                              onClick={() => onChangeRole(member.id, \"admin\")}\n                            >\n                              Change to Admin\n                            </DropdownMenuItem>\n                            <DropdownMenuItem\n                              onClick={() => onChangeRole(member.id, \"member\")}\n                            >\n                              Change to Member\n                            </DropdownMenuItem>\n                            <DropdownMenuItem\n                              onClick={() => onChangeRole(member.id, \"viewer\")}\n                            >\n                              Change to Viewer\n                            </DropdownMenuItem>\n                            <DropdownMenuSeparator />\n                          </>\n                        )}\n                        {onTransferOwnership && (\n                          <>\n                            <DropdownMenuItem\n                              className=\"flex items-center gap-2\"\n                              onClick={() => onTransferOwnership(member.id)}\n                            >\n                              <Crown className=\"size-4\" />\n                              <span>Transfer Ownership</span>\n                            </DropdownMenuItem>\n                            <DropdownMenuSeparator />\n                          </>\n                        )}\n                        {onRemove && (\n                          <DropdownMenuItem\n                            className=\"flex items-center gap-2 text-destructive\"\n                            onClick={() => onRemove(member.id)}\n                          >\n                            <Trash2 className=\"size-4\" />\n                            <span>Remove Member</span>\n                          </DropdownMenuItem>\n                        )}\n                      </DropdownMenuContent>\n                    </DropdownMenu>\n                  )}\n                </div>\n              );\n            })}\n          </div>\n        )}\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "settings"
  ],
  "type": "registry:ui"
}