Add translation keys in settings/access/invitations

This commit is contained in:
vlalx
2025-05-07 17:46:16 +03:00
parent 491b4e7b18
commit 840d5c2b66
3 changed files with 46 additions and 51 deletions

View File

@@ -22,7 +22,7 @@ export function InvitationsDataTable<TData, TValue>({
<DataTable <DataTable
columns={columns} columns={columns}
data={data} data={data}
title="Invitations" title={t('invite')}
searchPlaceholder={t('inviteSearch')} searchPlaceholder={t('inviteSearch')}
searchColumn="email" searchColumn="email"
/> />

View File

@@ -17,6 +17,7 @@ import { useOrgContext } from "@app/hooks/useOrgContext";
import { toast } from "@app/hooks/useToast"; import { toast } from "@app/hooks/useToast";
import { createApiClient } from "@app/lib/api"; import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
import { useTranslations } from "next-intl";
export type InvitationRow = { export type InvitationRow = {
id: string; id: string;
@@ -39,6 +40,8 @@ export default function InvitationsTable({
const [selectedInvitation, setSelectedInvitation] = const [selectedInvitation, setSelectedInvitation] =
useState<InvitationRow | null>(null); useState<InvitationRow | null>(null);
const t = useTranslations();
const api = createApiClient(useEnvContext()); const api = createApiClient(useEnvContext());
const { org } = useOrgContext(); const { org } = useOrgContext();
@@ -51,7 +54,7 @@ export default function InvitationsTable({
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0"> <Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">Open menu</span> <span className="sr-only">{t('openMenu')}</span>
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
@@ -62,7 +65,7 @@ export default function InvitationsTable({
setSelectedInvitation(invitation); setSelectedInvitation(invitation);
}} }}
> >
<span>Regenerate Invitation</span> <span>{t('inviteRegenerate')}</span>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
onClick={() => { onClick={() => {
@@ -71,7 +74,7 @@ export default function InvitationsTable({
}} }}
> >
<span className="text-red-500"> <span className="text-red-500">
Remove Invitation {t('inviteRemove')}
</span> </span>
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
@@ -112,17 +115,16 @@ export default function InvitationsTable({
.catch((e) => { .catch((e) => {
toast({ toast({
variant: "destructive", variant: "destructive",
title: "Failed to remove invitation", title: t('inviteRemoveError'),
description: description: t('inviteRemoveErrorDescription')
"An error occurred while removing the invitation."
}); });
}); });
if (res && res.status === 200) { if (res && res.status === 200) {
toast({ toast({
variant: "default", variant: "default",
title: "Invitation removed", title: t('inviteRemoved'),
description: `The invitation for ${selectedInvitation.email} has been removed.` description: t('inviteRemovedDescription', {email: selectedInvitation.email})
}); });
setInvitations((prev) => setInvitations((prev) =>
@@ -146,23 +148,20 @@ export default function InvitationsTable({
dialog={ dialog={
<div className="space-y-4"> <div className="space-y-4">
<p> <p>
Are you sure you want to remove the invitation for{" "} {t('inviteQuestionRemove', {email: selectedInvitation?.email ?? ''})}
<b>{selectedInvitation?.email}</b>?
</p> </p>
<p> <p>
Once removed, this invitation will no longer be {t('inviteMessageRemove')}
valid. You can always re-invite the user later.
</p> </p>
<p> <p>
To confirm, please type the email address of the {t('inviteMessageConfirm')}
invitation below.
</p> </p>
</div> </div>
} }
buttonText="Confirm Remove Invitation" buttonText={t('inviteRemoveConfirm')}
onConfirm={removeInvitation} onConfirm={removeInvitation}
string={selectedInvitation?.email ?? ""} string={selectedInvitation?.email ?? ""}
title="Remove Invitation" title={t('inviteRemove')}
/> />
<RegenerateInvitationForm <RegenerateInvitationForm
open={isRegenerateModalOpen} open={isRegenerateModalOpen}

View File

@@ -24,6 +24,7 @@ import {
SelectValue SelectValue
} from "@app/components/ui/select"; } from "@app/components/ui/select";
import { Label } from "@app/components/ui/label"; import { Label } from "@app/components/ui/label";
import { useTranslations } from "next-intl";
type RegenerateInvitationFormProps = { type RegenerateInvitationFormProps = {
open: boolean; open: boolean;
@@ -56,14 +57,16 @@ export default function RegenerateInvitationForm({
const api = createApiClient(useEnvContext()); const api = createApiClient(useEnvContext());
const { org } = useOrgContext(); const { org } = useOrgContext();
const t = useTranslations();
const validForOptions = [ const validForOptions = [
{ hours: 24, name: "1 day" }, { hours: 24, name: t('day', { count: 1 }) },
{ hours: 48, name: "2 days" }, { hours: 48, name: t('day', { count: 2 }) },
{ hours: 72, name: "3 days" }, { hours: 72, name: t('day', { count: 3 }) },
{ hours: 96, name: "4 days" }, { hours: 96, name: t('day', { count: 4 }) },
{ hours: 120, name: "5 days" }, { hours: 120, name: t('day', { count: 5 }) },
{ hours: 144, name: "6 days" }, { hours: 144, name: t('day', { count: 6 }) },
{ hours: 168, name: "7 days" } { hours: 168, name: t('day', { count: 7 }) }
]; ];
useEffect(() => { useEffect(() => {
@@ -79,9 +82,8 @@ export default function RegenerateInvitationForm({
if (!org?.org.orgId) { if (!org?.org.orgId) {
toast({ toast({
variant: "destructive", variant: "destructive",
title: "Organization ID Missing", title: t('orgMissing'),
description: description: t('orgMissingMessage'),
"Unable to regenerate invitation without an organization ID.",
duration: 5000 duration: 5000
}); });
return; return;
@@ -105,15 +107,15 @@ export default function RegenerateInvitationForm({
if (sendEmail) { if (sendEmail) {
toast({ toast({
variant: "default", variant: "default",
title: "Invitation Regenerated", title: t('inviteRegenerated'),
description: `A new invitation has been sent to ${invitation.email}.`, description: t('inviteSent', {email: invitation.email}),
duration: 5000 duration: 5000
}); });
} else { } else {
toast({ toast({
variant: "default", variant: "default",
title: "Invitation Regenerated", title: t('inviteRegenerated'),
description: `A new invitation has been generated for ${invitation.email}.`, description: t('inviteGenerate', {email: invitation.email}),
duration: 5000 duration: 5000
}); });
} }
@@ -130,24 +132,22 @@ export default function RegenerateInvitationForm({
if (error.response?.status === 409) { if (error.response?.status === 409) {
toast({ toast({
variant: "destructive", variant: "destructive",
title: "Duplicate Invite", title: t('inviteDuplicateError'),
description: "An invitation for this user already exists.", description: t('inviteDuplicateErrorDescription'),
duration: 5000 duration: 5000
}); });
} else if (error.response?.status === 429) { } else if (error.response?.status === 429) {
toast({ toast({
variant: "destructive", variant: "destructive",
title: "Rate Limit Exceeded", title: t('inviteRateLimitError'),
description: description: t('inviteRateLimitErrorDescription'),
"You have exceeded the limit of 3 regenerations per hour. Please try again later.",
duration: 5000 duration: 5000
}); });
} else { } else {
toast({ toast({
variant: "destructive", variant: "destructive",
title: "Failed to Regenerate Invitation", title: t('inviteRegenerateError'),
description: description: t('inviteRegenerateErrorDescription'),
"An error occurred while regenerating the invitation.",
duration: 5000 duration: 5000
}); });
} }
@@ -168,18 +168,16 @@ export default function RegenerateInvitationForm({
> >
<CredenzaContent> <CredenzaContent>
<CredenzaHeader> <CredenzaHeader>
<CredenzaTitle>Regenerate Invitation</CredenzaTitle> <CredenzaTitle>{t('inviteRegenerate')}</CredenzaTitle>
<CredenzaDescription> <CredenzaDescription>
Revoke previous invitation and create a new one {t('inviteRegenerateDescription')}
</CredenzaDescription> </CredenzaDescription>
</CredenzaHeader> </CredenzaHeader>
<CredenzaBody> <CredenzaBody>
{!inviteLink ? ( {!inviteLink ? (
<div> <div>
<p> <p>
Are you sure you want to regenerate the {t('inviteQuestionRegenerate', {email: invitation?.email ?? ''})}
invitation for <b>{invitation?.email}</b>? This
will revoke the previous invitation.
</p> </p>
<div className="flex items-center space-x-2 mt-4"> <div className="flex items-center space-x-2 mt-4">
<Checkbox <Checkbox
@@ -190,12 +188,12 @@ export default function RegenerateInvitationForm({
} }
/> />
<label htmlFor="send-email"> <label htmlFor="send-email">
Send email notification to the user {t('inviteSentEmail')}
</label> </label>
</div> </div>
<div className="mt-4 space-y-2"> <div className="mt-4 space-y-2">
<Label> <Label>
Validity Period {t('inviteValidityPeriod')}
</Label> </Label>
<Select <Select
value={validHours.toString()} value={validHours.toString()}
@@ -204,7 +202,7 @@ export default function RegenerateInvitationForm({
} }
> >
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select validity period" /> <SelectValue placeholder={t('inviteValidityPeriodSelect')} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{validForOptions.map((option) => ( {validForOptions.map((option) => (
@@ -222,9 +220,7 @@ export default function RegenerateInvitationForm({
) : ( ) : (
<div className="space-y-4 max-w-md"> <div className="space-y-4 max-w-md">
<p> <p>
The invitation has been regenerated. The user {t('inviteRegenerateMessage')}
must access the link below to accept the
invitation.
</p> </p>
<CopyTextBox text={inviteLink} wrapText={false} /> <CopyTextBox text={inviteLink} wrapText={false} />
</div> </div>
@@ -240,7 +236,7 @@ export default function RegenerateInvitationForm({
onClick={handleRegenerate} onClick={handleRegenerate}
loading={loading} loading={loading}
> >
Regenerate {t('inviteRegenerateButton')}
</Button> </Button>
</> </>
) : ( ) : (