mirror of
https://github.com/documenso/documenso.git
synced 2025-11-21 04:01:45 +10:00
feat: web i18n (#1286)
This commit is contained in:
@ -5,6 +5,8 @@ import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
@ -51,6 +53,7 @@ export const AddTeamEmailDialog = ({ teamId, trigger, ...props }: AddTeamEmailDi
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const form = useForm<TCreateTeamEmailFormSchema>({
|
||||
@ -73,8 +76,8 @@ export const AddTeamEmailDialog = ({ teamId, trigger, ...props }: AddTeamEmailDi
|
||||
});
|
||||
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'We have sent a confirmation email for verification.',
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`We have sent a confirmation email for verification.`),
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
@ -87,17 +90,18 @@ export const AddTeamEmailDialog = ({ teamId, trigger, ...props }: AddTeamEmailDi
|
||||
if (error.code === AppErrorCode.ALREADY_EXISTS) {
|
||||
form.setError('email', {
|
||||
type: 'manual',
|
||||
message: 'This email is already being used by another team.',
|
||||
message: _(msg`This email is already being used by another team.`),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
toast({
|
||||
title: 'An unknown error occurred',
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to add this email. Please try again later.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
description:
|
||||
'We encountered an unknown error while attempting to add this email. Please try again later.',
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -118,17 +122,19 @@ export const AddTeamEmailDialog = ({ teamId, trigger, ...props }: AddTeamEmailDi
|
||||
{trigger ?? (
|
||||
<Button variant="outline" loading={isLoading} className="bg-background">
|
||||
<Plus className="-ml-1 mr-1 h-5 w-5" />
|
||||
Add email
|
||||
<Trans>Add email</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent position="center">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add team email</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Add team email</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
A verification email will be sent to the provided email.
|
||||
<Trans>A verification email will be sent to the provided email.</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@ -143,7 +149,9 @@ export const AddTeamEmailDialog = ({ teamId, trigger, ...props }: AddTeamEmailDi
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>Name</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Name</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input className="bg-background" placeholder="eg. Legal" {...field} />
|
||||
</FormControl>
|
||||
@ -157,7 +165,9 @@ export const AddTeamEmailDialog = ({ teamId, trigger, ...props }: AddTeamEmailDi
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>Email</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Email</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="bg-background"
|
||||
@ -172,11 +182,11 @@ export const AddTeamEmailDialog = ({ teamId, trigger, ...props }: AddTeamEmailDi
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button type="submit" loading={form.formState.isSubmitting}>
|
||||
Add
|
||||
<Trans>Add</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { Loader, TagIcon } from 'lucide-react';
|
||||
@ -30,6 +32,7 @@ export const CreateTeamCheckoutDialog = ({
|
||||
onClose,
|
||||
...props
|
||||
}: CreateTeamCheckoutDialogProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const [interval, setInterval] = useState<'monthly' | 'yearly'>('monthly');
|
||||
@ -44,9 +47,10 @@ export const CreateTeamCheckoutDialog = ({
|
||||
},
|
||||
onError: () =>
|
||||
toast({
|
||||
title: 'Something went wrong',
|
||||
description:
|
||||
'We were unable to create a checkout session. Please try again, or contact support',
|
||||
title: _(msg`Something went wrong`),
|
||||
description: _(
|
||||
msg`We were unable to create a checkout session. Please try again, or contact support`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
}),
|
||||
});
|
||||
@ -77,10 +81,12 @@ export const CreateTeamCheckoutDialog = ({
|
||||
<Dialog {...props} open={pendingTeamId !== null} onOpenChange={handleOnOpenChange}>
|
||||
<DialogContent position="center">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Team checkout</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Team checkout</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
Payment is required to finalise the creation of your team.
|
||||
<Trans>Payment is required to finalise the creation of your team.</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@ -89,7 +95,9 @@ export const CreateTeamCheckoutDialog = ({
|
||||
{isLoading ? (
|
||||
<Loader className="text-documenso h-6 w-6 animate-spin" />
|
||||
) : (
|
||||
<p>Something went wrong</p>
|
||||
<p>
|
||||
<Trans>Something went wrong</Trans>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@ -136,10 +144,12 @@ export const CreateTeamCheckoutDialog = ({
|
||||
)}
|
||||
|
||||
<div className="text-muted-foreground mt-1.5 text-sm">
|
||||
<p>This price includes minimum 5 seats.</p>
|
||||
<p>
|
||||
<Trans>This price includes minimum 5 seats.</Trans>
|
||||
</p>
|
||||
|
||||
<p className="mt-1">
|
||||
Adding and removing seats will adjust your invoice accordingly.
|
||||
<Trans>Adding and removing seats will adjust your invoice accordingly.</Trans>
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
@ -153,7 +163,7 @@ export const CreateTeamCheckoutDialog = ({
|
||||
disabled={isCreatingCheckout}
|
||||
onClick={() => onClose()}
|
||||
>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -166,7 +176,7 @@ export const CreateTeamCheckoutDialog = ({
|
||||
})
|
||||
}
|
||||
>
|
||||
Checkout
|
||||
<Trans>Checkout</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</div>
|
||||
|
||||
@ -5,6 +5,8 @@ import { useEffect, useState } from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { z } from 'zod';
|
||||
@ -47,6 +49,7 @@ const ZCreateTeamFormSchema = ZCreateTeamMutationSchema.pick({
|
||||
type TCreateTeamFormSchema = z.infer<typeof ZCreateTeamFormSchema>;
|
||||
|
||||
export const CreateTeamDialog = ({ trigger, ...props }: CreateTeamDialogProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const router = useRouter();
|
||||
@ -82,8 +85,8 @@ export const CreateTeamDialog = ({ trigger, ...props }: CreateTeamDialogProps) =
|
||||
}
|
||||
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Your team has been created.',
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`Your team has been created.`),
|
||||
duration: 5000,
|
||||
});
|
||||
} catch (err) {
|
||||
@ -92,17 +95,18 @@ export const CreateTeamDialog = ({ trigger, ...props }: CreateTeamDialogProps) =
|
||||
if (error.code === AppErrorCode.ALREADY_EXISTS) {
|
||||
form.setError('teamUrl', {
|
||||
type: 'manual',
|
||||
message: 'This URL is already in use.',
|
||||
message: _(msg`This URL is already in use.`),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
toast({
|
||||
title: 'An unknown error occurred',
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to create a team. Please try again later.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
description:
|
||||
'We encountered an unknown error while attempting to create a team. Please try again later.',
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -131,17 +135,19 @@ export const CreateTeamDialog = ({ trigger, ...props }: CreateTeamDialogProps) =
|
||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
||||
{trigger ?? (
|
||||
<Button className="flex-shrink-0" variant="secondary">
|
||||
Create team
|
||||
<Trans>Create team</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent position="center">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create team</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Create team</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
Create a team to collaborate with your team members.
|
||||
<Trans>Create a team to collaborate with your team members.</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@ -156,7 +162,9 @@ export const CreateTeamDialog = ({ trigger, ...props }: CreateTeamDialogProps) =
|
||||
name="teamName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>Team Name</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Team Name</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
className="bg-background"
|
||||
@ -184,15 +192,19 @@ export const CreateTeamDialog = ({ trigger, ...props }: CreateTeamDialogProps) =
|
||||
name="teamUrl"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>Team URL</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Team URL</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input className="bg-background" {...field} />
|
||||
</FormControl>
|
||||
{!form.formState.errors.teamUrl && (
|
||||
<span className="text-foreground/50 text-xs font-normal">
|
||||
{field.value
|
||||
? `${WEBAPP_BASE_URL}/t/${field.value}`
|
||||
: 'A unique URL to identify your team'}
|
||||
{field.value ? (
|
||||
`${WEBAPP_BASE_URL}/t/${field.value}`
|
||||
) : (
|
||||
<Trans>A unique URL to identify your team</Trans>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@ -203,7 +215,7 @@ export const CreateTeamDialog = ({ trigger, ...props }: CreateTeamDialogProps) =
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -211,7 +223,7 @@ export const CreateTeamDialog = ({ trigger, ...props }: CreateTeamDialogProps) =
|
||||
data-testid="dialog-create-team-button"
|
||||
loading={form.formState.isSubmitting}
|
||||
>
|
||||
Create Team
|
||||
<Trans>Create Team</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
|
||||
@ -5,6 +5,8 @@ import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
@ -42,13 +44,14 @@ export const DeleteTeamDialog = ({ trigger, teamId, teamName }: DeleteTeamDialog
|
||||
const router = useRouter();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const deleteMessage = `delete ${teamName}`;
|
||||
|
||||
const ZDeleteTeamFormSchema = z.object({
|
||||
teamName: z.literal(deleteMessage, {
|
||||
errorMap: () => ({ message: `You must enter '${deleteMessage}' to proceed` }),
|
||||
errorMap: () => ({ message: _(msg`You must enter '${deleteMessage}' to proceed`) }),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -66,8 +69,8 @@ export const DeleteTeamDialog = ({ trigger, teamId, teamName }: DeleteTeamDialog
|
||||
await deleteTeam({ teamId });
|
||||
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Your team has been successfully deleted.',
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`Your team has been successfully deleted.`),
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
@ -78,20 +81,22 @@ export const DeleteTeamDialog = ({ trigger, teamId, teamName }: DeleteTeamDialog
|
||||
const error = AppError.parseError(err);
|
||||
|
||||
let toastError: Toast = {
|
||||
title: 'An unknown error occurred',
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to delete this team. Please try again later.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
description:
|
||||
'We encountered an unknown error while attempting to delete this team. Please try again later.',
|
||||
};
|
||||
|
||||
if (error.code === 'resource_missing') {
|
||||
toastError = {
|
||||
title: 'Unable to delete team',
|
||||
title: _(msg`Unable to delete team`),
|
||||
description: _(
|
||||
msg`Something went wrong while updating the team billing subscription, please contact support.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
duration: 15000,
|
||||
description:
|
||||
'Something went wrong while updating the team billing subscription, please contact support.',
|
||||
};
|
||||
}
|
||||
|
||||
@ -108,16 +113,24 @@ export const DeleteTeamDialog = ({ trigger, teamId, teamName }: DeleteTeamDialog
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}>
|
||||
<DialogTrigger asChild>
|
||||
{trigger ?? <Button variant="destructive">Delete team</Button>}
|
||||
{trigger ?? (
|
||||
<Button variant="destructive">
|
||||
<Trans>Delete team</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent position="center">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Are you sure you wish to delete this team?</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Are you sure you wish to delete this team?</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
Please note that you will lose access to all documents associated with this team & all
|
||||
the members will be removed and notified
|
||||
<Trans>
|
||||
Please note that you will lose access to all documents associated with this team & all
|
||||
the members will be removed and notified
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@ -133,7 +146,9 @@ export const DeleteTeamDialog = ({ trigger, teamId, teamName }: DeleteTeamDialog
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Confirm by typing <span className="text-destructive">{deleteMessage}</span>
|
||||
<Trans>
|
||||
Confirm by typing <span className="text-destructive">{deleteMessage}</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input className="bg-background" {...field} />
|
||||
@ -145,11 +160,11 @@ export const DeleteTeamDialog = ({ trigger, teamId, teamName }: DeleteTeamDialog
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button type="submit" variant="destructive" loading={form.formState.isSubmitting}>
|
||||
Delete
|
||||
<Trans>Delete</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Alert } from '@documenso/ui/primitives/alert';
|
||||
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
|
||||
@ -36,14 +39,15 @@ export const DeleteTeamMemberDialog = ({
|
||||
}: DeleteTeamMemberDialogProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { mutateAsync: deleteTeamMembers, isLoading: isDeletingTeamMember } =
|
||||
trpc.team.deleteTeamMembers.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'You have successfully removed this user from the team.',
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`You have successfully removed this user from the team.`),
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
@ -51,11 +55,12 @@ export const DeleteTeamMemberDialog = ({
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: 'An unknown error occurred',
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to remove this user. Please try again later.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
description:
|
||||
'We encountered an unknown error while attempting to remove this user. Please try again later.',
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -63,16 +68,24 @@ export const DeleteTeamMemberDialog = ({
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(value) => !isDeletingTeamMember && setOpen(value)}>
|
||||
<DialogTrigger asChild>
|
||||
{trigger ?? <Button variant="secondary">Delete team member</Button>}
|
||||
{trigger ?? (
|
||||
<Button variant="secondary">
|
||||
<Trans>Delete team member</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent position="center">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Are you sure?</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Are you sure?</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
You are about to remove the following user from{' '}
|
||||
<span className="font-semibold">{teamName}</span>.
|
||||
<Trans>
|
||||
You are about to remove the following user from{' '}
|
||||
<span className="font-semibold">{teamName}</span>.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@ -88,7 +101,7 @@ export const DeleteTeamMemberDialog = ({
|
||||
<fieldset disabled={isDeletingTeamMember}>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -97,7 +110,7 @@ export const DeleteTeamMemberDialog = ({
|
||||
loading={isDeletingTeamMember}
|
||||
onClick={async () => deleteTeamMembers({ teamId, teamMemberIds: [teamMemberId] })}
|
||||
>
|
||||
Delete
|
||||
<Trans>Delete</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { Download, Mail, MailIcon, PlusCircle, Trash, Upload, UsersIcon } from 'lucide-react';
|
||||
import Papa, { type ParseResult } from 'papaparse';
|
||||
@ -104,6 +106,7 @@ export const InviteTeamMembersDialog = ({
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [invitationType, setInvitationType] = useState<TabTypes>('INDIVIDUAL');
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const form = useForm<TInviteTeamMembersFormSchema>({
|
||||
@ -144,18 +147,19 @@ export const InviteTeamMembersDialog = ({
|
||||
});
|
||||
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Team invitations have been sent.',
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`Team invitations have been sent.`),
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
setOpen(false);
|
||||
} catch {
|
||||
toast({
|
||||
title: 'An unknown error occurred',
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to invite team members. Please try again later.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
description:
|
||||
'We encountered an unknown error while attempting to invite team members. Please try again later.',
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -203,9 +207,11 @@ export const InviteTeamMembersDialog = ({
|
||||
console.error(err.message);
|
||||
|
||||
toast({
|
||||
title: _(msg`Something went wrong`),
|
||||
description: _(
|
||||
msg`Please check the CSV file and make sure it is according to our format`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
title: 'Something went wrong',
|
||||
description: 'Please check the CSV file and make sure it is according to our format',
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -239,15 +245,21 @@ export const InviteTeamMembersDialog = ({
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
||||
>
|
||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||
{trigger ?? <Button variant="secondary">Invite member</Button>}
|
||||
{trigger ?? (
|
||||
<Button variant="secondary">
|
||||
<Trans>Invite member</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent position="center">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Invite team members</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Invite team members</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
An email containing an invitation will be sent to each member.
|
||||
<Trans>An email containing an invitation will be sent to each member.</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@ -260,11 +272,11 @@ export const InviteTeamMembersDialog = ({
|
||||
<TabsList className="w-full">
|
||||
<TabsTrigger value="INDIVIDUAL" className="hover:text-foreground w-full">
|
||||
<MailIcon size={20} className="mr-2" />
|
||||
Invite Members
|
||||
<Trans>Invite Members</Trans>
|
||||
</TabsTrigger>
|
||||
|
||||
<TabsTrigger value="BULK" className="hover:text-foreground w-full">
|
||||
<UsersIcon size={20} className="mr-2" /> Bulk Import
|
||||
<UsersIcon size={20} className="mr-2" /> <Trans>Bulk Import</Trans>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
@ -283,7 +295,11 @@ export const InviteTeamMembersDialog = ({
|
||||
name={`invitations.${index}.email`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full">
|
||||
{index === 0 && <FormLabel required>Email address</FormLabel>}
|
||||
{index === 0 && (
|
||||
<FormLabel required>
|
||||
<Trans>Email address</Trans>
|
||||
</FormLabel>
|
||||
)}
|
||||
<FormControl>
|
||||
<Input className="bg-background" {...field} />
|
||||
</FormControl>
|
||||
@ -297,7 +313,11 @@ export const InviteTeamMembersDialog = ({
|
||||
name={`invitations.${index}.role`}
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full">
|
||||
{index === 0 && <FormLabel required>Role</FormLabel>}
|
||||
{index === 0 && (
|
||||
<FormLabel required>
|
||||
<Trans>Role</Trans>
|
||||
</FormLabel>
|
||||
)}
|
||||
<FormControl>
|
||||
<Select {...field} onValueChange={field.onChange}>
|
||||
<SelectTrigger className="text-muted-foreground max-w-[200px]">
|
||||
@ -307,7 +327,7 @@ export const InviteTeamMembersDialog = ({
|
||||
<SelectContent position="popper">
|
||||
{TEAM_MEMBER_ROLE_HIERARCHY[currentUserTeamRole].map((role) => (
|
||||
<SelectItem key={role} value={role}>
|
||||
{TEAM_MEMBER_ROLE_MAP[role] ?? role}
|
||||
{_(TEAM_MEMBER_ROLE_MAP[role]) ?? role}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@ -341,17 +361,17 @@ export const InviteTeamMembersDialog = ({
|
||||
onClick={() => onAddTeamMemberInvite()}
|
||||
>
|
||||
<PlusCircle className="mr-2 h-4 w-4" />
|
||||
Add more
|
||||
<Trans>Add more</Trans>
|
||||
</Button>
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button type="submit" loading={form.formState.isSubmitting}>
|
||||
{!form.formState.isSubmitting && <Mail className="mr-2 h-4 w-4" />}
|
||||
Invite
|
||||
<Trans>Invite</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
@ -368,7 +388,9 @@ export const InviteTeamMembersDialog = ({
|
||||
>
|
||||
<Upload className="h-5 w-5" />
|
||||
|
||||
<p className="mt-1 text-sm">Click here to upload</p>
|
||||
<p className="mt-1 text-sm">
|
||||
<Trans>Click here to upload</Trans>
|
||||
</p>
|
||||
|
||||
<input
|
||||
onChange={onFileInputChange}
|
||||
@ -383,7 +405,7 @@ export const InviteTeamMembersDialog = ({
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={downloadTemplate}>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Template
|
||||
<Trans>Template</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</div>
|
||||
|
||||
@ -2,6 +2,9 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams';
|
||||
import type { TeamMemberRole } from '@documenso/prisma/client';
|
||||
@ -37,13 +40,14 @@ export const LeaveTeamDialog = ({
|
||||
}: LeaveTeamDialogProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { mutateAsync: leaveTeam, isLoading: isLeavingTeam } = trpc.team.leaveTeam.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'You have successfully left this team.',
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`You have successfully left this team.`),
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
@ -51,11 +55,12 @@ export const LeaveTeamDialog = ({
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: 'An unknown error occurred',
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to leave this team. Please try again later.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
description:
|
||||
'We encountered an unknown error while attempting to leave this team. Please try again later.',
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -63,15 +68,21 @@ export const LeaveTeamDialog = ({
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(value) => !isLeavingTeam && setOpen(value)}>
|
||||
<DialogTrigger asChild>
|
||||
{trigger ?? <Button variant="destructive">Leave team</Button>}
|
||||
{trigger ?? (
|
||||
<Button variant="destructive">
|
||||
<Trans>Leave team</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent position="center">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Are you sure?</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Are you sure?</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
You are about to leave the following team.
|
||||
<Trans>You are about to leave the following team.</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@ -81,14 +92,14 @@ export const LeaveTeamDialog = ({
|
||||
avatarSrc={`${NEXT_PUBLIC_WEBAPP_URL()}/api/avatar/${teamAvatarImageId}`}
|
||||
avatarFallback={teamName.slice(0, 1).toUpperCase()}
|
||||
primaryText={teamName}
|
||||
secondaryText={TEAM_MEMBER_ROLE_MAP[role]}
|
||||
secondaryText={_(TEAM_MEMBER_ROLE_MAP[role])}
|
||||
/>
|
||||
</Alert>
|
||||
|
||||
<fieldset disabled={isLeavingTeam}>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -97,7 +108,7 @@ export const LeaveTeamDialog = ({
|
||||
loading={isLeavingTeam}
|
||||
onClick={async () => leaveTeam({ teamId })}
|
||||
>
|
||||
Leave
|
||||
<Trans>Leave</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
|
||||
@ -4,6 +4,9 @@ import { useState } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
|
||||
import type { Prisma } from '@documenso/prisma/client';
|
||||
@ -42,24 +45,26 @@ export type RemoveTeamEmailDialogProps = {
|
||||
export const RemoveTeamEmailDialog = ({ trigger, teamName, team }: RemoveTeamEmailDialogProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const { mutateAsync: deleteTeamEmail, isLoading: isDeletingTeamEmail } =
|
||||
trpc.team.deleteTeamEmail.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Team email has been removed',
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`Team email has been removed`),
|
||||
duration: 5000,
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: 'Something went wrong',
|
||||
title: _(msg`Something went wrong`),
|
||||
description: _(msg`Unable to remove team email at this time. Please try again.`),
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
description: 'Unable to remove team email at this time. Please try again.',
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -68,17 +73,17 @@ export const RemoveTeamEmailDialog = ({ trigger, teamName, team }: RemoveTeamEma
|
||||
trpc.team.deleteTeamEmailVerification.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Email verification has been removed',
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`Email verification has been removed`),
|
||||
duration: 5000,
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: 'Something went wrong',
|
||||
title: _(msg`Something went wrong`),
|
||||
description: _(msg`Unable to remove email verification at this time. Please try again.`),
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
description: 'Unable to remove email verification at this time. Please try again.',
|
||||
});
|
||||
},
|
||||
});
|
||||
@ -98,16 +103,24 @@ export const RemoveTeamEmailDialog = ({ trigger, teamName, team }: RemoveTeamEma
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={(value) => setOpen(value)}>
|
||||
<DialogTrigger asChild>
|
||||
{trigger ?? <Button variant="destructive">Remove team email</Button>}
|
||||
{trigger ?? (
|
||||
<Button variant="destructive">
|
||||
<Trans>Remove team email</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent position="center">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Are you sure?</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Are you sure?</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
You are about to delete the following team email from{' '}
|
||||
<span className="font-semibold">{teamName}</span>.
|
||||
<Trans>
|
||||
You are about to delete the following team email from{' '}
|
||||
<span className="font-semibold">{teamName}</span>.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@ -134,7 +147,7 @@ export const RemoveTeamEmailDialog = ({ trigger, teamName, team }: RemoveTeamEma
|
||||
<fieldset disabled={isDeletingTeamEmail || isDeletingTeamEmailVerification}>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -143,7 +156,7 @@ export const RemoveTeamEmailDialog = ({ trigger, teamName, team }: RemoveTeamEma
|
||||
loading={isDeletingTeamEmail || isDeletingTeamEmailVerification}
|
||||
onClick={async () => onRemove()}
|
||||
>
|
||||
Remove
|
||||
<Trans>Remove</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
|
||||
@ -5,6 +5,8 @@ import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Loader } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
@ -56,6 +58,7 @@ export const TransferTeamDialog = ({
|
||||
const router = useRouter();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { mutateAsync: requestTeamOwnershipTransfer } =
|
||||
@ -102,19 +105,20 @@ export const TransferTeamDialog = ({
|
||||
router.refresh();
|
||||
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'An email requesting the transfer of this team has been sent.',
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`An email requesting the transfer of this team has been sent.`),
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
setOpen(false);
|
||||
} catch (err) {
|
||||
toast({
|
||||
title: 'An unknown error occurred',
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to request a transfer of this team. Please try again later.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
description:
|
||||
'We encountered an unknown error while attempting to request a transfer of this team. Please try again later.',
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -140,7 +144,7 @@ export const TransferTeamDialog = ({
|
||||
<DialogTrigger asChild>
|
||||
{trigger ?? (
|
||||
<Button variant="outline" className="bg-background">
|
||||
Transfer team
|
||||
<Trans>Transfer team</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
@ -148,10 +152,12 @@ export const TransferTeamDialog = ({
|
||||
{teamMembers && teamMembers.length > 0 ? (
|
||||
<DialogContent position="center">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Transfer team</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Transfer team</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
Transfer ownership of this team to a selected team member.
|
||||
<Trans>Transfer ownership of this team to a selected team member.</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@ -166,7 +172,9 @@ export const TransferTeamDialog = ({
|
||||
name="newOwnerUserId"
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full">
|
||||
<FormLabel required>New team owner</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>New team owner</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select {...field} onValueChange={field.onChange}>
|
||||
<SelectTrigger className="text-muted-foreground">
|
||||
@ -196,8 +204,10 @@ export const TransferTeamDialog = ({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Confirm by typing{' '}
|
||||
<span className="text-destructive">{confirmTransferMessage}</span>
|
||||
<Trans>
|
||||
Confirm by typing{' '}
|
||||
<span className="text-destructive">{confirmTransferMessage}</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input className="bg-background" {...field} />
|
||||
@ -247,13 +257,17 @@ export const TransferTeamDialog = ({
|
||||
// </li>
|
||||
|
||||
<li>
|
||||
Any payment methods attached to this team will remain attached to this
|
||||
team. Please contact us if you need to update this information.
|
||||
<Trans>
|
||||
Any payment methods attached to this team will remain attached to this
|
||||
team. Please contact us if you need to update this information.
|
||||
</Trans>
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
The selected team member will receive an email which they must accept before
|
||||
the team is transferred
|
||||
<Trans>
|
||||
The selected team member will receive an email which they must accept
|
||||
before the team is transferred
|
||||
</Trans>
|
||||
</li>
|
||||
</ul>
|
||||
</AlertDescription>
|
||||
@ -261,11 +275,11 @@ export const TransferTeamDialog = ({
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button type="submit" variant="destructive" loading={form.formState.isSubmitting}>
|
||||
Request transfer
|
||||
<Trans>Request transfer</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
@ -281,9 +295,11 @@ export const TransferTeamDialog = ({
|
||||
<Loader className="text-muted-foreground h-6 w-6 animate-spin" />
|
||||
) : (
|
||||
<p className="text-center text-sm">
|
||||
{loadingTeamMembersError
|
||||
? 'An error occurred while loading team members. Please try again later.'
|
||||
: 'You must have at least one other team member to transfer ownership.'}
|
||||
{loadingTeamMembersError ? (
|
||||
<Trans>An error occurred while loading team members. Please try again later.</Trans>
|
||||
) : (
|
||||
<Trans>You must have at least one other team member to transfer ownership.</Trans>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</DialogContent>
|
||||
|
||||
@ -5,6 +5,8 @@ import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
@ -52,6 +54,7 @@ export const UpdateTeamEmailDialog = ({
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const form = useForm<TUpdateTeamEmailFormSchema>({
|
||||
@ -73,8 +76,8 @@ export const UpdateTeamEmailDialog = ({
|
||||
});
|
||||
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Team email was updated.',
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`Team email was updated.`),
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
@ -83,10 +86,11 @@ export const UpdateTeamEmailDialog = ({
|
||||
setOpen(false);
|
||||
} catch (err) {
|
||||
toast({
|
||||
title: 'An unknown error occurred',
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting update the team email. Please try again later.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
description:
|
||||
'We encountered an unknown error while attempting update the team email. Please try again later.',
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -106,17 +110,19 @@ export const UpdateTeamEmailDialog = ({
|
||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||
{trigger ?? (
|
||||
<Button variant="outline" className="bg-background">
|
||||
Update team email
|
||||
<Trans>Update team email</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent position="center">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Update team email</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Update team email</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
To change the email you must remove and add a new email address.
|
||||
<Trans>To change the email you must remove and add a new email address.</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@ -131,7 +137,9 @@ export const UpdateTeamEmailDialog = ({
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>Name</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Name</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input className="bg-background" placeholder="eg. Legal" {...field} />
|
||||
</FormControl>
|
||||
@ -141,7 +149,9 @@ export const UpdateTeamEmailDialog = ({
|
||||
/>
|
||||
|
||||
<FormItem>
|
||||
<FormLabel required>Email</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Email</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input className="bg-background" value={teamEmail.email} disabled={true} />
|
||||
</FormControl>
|
||||
@ -149,11 +159,11 @@ export const UpdateTeamEmailDialog = ({
|
||||
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button type="submit" loading={form.formState.isSubmitting}>
|
||||
Update
|
||||
<Trans>Update</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
@ -64,6 +66,7 @@ export const UpdateTeamMemberDialog = ({
|
||||
}: UpdateTeamMemberDialogProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const form = useForm<ZUpdateTeamMemberSchema>({
|
||||
@ -86,18 +89,19 @@ export const UpdateTeamMemberDialog = ({
|
||||
});
|
||||
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: `You have updated ${teamMemberName}.`,
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`You have updated ${teamMemberName}.`),
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
setOpen(false);
|
||||
} catch {
|
||||
toast({
|
||||
title: 'An unknown error occurred',
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to update this team member. Please try again later.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
description:
|
||||
'We encountered an unknown error while attempting to update this team member. Please try again later.',
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -113,10 +117,11 @@ export const UpdateTeamMemberDialog = ({
|
||||
setOpen(false);
|
||||
|
||||
toast({
|
||||
title: 'You cannot modify a team member who has a higher role than you.',
|
||||
title: _(msg`You cannot modify a team member who has a higher role than you.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [open, currentUserTeamRole, teamMemberRole, form, toast]);
|
||||
|
||||
return (
|
||||
@ -126,15 +131,23 @@ export const UpdateTeamMemberDialog = ({
|
||||
onOpenChange={(value) => !form.formState.isSubmitting && setOpen(value)}
|
||||
>
|
||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||
{trigger ?? <Button variant="secondary">Update team member</Button>}
|
||||
{trigger ?? (
|
||||
<Button variant="secondary">
|
||||
<Trans>Update team member</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent position="center">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Update team member</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Update team member</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription className="mt-4">
|
||||
You are currently updating <span className="font-bold">{teamMemberName}.</span>
|
||||
<Trans>
|
||||
You are currently updating <span className="font-bold">{teamMemberName}.</span>
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@ -146,7 +159,9 @@ export const UpdateTeamMemberDialog = ({
|
||||
name="role"
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full">
|
||||
<FormLabel required>Role</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Role</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Select {...field} onValueChange={field.onChange}>
|
||||
<SelectTrigger className="text-muted-foreground">
|
||||
@ -156,7 +171,7 @@ export const UpdateTeamMemberDialog = ({
|
||||
<SelectContent className="w-full" position="popper">
|
||||
{TEAM_MEMBER_ROLE_HIERARCHY[currentUserTeamRole].map((role) => (
|
||||
<SelectItem key={role} value={role}>
|
||||
{TEAM_MEMBER_ROLE_MAP[role] ?? role}
|
||||
{_(TEAM_MEMBER_ROLE_MAP[role]) ?? role}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@ -169,11 +184,11 @@ export const UpdateTeamMemberDialog = ({
|
||||
|
||||
<DialogFooter className="mt-4">
|
||||
<Button type="button" variant="secondary" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button type="submit" loading={form.formState.isSubmitting}>
|
||||
Update
|
||||
<Trans>Update</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { z } from 'zod';
|
||||
@ -39,6 +41,7 @@ type TUpdateTeamFormSchema = z.infer<typeof ZUpdateTeamFormSchema>;
|
||||
export const UpdateTeamForm = ({ teamId, teamName, teamUrl }: UpdateTeamDialogProps) => {
|
||||
const router = useRouter();
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const form = useForm({
|
||||
@ -62,8 +65,8 @@ export const UpdateTeamForm = ({ teamId, teamName, teamUrl }: UpdateTeamDialogPr
|
||||
});
|
||||
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Your team has been successfully updated.',
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`Your team has been successfully updated.`),
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
@ -81,17 +84,18 @@ export const UpdateTeamForm = ({ teamId, teamName, teamUrl }: UpdateTeamDialogPr
|
||||
if (error.code === AppErrorCode.ALREADY_EXISTS) {
|
||||
form.setError('url', {
|
||||
type: 'manual',
|
||||
message: 'This URL is already in use.',
|
||||
message: _(msg`This URL is already in use.`),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
toast({
|
||||
title: 'An unknown error occurred',
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to update your team. Please try again later.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
description:
|
||||
'We encountered an unknown error while attempting to update your team. Please try again later.',
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -105,7 +109,9 @@ export const UpdateTeamForm = ({ teamId, teamName, teamUrl }: UpdateTeamDialogPr
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel required>Team Name</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Team Name</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input className="bg-background" {...field} />
|
||||
</FormControl>
|
||||
@ -119,15 +125,19 @@ export const UpdateTeamForm = ({ teamId, teamName, teamUrl }: UpdateTeamDialogPr
|
||||
name="url"
|
||||
render={({ field }) => (
|
||||
<FormItem className="mt-4">
|
||||
<FormLabel required>Team URL</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Team URL</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input className="bg-background" {...field} />
|
||||
</FormControl>
|
||||
{!form.formState.errors.url && (
|
||||
<span className="text-foreground/50 text-xs font-normal">
|
||||
{field.value
|
||||
? `${WEBAPP_BASE_URL}/t/${field.value}`
|
||||
: 'A unique URL to identify your team'}
|
||||
{field.value ? (
|
||||
`${WEBAPP_BASE_URL}/t/${field.value}`
|
||||
) : (
|
||||
<Trans>A unique URL to identify your team</Trans>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@ -151,7 +161,7 @@ export const UpdateTeamForm = ({ teamId, teamName, teamUrl }: UpdateTeamDialogPr
|
||||
}}
|
||||
>
|
||||
<Button type="button" variant="secondary" onClick={() => form.reset()}>
|
||||
Reset
|
||||
<Trans>Reset</Trans>
|
||||
</Button>
|
||||
</motion.div>
|
||||
)}
|
||||
@ -163,7 +173,7 @@ export const UpdateTeamForm = ({ teamId, teamName, teamUrl }: UpdateTeamDialogPr
|
||||
disabled={!form.formState.isDirty}
|
||||
loading={form.formState.isSubmitting}
|
||||
>
|
||||
Update team
|
||||
<Trans>Update team</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
@ -5,6 +5,7 @@ import type { HTMLAttributes } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useParams, usePathname } from 'next/navigation';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { Braces, CreditCard, Globe2Icon, Settings, Users, Webhook } from 'lucide-react';
|
||||
|
||||
import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag';
|
||||
@ -39,7 +40,7 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
|
||||
className={cn('w-full justify-start', pathname === settingsPath && 'bg-secondary')}
|
||||
>
|
||||
<Settings className="mr-2 h-5 w-5" />
|
||||
General
|
||||
<Trans>General</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -53,7 +54,7 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
|
||||
)}
|
||||
>
|
||||
<Globe2Icon className="mr-2 h-5 w-5" />
|
||||
Public Profile
|
||||
<Trans>Public Profile</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
@ -67,7 +68,7 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
|
||||
)}
|
||||
>
|
||||
<Users className="mr-2 h-5 w-5" />
|
||||
Members
|
||||
<Trans>Members</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -77,7 +78,7 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
|
||||
className={cn('w-full justify-start', pathname?.startsWith(tokensPath) && 'bg-secondary')}
|
||||
>
|
||||
<Braces className="mr-2 h-5 w-5" />
|
||||
API Tokens
|
||||
<Trans>API Tokens</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -90,7 +91,7 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
|
||||
)}
|
||||
>
|
||||
<Webhook className="mr-2 h-5 w-5" />
|
||||
Webhooks
|
||||
<Trans>Webhooks</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -104,7 +105,7 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
|
||||
)}
|
||||
>
|
||||
<CreditCard className="mr-2 h-5 w-5" />
|
||||
Billing
|
||||
<Trans>Billing</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@ -5,6 +5,7 @@ import type { HTMLAttributes } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useParams, usePathname } from 'next/navigation';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { Braces, CreditCard, Globe2Icon, Key, User, Webhook } from 'lucide-react';
|
||||
|
||||
import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag';
|
||||
@ -47,7 +48,7 @@ export const MobileNav = ({ className, ...props }: MobileNavProps) => {
|
||||
)}
|
||||
>
|
||||
<User className="mr-2 h-5 w-5" />
|
||||
General
|
||||
<Trans>General</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -61,7 +62,7 @@ export const MobileNav = ({ className, ...props }: MobileNavProps) => {
|
||||
)}
|
||||
>
|
||||
<Globe2Icon className="mr-2 h-5 w-5" />
|
||||
Public Profile
|
||||
<Trans>Public Profile</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
@ -75,7 +76,7 @@ export const MobileNav = ({ className, ...props }: MobileNavProps) => {
|
||||
)}
|
||||
>
|
||||
<Key className="mr-2 h-5 w-5" />
|
||||
Members
|
||||
<Trans>Members</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -85,7 +86,7 @@ export const MobileNav = ({ className, ...props }: MobileNavProps) => {
|
||||
className={cn('w-full justify-start', pathname?.startsWith(tokensPath) && 'bg-secondary')}
|
||||
>
|
||||
<Braces className="mr-2 h-5 w-5" />
|
||||
API Tokens
|
||||
<Trans>API Tokens</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -98,7 +99,7 @@ export const MobileNav = ({ className, ...props }: MobileNavProps) => {
|
||||
)}
|
||||
>
|
||||
<Webhook className="mr-2 h-5 w-5" />
|
||||
Webhooks
|
||||
<Trans>Webhooks</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -112,7 +113,7 @@ export const MobileNav = ({ className, ...props }: MobileNavProps) => {
|
||||
)}
|
||||
>
|
||||
<CreditCard className="mr-2 h-5 w-5" />
|
||||
Billing
|
||||
<Trans>Billing</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@ -3,6 +3,9 @@
|
||||
import Link from 'next/link';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL, WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||
import { TEAM_MEMBER_ROLE_MAP } from '@documenso/lib/constants/teams';
|
||||
@ -21,6 +24,8 @@ import { LocaleDate } from '~/components/formatter/locale-date';
|
||||
import { LeaveTeamDialog } from '../dialogs/leave-team-dialog';
|
||||
|
||||
export const CurrentUserTeamsDataTable = () => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
const updateSearchParams = useUpdateSearchParams();
|
||||
|
||||
@ -57,7 +62,7 @@ export const CurrentUserTeamsDataTable = () => {
|
||||
<DataTable
|
||||
columns={[
|
||||
{
|
||||
header: 'Team',
|
||||
header: _(msg`Team`),
|
||||
accessorKey: 'name',
|
||||
cell: ({ row }) => (
|
||||
<Link href={`/t/${row.original.url}`} scroll={false}>
|
||||
@ -74,15 +79,15 @@ export const CurrentUserTeamsDataTable = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
header: 'Role',
|
||||
header: _(msg`Role`),
|
||||
accessorKey: 'role',
|
||||
cell: ({ row }) =>
|
||||
row.original.ownerUserId === row.original.currentTeamMember.userId
|
||||
? 'Owner'
|
||||
: TEAM_MEMBER_ROLE_MAP[row.original.currentTeamMember.role],
|
||||
: _(TEAM_MEMBER_ROLE_MAP[row.original.currentTeamMember.role]),
|
||||
},
|
||||
{
|
||||
header: 'Member Since',
|
||||
header: _(msg`Member Since`),
|
||||
accessorKey: 'createdAt',
|
||||
cell: ({ row }) => <LocaleDate date={row.original.createdAt} />,
|
||||
},
|
||||
@ -92,7 +97,9 @@ export const CurrentUserTeamsDataTable = () => {
|
||||
<div className="flex justify-end space-x-2">
|
||||
{canExecuteTeamAction('MANAGE_TEAM', row.original.currentTeamMember.role) && (
|
||||
<Button variant="outline" asChild>
|
||||
<Link href={`/t/${row.original.url}/settings`}>Manage</Link>
|
||||
<Link href={`/t/${row.original.url}/settings`}>
|
||||
<Trans>Manage</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@ -107,7 +114,7 @@ export const CurrentUserTeamsDataTable = () => {
|
||||
disabled={row.original.ownerUserId === row.original.currentTeamMember.userId}
|
||||
onSelect={(e) => e.preventDefault()}
|
||||
>
|
||||
Leave
|
||||
<Trans>Leave</Trans>
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
@ -14,21 +17,23 @@ export const PendingUserTeamsDataTableActions = ({
|
||||
pendingTeamId,
|
||||
onPayClick,
|
||||
}: PendingUserTeamsDataTableActionsProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { mutateAsync: deleteTeamPending, isLoading: deletingTeam } =
|
||||
trpc.team.deleteTeamPending.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Pending team deleted.',
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`Pending team deleted.`),
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: 'Something went wrong',
|
||||
description:
|
||||
'We encountered an unknown error while attempting to delete the pending team. Please try again later.',
|
||||
title: _(msg`Something went wrong`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to delete the pending team. Please try again later.`,
|
||||
),
|
||||
duration: 10000,
|
||||
variant: 'destructive',
|
||||
});
|
||||
@ -38,7 +43,7 @@ export const PendingUserTeamsDataTableActions = ({
|
||||
return (
|
||||
<fieldset disabled={deletingTeam} className={cn('flex justify-end space-x-2', className)}>
|
||||
<Button variant="outline" onClick={() => onPayClick(pendingTeamId)}>
|
||||
Pay
|
||||
<Trans>Pay</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -46,7 +51,7 @@ export const PendingUserTeamsDataTableActions = ({
|
||||
loading={deletingTeam}
|
||||
onClick={async () => deleteTeamPending({ pendingTeamId: pendingTeamId })}
|
||||
>
|
||||
Remove
|
||||
<Trans>Remove</Trans>
|
||||
</Button>
|
||||
</fieldset>
|
||||
);
|
||||
|
||||
@ -4,6 +4,9 @@ import { useEffect, useState } from 'react';
|
||||
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||
import { WEBAPP_BASE_URL } from '@documenso/lib/constants/app';
|
||||
import { ZBaseTableSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
@ -20,6 +23,8 @@ import { CreateTeamCheckoutDialog } from '../dialogs/create-team-checkout-dialog
|
||||
import { PendingUserTeamsDataTableActions } from './pending-user-teams-data-table-actions';
|
||||
|
||||
export const PendingUserTeamsDataTable = () => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
const updateSearchParams = useUpdateSearchParams();
|
||||
|
||||
@ -68,7 +73,7 @@ export const PendingUserTeamsDataTable = () => {
|
||||
<DataTable
|
||||
columns={[
|
||||
{
|
||||
header: 'Team',
|
||||
header: _(msg`Team`),
|
||||
accessorKey: 'name',
|
||||
cell: ({ row }) => (
|
||||
<AvatarWithText
|
||||
@ -82,7 +87,7 @@ export const PendingUserTeamsDataTable = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
header: 'Created on',
|
||||
header: _(msg`Created on`),
|
||||
accessorKey: 'createdAt',
|
||||
cell: ({ row }) => <LocaleDate date={row.original.createdAt} />,
|
||||
},
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
import Link from 'next/link';
|
||||
|
||||
import { Plural, Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { File } from 'lucide-react';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
@ -17,6 +19,8 @@ export type TeamBillingInvoicesDataTableProps = {
|
||||
};
|
||||
|
||||
export const TeamBillingInvoicesDataTable = ({ teamId }: TeamBillingInvoicesDataTableProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const { data, isLoading, isInitialLoading, isLoadingError } = trpc.team.findTeamInvoices.useQuery(
|
||||
{
|
||||
teamId,
|
||||
@ -46,7 +50,7 @@ export const TeamBillingInvoicesDataTable = ({ teamId }: TeamBillingInvoicesData
|
||||
<DataTable
|
||||
columns={[
|
||||
{
|
||||
header: 'Invoice',
|
||||
header: _(msg`Invoice`),
|
||||
accessorKey: 'created',
|
||||
cell: ({ row }) => (
|
||||
<div className="flex max-w-xs items-center gap-2">
|
||||
@ -57,27 +61,27 @@ export const TeamBillingInvoicesDataTable = ({ teamId }: TeamBillingInvoicesData
|
||||
{DateTime.fromSeconds(row.original.created).toFormat('MMMM yyyy')}
|
||||
</span>
|
||||
<span className="text-muted-foreground">
|
||||
{row.original.quantity} {row.original.quantity > 1 ? 'Seats' : 'Seat'}
|
||||
<Plural value={row.original.quantity} one="# Seat" other="# Seats" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
header: 'Status',
|
||||
header: _(msg`Status`),
|
||||
accessorKey: 'status',
|
||||
cell: ({ row }) => {
|
||||
const { status, paid } = row.original;
|
||||
|
||||
if (!status) {
|
||||
return paid ? 'Paid' : 'Unpaid';
|
||||
return paid ? <Trans>Paid</Trans> : <Trans>Unpaid</Trans>;
|
||||
}
|
||||
|
||||
return status.charAt(0).toUpperCase() + status.slice(1);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: 'Amount',
|
||||
header: _(msg`Amount`),
|
||||
accessorKey: 'total',
|
||||
cell: ({ row }) => formatCurrency(row.original.currency, row.original.total / 100),
|
||||
},
|
||||
@ -91,7 +95,7 @@ export const TeamBillingInvoicesDataTable = ({ teamId }: TeamBillingInvoicesData
|
||||
disabled={typeof row.original.hostedInvoicePdf !== 'string'}
|
||||
>
|
||||
<Link href={row.original.hostedInvoicePdf ?? ''} target="_blank">
|
||||
View
|
||||
<Trans>View</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
@ -101,7 +105,7 @@ export const TeamBillingInvoicesDataTable = ({ teamId }: TeamBillingInvoicesData
|
||||
disabled={typeof row.original.hostedInvoicePdf !== 'string'}
|
||||
>
|
||||
<Link href={row.original.invoicePdf ?? ''} target="_blank">
|
||||
Download
|
||||
<Trans>Download</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { History, MoreHorizontal, Trash2 } from 'lucide-react';
|
||||
|
||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||
@ -32,6 +34,7 @@ export const TeamMemberInvitesDataTable = ({ teamId }: TeamMemberInvitesDataTabl
|
||||
const searchParams = useSearchParams();
|
||||
const updateSearchParams = useUpdateSearchParams();
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const parsedSearchParams = ZBaseTableSearchParamsSchema.parse(
|
||||
@ -55,14 +58,14 @@ export const TeamMemberInvitesDataTable = ({ teamId }: TeamMemberInvitesDataTabl
|
||||
trpc.team.resendTeamMemberInvitation.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Invitation has been resent',
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`Invitation has been resent`),
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: 'Something went wrong',
|
||||
description: 'Unable to resend invitation. Please try again.',
|
||||
title: _(msg`Something went wrong`),
|
||||
description: _(msg`Unable to resend invitation. Please try again.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
},
|
||||
@ -72,14 +75,14 @@ export const TeamMemberInvitesDataTable = ({ teamId }: TeamMemberInvitesDataTabl
|
||||
trpc.team.deleteTeamMemberInvitations.useMutation({
|
||||
onSuccess: () => {
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Invitation has been deleted',
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`Invitation has been deleted`),
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: 'Something went wrong',
|
||||
description: 'Unable to delete invitation. Please try again.',
|
||||
title: _(msg`Something went wrong`),
|
||||
description: _(msg`Unable to delete invitation. Please try again.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
},
|
||||
@ -103,7 +106,7 @@ export const TeamMemberInvitesDataTable = ({ teamId }: TeamMemberInvitesDataTabl
|
||||
<DataTable
|
||||
columns={[
|
||||
{
|
||||
header: 'Team Member',
|
||||
header: _(msg`Team Member`),
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<AvatarWithText
|
||||
@ -117,17 +120,17 @@ export const TeamMemberInvitesDataTable = ({ teamId }: TeamMemberInvitesDataTabl
|
||||
},
|
||||
},
|
||||
{
|
||||
header: 'Role',
|
||||
header: _(msg`Role`),
|
||||
accessorKey: 'role',
|
||||
cell: ({ row }) => TEAM_MEMBER_ROLE_MAP[row.original.role] ?? row.original.role,
|
||||
cell: ({ row }) => _(TEAM_MEMBER_ROLE_MAP[row.original.role]) ?? row.original.role,
|
||||
},
|
||||
{
|
||||
header: 'Invited At',
|
||||
header: _(msg`Invited At`),
|
||||
accessorKey: 'createdAt',
|
||||
cell: ({ row }) => <LocaleDate date={row.original.createdAt} />,
|
||||
},
|
||||
{
|
||||
header: 'Actions',
|
||||
header: _(msg`Actions`),
|
||||
cell: ({ row }) => (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
@ -135,7 +138,9 @@ export const TeamMemberInvitesDataTable = ({ teamId }: TeamMemberInvitesDataTabl
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent className="w-52" align="start" forceMount>
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>
|
||||
<Trans>Actions</Trans>
|
||||
</DropdownMenuLabel>
|
||||
|
||||
<DropdownMenuItem
|
||||
onClick={async () =>
|
||||
@ -146,7 +151,7 @@ export const TeamMemberInvitesDataTable = ({ teamId }: TeamMemberInvitesDataTabl
|
||||
}
|
||||
>
|
||||
<History className="mr-2 h-4 w-4" />
|
||||
Resend
|
||||
<Trans>Resend</Trans>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
@ -158,7 +163,7 @@ export const TeamMemberInvitesDataTable = ({ teamId }: TeamMemberInvitesDataTabl
|
||||
}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Remove
|
||||
<Trans>Remove</Trans>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Edit, MoreHorizontal, Trash2 } from 'lucide-react';
|
||||
|
||||
import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params';
|
||||
@ -42,6 +44,8 @@ export const TeamMembersDataTable = ({
|
||||
teamId,
|
||||
teamName,
|
||||
}: TeamMembersDataTableProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
const updateSearchParams = useUpdateSearchParams();
|
||||
|
||||
@ -79,7 +83,7 @@ export const TeamMembersDataTable = ({
|
||||
<DataTable
|
||||
columns={[
|
||||
{
|
||||
header: 'Team Member',
|
||||
header: _(msg`Team Member`),
|
||||
cell: ({ row }) => {
|
||||
const avatarFallbackText = row.original.user.name
|
||||
? extractInitials(row.original.user.name)
|
||||
@ -98,20 +102,20 @@ export const TeamMembersDataTable = ({
|
||||
},
|
||||
},
|
||||
{
|
||||
header: 'Role',
|
||||
header: _(msg`Role`),
|
||||
accessorKey: 'role',
|
||||
cell: ({ row }) =>
|
||||
teamOwnerUserId === row.original.userId
|
||||
? 'Owner'
|
||||
: TEAM_MEMBER_ROLE_MAP[row.original.role],
|
||||
: _(TEAM_MEMBER_ROLE_MAP[row.original.role]),
|
||||
},
|
||||
{
|
||||
header: 'Member Since',
|
||||
header: _(msg`Member Since`),
|
||||
accessorKey: 'createdAt',
|
||||
cell: ({ row }) => <LocaleDate date={row.original.createdAt} />,
|
||||
},
|
||||
{
|
||||
header: 'Actions',
|
||||
header: _(msg`Actions`),
|
||||
cell: ({ row }) => (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
@ -119,7 +123,9 @@ export const TeamMembersDataTable = ({
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent className="w-52" align="start" forceMount>
|
||||
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>
|
||||
<Trans>Actions</Trans>
|
||||
</DropdownMenuLabel>
|
||||
|
||||
<UpdateTeamMemberDialog
|
||||
currentUserTeamRole={currentUserTeamRole}
|
||||
@ -137,7 +143,7 @@ export const TeamMembersDataTable = ({
|
||||
title="Update team member role"
|
||||
>
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
Update role
|
||||
<Trans>Update role</Trans>
|
||||
</DropdownMenuItem>
|
||||
}
|
||||
/>
|
||||
@ -155,10 +161,10 @@ export const TeamMembersDataTable = ({
|
||||
teamOwnerUserId === row.original.userId ||
|
||||
!isTeamRoleWithinUserHierarchy(currentUserTeamRole, row.original.role)
|
||||
}
|
||||
title="Remove team member"
|
||||
title={_(msg`Remove team member`)}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
Remove
|
||||
<Trans>Remove</Trans>
|
||||
</DropdownMenuItem>
|
||||
}
|
||||
/>
|
||||
|
||||
@ -5,6 +5,9 @@ import { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
|
||||
import type { TeamMemberRole } from '@documenso/prisma/client';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
@ -26,6 +29,8 @@ export const TeamsMemberPageDataTable = ({
|
||||
teamName,
|
||||
teamOwnerUserId,
|
||||
}: TeamsMemberPageDataTableProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
@ -61,17 +66,21 @@ export const TeamsMemberPageDataTable = ({
|
||||
<Input
|
||||
defaultValue={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder="Search"
|
||||
placeholder={_(msg`Search`)}
|
||||
/>
|
||||
|
||||
<Tabs value={currentTab} className="flex-shrink-0 overflow-x-auto">
|
||||
<TabsList>
|
||||
<TabsTrigger className="min-w-[60px]" value="members" asChild>
|
||||
<Link href={pathname ?? '/'}>Active</Link>
|
||||
<Link href={pathname ?? '/'}>
|
||||
<Trans>Active</Trans>
|
||||
</Link>
|
||||
</TabsTrigger>
|
||||
|
||||
<TabsTrigger className="min-w-[60px]" value="invites" asChild>
|
||||
<Link href={`${pathname}?tab=invites`}>Pending</Link>
|
||||
<Link href={`${pathname}?tab=invites`}>
|
||||
<Trans>Pending</Trans>
|
||||
</Link>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
@ -5,6 +5,9 @@ import { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { useDebouncedValue } from '@documenso/lib/client-only/hooks/use-debounced-value';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
@ -14,6 +17,8 @@ import { CurrentUserTeamsDataTable } from './current-user-teams-data-table';
|
||||
import { PendingUserTeamsDataTable } from './pending-user-teams-data-table';
|
||||
|
||||
export const UserSettingsTeamsPageDataTable = () => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
@ -56,18 +61,20 @@ export const UserSettingsTeamsPageDataTable = () => {
|
||||
<Input
|
||||
defaultValue={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder="Search"
|
||||
placeholder={_(msg`Search`)}
|
||||
/>
|
||||
|
||||
<Tabs value={currentTab} className="flex-shrink-0 overflow-x-auto">
|
||||
<TabsList>
|
||||
<TabsTrigger className="min-w-[60px]" value="active" asChild>
|
||||
<Link href={pathname ?? '/'}>Active</Link>
|
||||
<Link href={pathname ?? '/'}>
|
||||
<Trans>Active</Trans>
|
||||
</Link>
|
||||
</TabsTrigger>
|
||||
|
||||
<TabsTrigger className="min-w-[60px]" value="pending" asChild>
|
||||
<Link href={`${pathname}?tab=pending`}>
|
||||
Pending
|
||||
<Trans>Pending</Trans>
|
||||
{data && data.count > 0 && (
|
||||
<span className="ml-1 hidden opacity-50 md:inline-block">{data.count}</span>
|
||||
)}
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
@ -10,6 +13,7 @@ export type TeamBillingPortalButtonProps = {
|
||||
};
|
||||
|
||||
export const TeamBillingPortalButton = ({ buttonProps, teamId }: TeamBillingPortalButtonProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const { mutateAsync: createBillingPortal, isLoading } =
|
||||
@ -22,9 +26,10 @@ export const TeamBillingPortalButton = ({ buttonProps, teamId }: TeamBillingPort
|
||||
window.open(sessionUrl, '_blank');
|
||||
} catch (err) {
|
||||
toast({
|
||||
title: 'Something went wrong',
|
||||
description:
|
||||
'We are unable to proceed to the billing portal at this time. Please try again, or contact support.',
|
||||
title: _(msg`Something went wrong`),
|
||||
description: _(
|
||||
msg`We are unable to proceed to the billing portal at this time. Please try again, or contact support.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
duration: 10000,
|
||||
});
|
||||
@ -33,7 +38,7 @@ export const TeamBillingPortalButton = ({ buttonProps, teamId }: TeamBillingPort
|
||||
|
||||
return (
|
||||
<Button {...buttonProps} onClick={async () => handleCreatePortal()} loading={isLoading}>
|
||||
Manage subscription
|
||||
<Trans>Manage subscription</Trans>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user