Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | 65x 65x 65x 65x 65x 65x 65x 21x 21x 21x 21x 21x 21x 21x 21x 21x 65x 65x 23x |
import { useState, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { Button } from '@/components/ui/button';
import { AppDialog } from '@/components/common/AppDialog';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { createInvitation, type InvitationCreateRequest } from '@/services/invitations.service';
import { getAccounts, type AccountListItem } from '@/services/accounts.service';
import { handleApiError } from '@/lib/api';
import { useToast } from "@/hooks/use-toast";
interface CreateInvitationDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSuccess: () => void;
}
export function CreateInvitationDialog({
open,
onOpenChange,
onSuccess,
}: CreateInvitationDialogProps) {
const { toast } = useToast();
const [isLoading, setIsLoading] = useState(false);
const [accounts, setAccounts] = useState<AccountListItem[]>([]);
const [loadingAccounts, setLoadingAccounts] = useState(false);
const [showAccountSelect, setShowAccountSelect] = useState(false);
const { register, handleSubmit, reset, getValues, formState: { errors } } = useForm<InvitationCreateRequest>();
useEffect(() => {
const loadAccounts = async () => {
try {
setLoadingAccounts(true);
const response = await getAccounts({ page_size: 100 });
Eif (response.items && response.items.length > 0) {
setAccounts(response.items);
setShowAccountSelect(true);
}
} catch (error) {
// Ignore error - likely not superadmin, so just don't show account selector
console.debug("Failed to load accounts for invitation selector", error);
} finally {
setLoadingAccounts(false);
}
};
loadAccounts();
}, []);
const onSubmit = async (data: InvitationCreateRequest) => {
setIsLoading(true);
try {
await createInvitation(data);
toast({
title: "Invitation sent",
description: `Invitation sent to ${data.email}`,
});
reset();
onSuccess();
onOpenChange(false);
} catch (err) {
toast({
variant: "destructive",
title: "Error",
description: handleApiError(err),
});
} finally {
setIsLoading(false);
}
};
return (
<AppDialog
open={open}
onOpenChange={onOpenChange}
title="Invite User"
description="Send an invitation email to a new user. They will receive a link to set their password."
footer={
<>
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
disabled={isLoading}
>
Cancel
</Button>
<Button type="submit" form="create-invitation-form" disabled={isLoading}>
{isLoading ? 'Sending...' : 'Send Invitation'}
</Button>
</>
}
>
<form id="create-invitation-form" onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email address</Label>
<Input
id="email"
type="email"
placeholder="user@example.com"
disabled={isLoading}
{...register('email', {
required: 'Email is required',
pattern: {
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
message: "Invalid email address"
}
})}
/>
{errors.email && (
<p className="text-sm text-destructive">{errors.email.message}</p>
)}
</div>
{showAccountSelect && (
<div className="space-y-2">
<Label htmlFor="account">Account (Optional)</Label>
<Select
onValueChange={(value) => reset({ ...getValues(), account_id: value })}
disabled={isLoading || loadingAccounts}
>
<SelectTrigger>
<SelectValue placeholder="Select an account" />
</SelectTrigger>
<SelectContent>
{accounts.map((account) => (
<SelectItem key={account.id} value={account.id}>
{account.name} ({account.account_code})
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">Leave empty to invite to your current account.</p>
</div>
)}
</form>
</AppDialog>
);
}
|