'use client' import { useLocale, useTranslations } from 'next-intl' import { useState, useEffect, useCallback } from 'react' import { Loader2, Mail, Plus, RefreshCw, Trash2 } from 'lucide-react' import { AttnLine } from '@/components/ui/attn-line' import { Button } from '@/components/ui/button' import { ConfirmDialog } from '@/components/ui/confirm-dialog' import { useToast } from '@/components/ui/use-toast' import { SettingsGroup, SettingsInput, } from '@/components/settings/SettingsRows' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { parseTeamMembersPayload } from '@/components/settings/members-payload' import { getErrorMessage, type ErrorLocale } from '@/lib/errors/get-error-message' import { cn, formatDateLong } from '@/lib/utils' // Role dropdowns use the Radix Select for its styled popup (the native // void handleRoleChange(member, value)} disabled={changingRoleId === member.id} > {isOwner && {t('role_owner')}} {t('role_admin')} {t('role_member')} ) : ( {roleLabel(member.role)} )} {removable && ( )} ) })} {/* Pending invitations continue the same list, visually quieter. */} {canManage && invitations.map((inv) => { const expired = new Date(inv.expires_at) <= new Date() return (

{inv.email} ·{' '} {expired ? t('invite_expired') : t('invite_expires', { date: formatDateLong(inv.expires_at) })}

{roleLabel(inv.role)}
) })} {/* Shareable accept link from the latest invite/re-send: one sentence, not a banner. The failed-send case is the page's single attn line; the sent case is a quiet muted line with the same copy action, since the link is a legitimate share path either way. */} {canManage && shareInvite && (
{shareInvite.sent ? (

{t('invite_link_sent', { email: shareInvite.email })}{' '}

) : ( void handleCopyInviteLink(shareInvite.url), }} > {t('invite_link_not_sent', { email: shareInvite.email })} )}
)} {/* Inline invite: the list's own last row instead of a separate card. */} {canManage && (
setInviteEmail(e.target.value)} disabled={isSending} required className="border-border sm:flex-1" /> )} {/* Confirm up front: the dialog describes the outcome (loses access to every client company via the team) before anything is deleted. */} { if (!open) setRemoveTarget(null) }} title={t('remove_confirm_title')} description={ removeTarget ? t('remove_confirm_body', { email: removeTarget.email, team: teamName || t('team_fallback'), }) : undefined } confirmLabel={t('remove_confirm_button')} destructive onConfirm={async () => { if (removeTarget) await handleRemove(removeTarget) }} /> ) }