import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { Modal, Stack, Text, Button, TextInput, Select, Paper, Checkbox, Textarea, SegmentedControl, Tooltip, CloseButton, Box, Group, } from '@mantine/core'; import LocalIcon from '@app/components/shared/LocalIcon'; import { alert } from '@app/components/toast'; import { userManagementService } from '@app/services/userManagementService'; import { teamService, Team } from '@app/services/teamService'; import { Z_INDEX_OVER_CONFIG_MODAL } from '@app/styles/zIndex'; import { useAppConfig } from '@app/contexts/AppConfigContext'; interface InviteMembersModalProps { opened: boolean; onClose: () => void; } export default function InviteMembersModal({ opened, onClose }: InviteMembersModalProps) { const { t } = useTranslation(); const { config } = useAppConfig(); const [teams, setTeams] = useState([]); const [processing, setProcessing] = useState(false); const [inviteMode, setInviteMode] = useState<'email' | 'direct' | 'link'>('direct'); const [generatedInviteLink, setGeneratedInviteLink] = useState(null); // License information const [licenseInfo, setLicenseInfo] = useState<{ maxAllowedUsers: number; availableSlots: number; grandfatheredUserCount: number; licenseMaxUsers: number; premiumEnabled: boolean; totalUsers: number; } | null>(null); // Form state for direct invite const [inviteForm, setInviteForm] = useState({ username: '', password: '', role: 'ROLE_USER', teamId: undefined as number | undefined, forceChange: false, }); // Form state for email invite const [emailInviteForm, setEmailInviteForm] = useState({ emails: '', role: 'ROLE_USER', teamId: undefined as number | undefined, }); // Form state for invite link const [inviteLinkForm, setInviteLinkForm] = useState({ email: '', role: 'ROLE_USER', teamId: undefined as number | undefined, expiryHours: 72, sendEmail: false, }); // Fetch teams and license info useEffect(() => { if (opened) { const fetchData = async () => { try { const [adminData, teamsData] = await Promise.all([ userManagementService.getUsers(), teamService.getTeams(), ]); setTeams(teamsData); setLicenseInfo({ maxAllowedUsers: adminData.maxAllowedUsers, availableSlots: adminData.availableSlots, grandfatheredUserCount: adminData.grandfatheredUserCount, licenseMaxUsers: adminData.licenseMaxUsers, premiumEnabled: adminData.premiumEnabled, totalUsers: adminData.totalUsers, }); } catch (error) { console.error('Failed to fetch data:', error); } }; fetchData(); } }, [opened]); const roleOptions = [ { value: 'ROLE_USER', label: t('workspace.people.roleDescriptions.user', 'User'), }, { value: 'ROLE_ADMIN', label: t('workspace.people.roleDescriptions.admin', 'Admin'), }, ]; const teamOptions = teams.map((team) => ({ value: team.id.toString(), label: team.name, })); const handleInviteUser = async () => { if (!inviteForm.username || !inviteForm.password) { alert({ alertType: 'error', title: t('workspace.people.addMember.usernameRequired') }); return; } try { setProcessing(true); await userManagementService.createUser({ username: inviteForm.username, password: inviteForm.password, role: inviteForm.role, teamId: inviteForm.teamId, authType: 'password', forceChange: inviteForm.forceChange, }); alert({ alertType: 'success', title: t('workspace.people.addMember.success') }); onClose(); // Reset form setInviteForm({ username: '', password: '', role: 'ROLE_USER', teamId: undefined, forceChange: false, }); } catch (error: any) { console.error('Failed to invite user:', error); const errorMessage = error.response?.data?.message || error.response?.data?.error || error.message || t('workspace.people.addMember.error'); alert({ alertType: 'error', title: errorMessage }); } finally { setProcessing(false); } }; const handleEmailInvite = async () => { if (!emailInviteForm.emails.trim()) { alert({ alertType: 'error', title: t('workspace.people.emailInvite.emailsRequired', 'Email addresses are required') }); return; } try { setProcessing(true); const response = await userManagementService.inviteUsers({ emails: emailInviteForm.emails, // comma-separated string as required by API role: emailInviteForm.role, teamId: emailInviteForm.teamId, }); if (response.successCount > 0) { alert({ alertType: 'success', title: t('workspace.people.emailInvite.success', { count: response.successCount, defaultValue: `Successfully invited ${response.successCount} user(s)` }) }); onClose(); setEmailInviteForm({ emails: '', role: 'ROLE_USER', teamId: undefined, }); } else { alert({ alertType: 'error', title: t('workspace.people.emailInvite.allFailed', 'Failed to invite users'), body: response.errors || response.error }); } } catch (error: any) { console.error('Failed to invite users:', error); const errorMessage = error.response?.data?.message || error.response?.data?.error || error.message || t('workspace.people.emailInvite.error', 'Failed to send invites'); alert({ alertType: 'error', title: errorMessage }); } finally { setProcessing(false); } }; const handleGenerateInviteLink = async () => { try { setProcessing(true); const response = await userManagementService.generateInviteLink({ email: inviteLinkForm.email || undefined, role: inviteLinkForm.role, teamId: inviteLinkForm.teamId, expiryHours: inviteLinkForm.expiryHours, sendEmail: inviteLinkForm.sendEmail, }); setGeneratedInviteLink(response.inviteUrl); if (inviteLinkForm.sendEmail && inviteLinkForm.email) { alert({ alertType: 'success', title: t('workspace.people.inviteLink.emailSent', 'Invite link generated and sent via email') }); } } catch (error: any) { console.error('Failed to generate invite link:', error); const errorMessage = error.response?.data?.message || error.response?.data?.error || error.message || t('workspace.people.inviteLink.error', 'Failed to generate invite link'); alert({ alertType: 'error', title: errorMessage }); } finally { setProcessing(false); } }; const handleClose = () => { setGeneratedInviteLink(null); setInviteMode('direct'); setInviteForm({ username: '', password: '', role: 'ROLE_USER', teamId: undefined, forceChange: false, }); setEmailInviteForm({ emails: '', role: 'ROLE_USER', teamId: undefined, }); setInviteLinkForm({ email: '', role: 'ROLE_USER', teamId: undefined, expiryHours: 72, sendEmail: false, }); onClose(); }; return ( {/* Header with Icon */} {t('workspace.people.inviteMembers.label', 'Invite Members')} {inviteMode === 'email' && ( {t('workspace.people.inviteMembers.subtitle', 'Type or paste in emails below, separated by commas. Your workspace will be billed by members.')} )} {/* License Warning/Info */} {licenseInfo && ( 0 ? 'info' : 'warning'} width="1rem" height="1rem" /> {licenseInfo.availableSlots > 0 ? t('workspace.people.license.slotsAvailable', { count: licenseInfo.availableSlots, defaultValue: `${licenseInfo.availableSlots} user slot(s) available` }) : t('workspace.people.license.noSlotsAvailable', 'No user slots available')} {t('workspace.people.license.currentUsage', { current: licenseInfo.totalUsers, max: licenseInfo.maxAllowedUsers, defaultValue: `Currently using ${licenseInfo.totalUsers} of ${licenseInfo.maxAllowedUsers} user licenses` })} )} {/* Mode Toggle */}
{ setInviteMode(value as 'email' | 'direct' | 'link'); setGeneratedInviteLink(null); }} data={[ { label: t('workspace.people.inviteMode.username', 'Username'), value: 'direct', }, { label: t('workspace.people.inviteMode.link', 'Link'), value: 'link', }, { label: t('workspace.people.inviteMode.email', 'Email'), value: 'email', disabled: !config?.enableEmailInvites, }, ]} fullWidth />
{/* Link Mode */} {inviteMode === 'link' && ( <> setInviteLinkForm({ ...inviteLinkForm, email: e.currentTarget.value })} description={t('workspace.people.inviteLink.emailDescription', 'If provided, the link will be tied to this email address')} /> setInviteLinkForm({ ...inviteLinkForm, teamId: value ? parseInt(value) : undefined })} clearable comboboxProps={{ withinPortal: true, zIndex: Z_INDEX_OVER_CONFIG_MODAL }} /> setInviteLinkForm({ ...inviteLinkForm, expiryHours: parseInt(e.currentTarget.value) || 72 })} min={1} max={720} /> {inviteLinkForm.email && ( setInviteLinkForm({ ...inviteLinkForm, sendEmail: e.currentTarget.checked })} /> )} {/* Display generated link */} {generatedInviteLink && ( {t('workspace.people.inviteLink.generated', 'Invite Link Generated')} )} )} {/* Email Mode */} {inviteMode === 'email' && config?.enableEmailInvites && ( <>