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
columns={columns}
data={data}
title="Invitations"
title={t('invite')}
searchPlaceholder={t('inviteSearch')}
searchColumn="email"
/>

View File

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

View File

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