feat: web i18n (#1286)

This commit is contained in:
David Nguyen
2024-08-27 20:34:39 +09:00
committed by GitHub
parent 0829311214
commit 75c8772a02
294 changed files with 14846 additions and 2229 deletions

View File

@ -5,6 +5,8 @@ import { 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 { flushSync } from 'react-dom';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
@ -40,6 +42,7 @@ export type TDisable2FAForm = z.infer<typeof ZDisable2FAForm>;
export const DisableAuthenticatorAppDialog = () => {
const router = useRouter();
const { _ } = useLingui();
const { toast } = useToast();
const [isOpen, setIsOpen] = useState(false);
@ -60,9 +63,10 @@ export const DisableAuthenticatorAppDialog = () => {
await disable2FA({ token });
toast({
title: 'Two-factor authentication disabled',
description:
'Two-factor authentication has been disabled for your account. You will no longer be required to enter a code from your authenticator app when signing in.',
title: _(msg`Two-factor authentication disabled`),
description: _(
msg`Two-factor authentication has been disabled for your account. You will no longer be required to enter a code from your authenticator app when signing in.`,
),
});
flushSync(() => {
@ -72,9 +76,10 @@ export const DisableAuthenticatorAppDialog = () => {
router.refresh();
} catch (_err) {
toast({
title: 'Unable to disable two-factor authentication',
description:
'We were unable to disable two-factor authentication for your account. Please ensure that you have entered your password and backup code correctly and try again.',
title: _(msg`Unable to disable two-factor authentication`),
description: _(
msg`We were unable to disable two-factor authentication for your account. Please ensure that you have entered your password and backup code correctly and try again.`,
),
variant: 'destructive',
});
}
@ -84,17 +89,21 @@ export const DisableAuthenticatorAppDialog = () => {
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild={true}>
<Button className="flex-shrink-0" variant="destructive">
Disable 2FA
<Trans>Disable 2FA</Trans>
</Button>
</DialogTrigger>
<DialogContent position="center">
<DialogHeader>
<DialogTitle>Disable 2FA</DialogTitle>
<DialogTitle>
<Trans>Disable 2FA</Trans>
</DialogTitle>
<DialogDescription>
Please provide a token from the authenticator, or a backup code. If you do not have a
backup code available, please contact support.
<Trans>
Please provide a token from the authenticator, or a backup code. If you do not have a
backup code available, please contact support.
</Trans>
</DialogDescription>
</DialogHeader>
@ -125,12 +134,12 @@ export const DisableAuthenticatorAppDialog = () => {
<DialogFooter>
<DialogClose asChild>
<Button type="button" variant="secondary">
Cancel
<Trans>Cancel</Trans>
</Button>
</DialogClose>
<Button type="submit" variant="destructive" loading={isDisable2FASubmitting}>
Disable 2FA
<Trans>Disable 2FA</Trans>
</Button>
</DialogFooter>
</fieldset>

View File

@ -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 { renderSVG } from 'uqr';
import { z } from 'zod';
@ -46,6 +48,7 @@ export type EnableAuthenticatorAppDialogProps = {
};
export const EnableAuthenticatorAppDialog = ({ onSuccess }: EnableAuthenticatorAppDialogProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const router = useRouter();
@ -62,9 +65,10 @@ export const EnableAuthenticatorAppDialog = ({ onSuccess }: EnableAuthenticatorA
} = trpc.twoFactorAuthentication.setup.useMutation({
onError: () => {
toast({
title: 'Unable to setup two-factor authentication',
description:
'We were unable to setup two-factor authentication for your account. Please ensure that you have entered your code correctly and try again.',
title: _(msg`Unable to setup two-factor authentication`),
description: _(
msg`We were unable to setup two-factor authentication for your account. Please ensure that you have entered your code correctly and try again.`,
),
variant: 'destructive',
});
},
@ -87,15 +91,17 @@ export const EnableAuthenticatorAppDialog = ({ onSuccess }: EnableAuthenticatorA
onSuccess?.();
toast({
title: 'Two-factor authentication enabled',
description:
'You will now be required to enter a code from your authenticator app when signing in.',
title: _(msg`Two-factor authentication enabled`),
description: _(
msg`You will now be required to enter a code from your authenticator app when signing in.`,
),
});
} catch (_err) {
toast({
title: 'Unable to setup two-factor authentication',
description:
'We were unable to setup two-factor authentication for your account. Please ensure that you have entered your code correctly and try again.',
title: _(msg`Unable to setup two-factor authentication`),
description: _(
msg`We were unable to setup two-factor authentication for your account. Please ensure that you have entered your code correctly and try again.`,
),
variant: 'destructive',
});
}
@ -144,7 +150,7 @@ export const EnableAuthenticatorAppDialog = ({ onSuccess }: EnableAuthenticatorA
void handleEnable2FA();
}}
>
Enable 2FA
<Trans>Enable 2FA</Trans>
</Button>
</DialogTrigger>
@ -154,9 +160,13 @@ export const EnableAuthenticatorAppDialog = ({ onSuccess }: EnableAuthenticatorA
{recoveryCodes ? (
<div>
<DialogHeader>
<DialogTitle>Backup codes</DialogTitle>
<DialogTitle>
<Trans>Backup codes</Trans>
</DialogTitle>
<DialogDescription>
Your recovery codes are listed below. Please store them in a safe place.
<Trans>
Your recovery codes are listed below. Please store them in a safe place.
</Trans>
</DialogDescription>
</DialogHeader>
@ -166,20 +176,28 @@ export const EnableAuthenticatorAppDialog = ({ onSuccess }: EnableAuthenticatorA
<DialogFooter className="mt-4">
<DialogClose asChild>
<Button variant="secondary">Close</Button>
<Button variant="secondary">
<Trans>Close</Trans>
</Button>
</DialogClose>
<Button onClick={downloadRecoveryCodes}>Download</Button>
<Button onClick={downloadRecoveryCodes}>
<Trans>Download</Trans>
</Button>
</DialogFooter>
</div>
) : (
<Form {...enable2FAForm}>
<form onSubmit={enable2FAForm.handleSubmit(onEnable2FAFormSubmit)}>
<DialogHeader>
<DialogTitle>Enable Authenticator App</DialogTitle>
<DialogTitle>
<Trans>Enable Authenticator App</Trans>
</DialogTitle>
<DialogDescription>
To enable two-factor authentication, scan the following QR code using your
authenticator app.
<Trans>
To enable two-factor authentication, scan the following QR code using your
authenticator app.
</Trans>
</DialogDescription>
</DialogHeader>
@ -192,8 +210,10 @@ export const EnableAuthenticatorAppDialog = ({ onSuccess }: EnableAuthenticatorA
/>
<p className="text-muted-foreground text-sm">
If your authenticator app does not support QR codes, you can use the following
code instead:
<Trans>
If your authenticator app does not support QR codes, you can use the
following code instead:
</Trans>
</p>
<p className="bg-muted/60 text-muted-foreground rounded-lg p-2 text-center font-mono tracking-widest">
@ -201,8 +221,10 @@ export const EnableAuthenticatorAppDialog = ({ onSuccess }: EnableAuthenticatorA
</p>
<p className="text-muted-foreground text-sm">
Once you have scanned the QR code or entered the code manually, enter the code
provided by your authenticator app below.
<Trans>
Once you have scanned the QR code or entered the code manually, enter the
code provided by your authenticator app below.
</Trans>
</p>
<FormField
@ -210,7 +232,9 @@ export const EnableAuthenticatorAppDialog = ({ onSuccess }: EnableAuthenticatorA
control={enable2FAForm.control}
render={({ field }) => (
<FormItem>
<FormLabel className="text-muted-foreground">Token</FormLabel>
<FormLabel className="text-muted-foreground">
<Trans>Token</Trans>
</FormLabel>
<FormControl>
<PinInput {...field} value={field.value ?? ''} maxLength={6}>
{Array(6)
@ -229,11 +253,13 @@ export const EnableAuthenticatorAppDialog = ({ onSuccess }: EnableAuthenticatorA
<DialogFooter>
<DialogClose asChild>
<Button variant="secondary">Cancel</Button>
<Button variant="secondary">
<Trans>Cancel</Trans>
</Button>
</DialogClose>
<Button type="submit" loading={isEnabling2FA}>
Enable 2FA
<Trans>Enable 2FA</Trans>
</Button>
</DialogFooter>
</fieldset>

View File

@ -1,3 +1,5 @@
import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { Copy } from 'lucide-react';
import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard';
@ -8,6 +10,7 @@ export type RecoveryCodeListProps = {
};
export const RecoveryCodeList = ({ recoveryCodes }: RecoveryCodeListProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const [, copyToClipboard] = useCopyToClipboard();
@ -20,14 +23,15 @@ export const RecoveryCodeList = ({ recoveryCodes }: RecoveryCodeListProps) => {
}
toast({
title: 'Recovery code copied',
description: 'Your recovery code has been copied to your clipboard.',
title: _(msg`Recovery code copied`),
description: _(msg`Your recovery code has been copied to your clipboard.`),
});
} catch (_err) {
toast({
title: 'Unable to copy recovery code',
description:
'We were unable to copy your recovery code to your clipboard. Please try again.',
title: _(msg`Unable to copy recovery code`),
description: _(
msg`We were unable to copy your recovery code to your clipboard. Please try again.`,
),
variant: 'destructive',
});
}

View File

@ -3,6 +3,7 @@
import { useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans } from '@lingui/macro';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
import { z } from 'zod';
@ -73,17 +74,23 @@ export const ViewRecoveryCodesDialog = () => {
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button className="flex-shrink-0">View Codes</Button>
<Button className="flex-shrink-0">
<Trans>View Codes</Trans>
</Button>
</DialogTrigger>
<DialogContent className="w-full max-w-xl md:max-w-xl lg:max-w-xl">
{recoveryCodes ? (
<div>
<DialogHeader className="mb-4">
<DialogTitle>View Recovery Codes</DialogTitle>
<DialogTitle>
<Trans>View Recovery Codes</Trans>
</DialogTitle>
<DialogDescription>
Your recovery codes are listed below. Please store them in a safe place.
<Trans>
Your recovery codes are listed below. Please store them in a safe place.
</Trans>
</DialogDescription>
</DialogHeader>
@ -91,20 +98,26 @@ export const ViewRecoveryCodesDialog = () => {
<DialogFooter className="mt-4">
<DialogClose asChild>
<Button variant="secondary">Close</Button>
<Button variant="secondary">
<Trans>Close</Trans>
</Button>
</DialogClose>
<Button onClick={downloadRecoveryCodes}>Download</Button>
<Button onClick={downloadRecoveryCodes}>
<Trans>Download</Trans>
</Button>
</DialogFooter>
</div>
) : (
<Form {...viewRecoveryCodesForm}>
<form onSubmit={viewRecoveryCodesForm.handleSubmit((value) => mutate(value))}>
<DialogHeader className="mb-4">
<DialogTitle>View Recovery Codes</DialogTitle>
<DialogTitle>
<Trans>View Recovery Codes</Trans>
</DialogTitle>
<DialogDescription>
Please provide a token from your authenticator, or a backup code.
<Trans>Please provide a token from your authenticator, or a backup code.</Trans>
</DialogDescription>
</DialogHeader>
@ -134,13 +147,12 @@ export const ViewRecoveryCodesDialog = () => {
<Alert variant="destructive">
<AlertDescription>
{match(AppError.parseError(error).message)
.with(
ErrorCode.INCORRECT_TWO_FACTOR_CODE,
() => 'Invalid code. Please try again.',
)
.otherwise(
() => 'Something went wrong. Please try again or contact support.',
)}
.with(ErrorCode.INCORRECT_TWO_FACTOR_CODE, () => (
<Trans>Invalid code. Please try again.</Trans>
))
.otherwise(() => (
<Trans>Something went wrong. Please try again or contact support.</Trans>
))}
</AlertDescription>
</Alert>
)}
@ -148,12 +160,12 @@ export const ViewRecoveryCodesDialog = () => {
<DialogFooter>
<DialogClose asChild>
<Button type="button" variant="secondary">
Cancel
<Trans>Cancel</Trans>
</Button>
</DialogClose>
<Button type="submit" loading={isLoading}>
View
<Trans>View</Trans>
</Button>
</DialogFooter>
</fieldset>

View File

@ -5,6 +5,8 @@ import { useMemo } 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 { ErrorCode, useDropzone } from 'react-dropzone';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
@ -42,7 +44,9 @@ export type AvatarImageFormProps = {
};
export const AvatarImageForm = ({ className, user, team }: AvatarImageFormProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const router = useRouter();
const { mutateAsync: setProfileImage } = trpc.profile.setProfileImage.useMutation();
@ -84,10 +88,10 @@ export const AvatarImageForm = ({ className, user, team }: AvatarImageFormProps)
form.setError('bytes', {
type: 'onChange',
message: match(file.errors[0].code)
.with(ErrorCode.FileTooLarge, () => 'Uploaded file is too large')
.with(ErrorCode.FileTooSmall, () => 'Uploaded file is too small')
.with(ErrorCode.FileInvalidType, () => 'Uploaded file not an allowed file type')
.otherwise(() => 'An unknown error occurred'),
.with(ErrorCode.FileTooLarge, () => _(msg`Uploaded file is too large`))
.with(ErrorCode.FileTooSmall, () => _(msg`Uploaded file is too small`))
.with(ErrorCode.FileInvalidType, () => _(msg`Uploaded file not an allowed file type`))
.otherwise(() => _(msg`An unknown error occurred`)),
});
},
});
@ -100,8 +104,8 @@ export const AvatarImageForm = ({ className, user, team }: AvatarImageFormProps)
});
toast({
title: 'Avatar Updated',
description: 'Your avatar has been updated successfully.',
title: _(msg`Avatar Updated`),
description: _(msg`Your avatar has been updated successfully.`),
duration: 5000,
});
@ -109,16 +113,17 @@ export const AvatarImageForm = ({ className, user, team }: AvatarImageFormProps)
} catch (err) {
if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
toast({
title: 'An error occurred',
title: _(msg`An error occurred`),
description: err.message,
variant: 'destructive',
});
} else {
toast({
title: 'An unknown error occurred',
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to update the avatar. Please try again later.`,
),
variant: 'destructive',
description:
'We encountered an unknown error while attempting to update the avatar. Please try again later.',
});
}
}
@ -136,7 +141,9 @@ export const AvatarImageForm = ({ className, user, team }: AvatarImageFormProps)
name="bytes"
render={() => (
<FormItem>
<FormLabel>Avatar</FormLabel>
<FormLabel>
<Trans>Avatar</Trans>
</FormLabel>
<FormControl>
<div className="flex items-center gap-8">
@ -159,7 +166,7 @@ export const AvatarImageForm = ({ className, user, team }: AvatarImageFormProps)
disabled={form.formState.isSubmitting}
onClick={() => void onFormSubmit({ bytes: null })}
>
Remove
<Trans>Remove</Trans>
</button>
)}
</div>
@ -172,7 +179,7 @@ export const AvatarImageForm = ({ className, user, team }: AvatarImageFormProps)
loading={form.formState.isSubmitting}
disabled={form.formState.isSubmitting}
>
Upload Avatar
<Trans>Upload Avatar</Trans>
<input {...getInputProps()} />
</Button>
</div>

View File

@ -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 { useForm } from 'react-hook-form';
import { z } from 'zod';
@ -31,9 +33,11 @@ export type ForgotPasswordFormProps = {
};
export const ForgotPasswordForm = ({ className }: ForgotPasswordFormProps) => {
const router = useRouter();
const { _ } = useLingui();
const { toast } = useToast();
const router = useRouter();
const form = useForm<TForgotPasswordFormSchema>({
values: {
email: '',
@ -49,9 +53,10 @@ export const ForgotPasswordForm = ({ className }: ForgotPasswordFormProps) => {
await forgotPassword({ email }).catch(() => null);
toast({
title: 'Reset email sent',
description:
'A password reset email has been sent, if you have an account you should see it in your inbox shortly.',
title: _(msg`Reset email sent`),
description: _(
msg`A password reset email has been sent, if you have an account you should see it in your inbox shortly.`,
),
duration: 5000,
});
@ -72,7 +77,9 @@ export const ForgotPasswordForm = ({ className }: ForgotPasswordFormProps) => {
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormLabel>
<Trans>Email</Trans>
</FormLabel>
<FormControl>
<Input type="email" {...field} />
</FormControl>
@ -83,7 +90,7 @@ export const ForgotPasswordForm = ({ className }: ForgotPasswordFormProps) => {
</fieldset>
<Button size="lg" loading={isSubmitting}>
{isSubmitting ? 'Sending Reset Email...' : 'Reset Password'}
{isSubmitting ? <Trans>Sending Reset Email...</Trans> : <Trans>Reset Password</Trans>}
</Button>
</form>
</Form>

View File

@ -1,6 +1,8 @@
'use client';
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';
@ -40,6 +42,7 @@ export type PasswordFormProps = {
};
export const PasswordForm = ({ className }: PasswordFormProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const form = useForm<TPasswordFormSchema>({
@ -65,23 +68,24 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
form.reset();
toast({
title: 'Password updated',
description: 'Your password has been updated successfully.',
title: _(msg`Password updated`),
description: _(msg`Your password has been updated successfully.`),
duration: 5000,
});
} catch (err) {
if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
toast({
title: 'An error occurred',
title: _(msg`An error occurred`),
description: err.message,
variant: 'destructive',
});
} else {
toast({
title: 'An unknown error occurred',
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to update your password. Please try again later.`,
),
variant: 'destructive',
description:
'We encountered an unknown error while attempting to update your password. Please try again later.',
});
}
}
@ -99,7 +103,9 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
name="currentPassword"
render={({ field }) => (
<FormItem>
<FormLabel>Current Password</FormLabel>
<FormLabel>
<Trans>Current Password</Trans>
</FormLabel>
<FormControl>
<PasswordInput autoComplete="current-password" {...field} />
</FormControl>
@ -113,7 +119,9 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormLabel>
<Trans>Password</Trans>
</FormLabel>
<FormControl>
<PasswordInput autoComplete="new-password" {...field} />
</FormControl>
@ -127,7 +135,9 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
name="repeatedPassword"
render={({ field }) => (
<FormItem>
<FormLabel>Repeat Password</FormLabel>
<FormLabel>
<Trans>Repeat Password</Trans>
</FormLabel>
<FormControl>
<PasswordInput autoComplete="new-password" {...field} />
</FormControl>
@ -139,7 +149,7 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
<div className="ml-auto mt-4">
<Button type="submit" loading={isSubmitting}>
{isSubmitting ? 'Updating password...' : 'Update password'}
{isSubmitting ? <Trans>Updating password...</Trans> : <Trans>Update password</Trans>}
</Button>
</div>
</form>

View File

@ -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 { useForm } from 'react-hook-form';
import { z } from 'zod';
@ -44,6 +46,7 @@ export type ProfileFormProps = {
export const ProfileForm = ({ className, user }: ProfileFormProps) => {
const router = useRouter();
const { _ } = useLingui();
const { toast } = useToast();
const form = useForm<TProfileFormSchema>({
@ -66,8 +69,8 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
});
toast({
title: 'Profile updated',
description: 'Your profile has been updated successfully.',
title: _(msg`Profile updated`),
description: _(msg`Your profile has been updated successfully.`),
duration: 5000,
});
@ -75,16 +78,17 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
} catch (err) {
if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
toast({
title: 'An error occurred',
title: _(msg`An error occurred`),
description: err.message,
variant: 'destructive',
});
} else {
toast({
title: 'An unknown error occurred',
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to sign you In. Please try again later.`,
),
variant: 'destructive',
description:
'We encountered an unknown error while attempting to sign you In. Please try again later.',
});
}
}
@ -102,7 +106,9 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Full Name</FormLabel>
<FormLabel>
<Trans>Full Name</Trans>
</FormLabel>
<FormControl>
<Input type="text" {...field} />
</FormControl>
@ -113,7 +119,7 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
<div>
<Label htmlFor="email" className="text-muted-foreground">
Email
<Trans>Email</Trans>
</Label>
<Input id="email" type="email" className="bg-muted mt-2" value={user.email} disabled />
</div>
@ -122,7 +128,9 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
name="signature"
render={({ field: { onChange } }) => (
<FormItem>
<FormLabel>Signature</FormLabel>
<FormLabel>
<Trans>Signature</Trans>
</FormLabel>
<FormControl>
<SignaturePad
className="h-44 w-full"
@ -139,7 +147,7 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
</fieldset>
<Button type="submit" loading={isSubmitting} className="self-end">
{isSubmitting ? 'Updating profile...' : 'Update profile'}
{isSubmitting ? <Trans>Updating profile...</Trans> : <Trans>Update profile</Trans>}
</Button>
</form>
</Form>

View File

@ -5,6 +5,8 @@ import React, { useState } from 'react';
import Image from 'next/image';
import { zodResolver } from '@hookform/resolvers/zod';
import { msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
@ -61,6 +63,7 @@ export const ClaimPublicProfileDialogForm = ({
onClaimed,
user,
}: ClaimPublicProfileDialogFormProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const [claimed, setClaimed] = useState(false);
@ -92,7 +95,7 @@ export const ClaimPublicProfileDialogForm = ({
if (error.code === AppErrorCode.PROFILE_URL_TAKEN) {
form.setError('url', {
type: 'manual',
message: 'This username is already taken',
message: _(msg`This username is already taken`),
});
} else if (error.code === AppErrorCode.PREMIUM_PROFILE_URL) {
form.setError('url', {
@ -107,10 +110,11 @@ export const ClaimPublicProfileDialogForm = ({
});
} else {
toast({
title: 'An unknown error occurred',
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to save your details. Please try again later.`,
),
variant: 'destructive',
description:
'We encountered an unknown error while attempting to save your details. Please try again later.',
});
}
}

View File

@ -3,6 +3,8 @@
import { useState } from 'react';
import { zodResolver } from '@hookform/resolvers/zod';
import { Plural, Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { motion } from 'framer-motion';
import { AnimatePresence } from 'framer-motion';
import { CheckSquareIcon, CopyIcon } from 'lucide-react';
@ -53,6 +55,7 @@ export const PublicProfileForm = ({
teamUrl,
onProfileUpdate,
}: PublicProfileFormProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const [, copy] = useCopyToClipboard();
@ -74,8 +77,8 @@ export const PublicProfileForm = ({
await onProfileUpdate(data);
toast({
title: 'Success',
description: 'Your public profile has been updated.',
title: _(msg`Success`),
description: _(msg`Your public profile has been updated.`),
duration: 5000,
});
@ -98,10 +101,11 @@ export const PublicProfileForm = ({
default:
toast({
title: 'An unknown error occurred',
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to update your public profile. Please try again later.`,
),
variant: 'destructive',
description:
'We encountered an unknown error while attempting to update your public profile. Please try again later.',
});
}
}
@ -110,8 +114,8 @@ export const PublicProfileForm = ({
const onCopy = async () => {
await copy(formatUserProfilePath(form.getValues('url') ?? '')).then(() => {
toast({
title: 'Copied to clipboard',
description: 'The profile link has been copied to your clipboard',
title: _(msg`Copied to clipboard`),
description: _(msg`The profile link has been copied to your clipboard`),
});
});
@ -138,15 +142,19 @@ export const PublicProfileForm = ({
name="url"
render={({ field }) => (
<FormItem>
<FormLabel>Public profile URL</FormLabel>
<FormLabel>
<Trans>Public profile URL</Trans>
</FormLabel>
<FormControl>
<Input {...field} disabled={field.disabled || teamUrl !== undefined} />
</FormControl>
{teamUrl && (
<p className="text-muted-foreground text-xs">
You can update the profile URL by updating the team URL in the general settings
page.
<Trans>
You can update the profile URL by updating the team URL in the general
settings page.
</Trans>
</p>
)}
@ -186,7 +194,9 @@ export const PublicProfileForm = ({
</Button>
</div>
) : (
<p>A unique URL to access your profile</p>
<p>
<Trans>A unique URL to access your profile</Trans>
</p>
)}
</div>
)}
@ -202,7 +212,6 @@ export const PublicProfileForm = ({
name="bio"
render={({ field }) => {
const remaningLength = MAX_PROFILE_BIO_LENGTH - (field.value || '').length;
const pluralWord = Math.abs(remaningLength) === 1 ? 'character' : 'characters';
return (
<FormItem>
@ -210,15 +219,27 @@ export const PublicProfileForm = ({
<FormControl>
<Textarea
{...field}
placeholder={teamUrl ? 'Write about the team' : 'Write about yourself'}
placeholder={
teamUrl ? _(msg`Write about the team`) : _(msg`Write about yourself`)
}
/>
</FormControl>
{!form.formState.errors.bio && (
<p className="text-muted-foreground text-sm">
{remaningLength >= 0
? `${remaningLength} ${pluralWord} remaining`
: `${Math.abs(remaningLength)} ${pluralWord} over the limit`}
{remaningLength >= 0 ? (
<Plural
value={remaningLength}
one={<Trans># character remaining</Trans>}
other={<Trans># characters remaining</Trans>}
/>
) : (
<Plural
value={Math.abs(remaningLength)}
one={<Trans># character over the limit</Trans>}
other={<Trans># characters over the limit</Trans>}
/>
)}
</p>
)}
@ -243,7 +264,7 @@ export const PublicProfileForm = ({
}}
>
<Button type="button" variant="secondary" onClick={() => form.reset()}>
Reset
<Trans>Reset</Trans>
</Button>
</motion.div>
)}
@ -255,7 +276,7 @@ export const PublicProfileForm = ({
disabled={!form.formState.isDirty}
loading={form.formState.isSubmitting}
>
Update
<Trans>Update</Trans>
</Button>
</div>
</fieldset>

View File

@ -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 { useForm } from 'react-hook-form';
import { z } from 'zod';
@ -42,6 +44,7 @@ export type ResetPasswordFormProps = {
export const ResetPasswordForm = ({ className, token }: ResetPasswordFormProps) => {
const router = useRouter();
const { _ } = useLingui();
const { toast } = useToast();
const form = useForm<TResetPasswordFormSchema>({
@ -66,8 +69,8 @@ export const ResetPasswordForm = ({ className, token }: ResetPasswordFormProps)
form.reset();
toast({
title: 'Password updated',
description: 'Your password has been updated successfully.',
title: _(msg`Password updated`),
description: _(msg`Your password has been updated successfully.`),
duration: 5000,
});
@ -75,16 +78,17 @@ export const ResetPasswordForm = ({ className, token }: ResetPasswordFormProps)
} catch (err) {
if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
toast({
title: 'An error occurred',
title: _(msg`An error occurred`),
description: err.message,
variant: 'destructive',
});
} else {
toast({
title: 'An unknown error occurred',
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to reset your password. Please try again later.`,
),
variant: 'destructive',
description:
'We encountered an unknown error while attempting to reset your password. Please try again later.',
});
}
}
@ -102,7 +106,9 @@ export const ResetPasswordForm = ({ className, token }: ResetPasswordFormProps)
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormLabel>
<Trans>Password</Trans>
</FormLabel>
<FormControl>
<PasswordInput {...field} />
</FormControl>
@ -116,7 +122,9 @@ export const ResetPasswordForm = ({ className, token }: ResetPasswordFormProps)
name="repeatedPassword"
render={({ field }) => (
<FormItem>
<FormLabel>Repeat Password</FormLabel>
<FormLabel>
<Trans>Repeat Password</Trans>
</FormLabel>
<FormControl>
<PasswordInput {...field} />
</FormControl>
@ -127,7 +135,7 @@ export const ResetPasswordForm = ({ className, token }: ResetPasswordFormProps)
</fieldset>
<Button type="submit" size="lg" loading={isSubmitting}>
{isSubmitting ? 'Resetting Password...' : 'Reset Password'}
{isSubmitting ? <Trans>Resetting Password...</Trans> : <Trans>Reset Password</Trans>}
</Button>
</form>
</Form>

View File

@ -1,6 +1,8 @@
'use client';
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';
@ -29,6 +31,7 @@ export type SendConfirmationEmailFormProps = {
};
export const SendConfirmationEmailForm = ({ className }: SendConfirmationEmailFormProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const form = useForm<TSendConfirmationEmailFormSchema>({
@ -47,18 +50,18 @@ export const SendConfirmationEmailForm = ({ className }: SendConfirmationEmailFo
await sendConfirmationEmail({ email });
toast({
title: 'Confirmation email sent',
description:
'A confirmation email has been sent, and it should arrive in your inbox shortly.',
title: _(msg`Confirmation email sent`),
description: _(
msg`A confirmation email has been sent, and it should arrive in your inbox shortly.`,
),
duration: 5000,
});
form.reset();
} catch (err) {
toast({
title: 'An error occurred while sending your confirmation email',
description: 'Please try again and make sure you enter the correct email address.',
variant: 'destructive',
title: _(msg`An error occurred while sending your confirmation email`),
description: _(msg`Please try again and make sure you enter the correct email address.`),
});
}
};
@ -75,7 +78,9 @@ export const SendConfirmationEmailForm = ({ className }: SendConfirmationEmailFo
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email address</FormLabel>
<FormLabel>
<Trans>Email address</Trans>
</FormLabel>
<FormControl>
<Input type="email" {...field} />
</FormControl>
@ -86,7 +91,7 @@ export const SendConfirmationEmailForm = ({ className }: SendConfirmationEmailFo
<FormMessage />
<Button size="lg" type="submit" disabled={isSubmitting} loading={isSubmitting}>
Send confirmation email
<Trans>Send confirmation email</Trans>
</Button>
</fieldset>
</form>

View File

@ -6,6 +6,8 @@ import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, msg } from '@lingui/macro';
import { useLingui } from '@lingui/react';
import { browserSupportsWebAuthn, startAuthentication } from '@simplewebauthn/browser';
import { KeyRoundIcon } from 'lucide-react';
import { signIn } from 'next-auth/react';
@ -81,6 +83,7 @@ export const SignInForm = ({
isOIDCSSOEnabled,
oidcProviderLabel,
}: SignInFormProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const { getFlag } = useFeatureFlags();
@ -136,8 +139,8 @@ export const SignInForm = ({
const onSignInWithPasskey = async () => {
if (!browserSupportsWebAuthn()) {
toast({
title: 'Not supported',
description: 'Passkeys are not supported on this browser',
title: _(msg`Not supported`),
description: _(msg`Passkeys are not supported on this browser`),
duration: 10000,
variant: 'destructive',
});
@ -176,14 +179,14 @@ export const SignInForm = ({
.with(
AppErrorCode.NOT_SETUP,
() =>
'This passkey is not configured for this application. Please login and add one in the user settings.',
msg`This passkey is not configured for this application. Please login and add one in the user settings.`,
)
.with(AppErrorCode.EXPIRED_CODE, () => 'This session has expired. Please try again.')
.otherwise(() => 'Please try again later or login using your normal details');
.with(AppErrorCode.EXPIRED_CODE, () => msg`This session has expired. Please try again.`)
.otherwise(() => msg`Please try again later or login using your normal details`);
toast({
title: 'Something went wrong',
description: errorMessage,
description: _(errorMessage),
duration: 10000,
variant: 'destructive',
});
@ -223,17 +226,17 @@ export const SignInForm = ({
router.push(`/unverified-account`);
toast({
title: 'Unable to sign in',
description: errorMessage ?? 'An unknown error occurred',
title: _(msg`Unable to sign in`),
description: errorMessage ?? _(msg`An unknown error occurred`),
});
return;
}
toast({
title: _(msg`Unable to sign in`),
description: errorMessage ?? _(msg`An unknown error occurred`),
variant: 'destructive',
title: 'Unable to sign in',
description: errorMessage ?? 'An unknown error occurred',
});
return;
@ -246,9 +249,10 @@ export const SignInForm = ({
window.location.href = result.url;
} catch (err) {
toast({
title: 'An unknown error occurred',
description:
'We encountered an unknown error while attempting to sign you In. Please try again later.',
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to sign you In. Please try again later.`,
),
});
}
};
@ -258,9 +262,10 @@ export const SignInForm = ({
await signIn('google', { callbackUrl: LOGIN_REDIRECT_PATH });
} catch (err) {
toast({
title: 'An unknown error occurred',
description:
'We encountered an unknown error while attempting to sign you In. Please try again later.',
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to sign you In. Please try again later.`,
),
variant: 'destructive',
});
}
@ -271,9 +276,10 @@ export const SignInForm = ({
await signIn('oidc', { callbackUrl: LOGIN_REDIRECT_PATH });
} catch (err) {
toast({
title: 'An unknown error occurred',
description:
'We encountered an unknown error while attempting to sign you In. Please try again later.',
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to sign you In. Please try again later.`,
),
variant: 'destructive',
});
}
@ -294,7 +300,9 @@ export const SignInForm = ({
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormLabel>
<Trans>Email</Trans>
</FormLabel>
<FormControl>
<Input type="email" {...field} />
@ -310,7 +318,9 @@ export const SignInForm = ({
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormLabel>
<Trans>Password</Trans>
</FormLabel>
<FormControl>
<PasswordInput {...field} />
@ -323,7 +333,7 @@ export const SignInForm = ({
href="/forgot-password"
className="text-muted-foreground text-sm duration-200 hover:opacity-70"
>
Forgot your password?
<Trans>Forgot your password?</Trans>
</Link>
</p>
</FormItem>
@ -336,13 +346,15 @@ export const SignInForm = ({
loading={isSubmitting}
className="dark:bg-documenso dark:hover:opacity-90"
>
{isSubmitting ? 'Signing in...' : 'Sign In'}
{isSubmitting ? <Trans>Signing in...</Trans> : <Trans>Sign In</Trans>}
</Button>
{(isGoogleSSOEnabled || isPasskeyEnabled || isOIDCSSOEnabled) && (
<div className="relative flex items-center justify-center gap-x-4 py-2 text-xs uppercase">
<div className="bg-border h-px flex-1" />
<span className="text-muted-foreground bg-transparent">Or continue with</span>
<span className="text-muted-foreground bg-transparent">
<Trans>Or continue with</Trans>
</span>
<div className="bg-border h-px flex-1" />
</div>
)}
@ -386,7 +398,7 @@ export const SignInForm = ({
onClick={onSignInWithPasskey}
>
{!isPasskeyLoading && <KeyRoundIcon className="-ml-1 mr-1 h-5 w-5" />}
Passkey
<Trans>Passkey</Trans>
</Button>
)}
</fieldset>
@ -398,7 +410,9 @@ export const SignInForm = ({
>
<DialogContent>
<DialogHeader>
<DialogTitle>Two-Factor Authentication</DialogTitle>
<DialogTitle>
<Trans>Two-Factor Authentication</Trans>
</DialogTitle>
</DialogHeader>
<form onSubmit={form.handleSubmit(onFormSubmit)}>
@ -433,7 +447,9 @@ export const SignInForm = ({
name="backupCode"
render={({ field }) => (
<FormItem>
<FormLabel> Backup Code</FormLabel>
<FormLabel>
<Trans>Backup Code</Trans>
</FormLabel>
<FormControl>
<Input type="text" {...field} />
</FormControl>
@ -449,13 +465,15 @@ export const SignInForm = ({
variant="secondary"
onClick={onToggleTwoFactorAuthenticationMethodClick}
>
{twoFactorAuthenticationMethod === 'totp'
? 'Use Backup Code'
: 'Use Authenticator'}
{twoFactorAuthenticationMethod === 'totp' ? (
<Trans>Use Backup Code</Trans>
) : (
<Trans>Use Authenticator</Trans>
)}
</Button>
<Button type="submit" loading={isSubmitting}>
{isSubmitting ? 'Signing in...' : 'Sign In'}
{isSubmitting ? <Trans>Signing in...</Trans> : <Trans>Sign In</Trans>}
</Button>
</DialogFooter>
</fieldset>

View File

@ -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 { signIn } from 'next-auth/react';
import { useForm } from 'react-hook-form';
import { FcGoogle } from 'react-icons/fc';
@ -61,7 +63,9 @@ export const SignUpForm = ({
isGoogleSSOEnabled,
isOIDCSSOEnabled,
}: SignUpFormProps) => {
const { _ } = useLingui();
const { toast } = useToast();
const analytics = useAnalytics();
const router = useRouter();
@ -86,9 +90,10 @@ export const SignUpForm = ({
router.push(`/unverified-account`);
toast({
title: 'Registration Successful',
description:
'You have successfully registered. Please verify your account by clicking on the link you received in the email.',
title: _(msg`Registration Successful`),
description: _(
msg`You have successfully registered. Please verify your account by clicking on the link you received in the email.`,
),
duration: 5000,
});
@ -99,15 +104,16 @@ export const SignUpForm = ({
} catch (err) {
if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
toast({
title: 'An error occurred',
title: _(msg`An error occurred`),
description: err.message,
variant: 'destructive',
});
} else {
toast({
title: 'An unknown error occurred',
description:
'We encountered an unknown error while attempting to sign you up. Please try again later.',
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to sign you up. Please try again later.`,
),
variant: 'destructive',
});
}
@ -119,9 +125,10 @@ export const SignUpForm = ({
await signIn('google', { callbackUrl: SIGN_UP_REDIRECT_PATH });
} catch (err) {
toast({
title: 'An unknown error occurred',
description:
'We encountered an unknown error while attempting to sign you Up. Please try again later.',
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to sign you Up. Please try again later.`,
),
variant: 'destructive',
});
}
@ -132,9 +139,10 @@ export const SignUpForm = ({
await signIn('oidc', { callbackUrl: SIGN_UP_REDIRECT_PATH });
} catch (err) {
toast({
title: 'An unknown error occurred',
description:
'We encountered an unknown error while attempting to sign you Up. Please try again later.',
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to sign you Up. Please try again later.`,
),
variant: 'destructive',
});
}
@ -152,7 +160,9 @@ export const SignUpForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormLabel>
<Trans>Name</Trans>
</FormLabel>
<FormControl>
<Input type="text" {...field} />
</FormControl>
@ -166,7 +176,9 @@ export const SignUpForm = ({
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormLabel>
<Trans>Email</Trans>
</FormLabel>
<FormControl>
<Input type="email" {...field} />
</FormControl>
@ -180,7 +192,9 @@ export const SignUpForm = ({
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormLabel>
<Trans>Password</Trans>
</FormLabel>
<FormControl>
<PasswordInput {...field} />
</FormControl>
@ -194,7 +208,9 @@ export const SignUpForm = ({
name="signature"
render={({ field: { onChange } }) => (
<FormItem>
<FormLabel>Sign Here</FormLabel>
<FormLabel>
<Trans>Sign Here</Trans>
</FormLabel>
<FormControl>
<SignaturePad
className="h-36 w-full"
@ -216,14 +232,16 @@ export const SignUpForm = ({
loading={isSubmitting}
className="dark:bg-documenso dark:hover:opacity-90"
>
{isSubmitting ? 'Signing up...' : 'Sign Up'}
{isSubmitting ? <Trans>Signing up...</Trans> : <Trans>Sign Up</Trans>}
</Button>
{isGoogleSSOEnabled && (
<>
<div className="relative flex items-center justify-center gap-x-4 py-2 text-xs uppercase">
<div className="bg-border h-px flex-1" />
<span className="text-muted-foreground bg-transparent">Or</span>
<span className="text-muted-foreground bg-transparent">
<Trans>Or</Trans>
</span>
<div className="bg-border h-px flex-1" />
</div>
@ -236,7 +254,7 @@ export const SignUpForm = ({
onClick={onSignUpWithGoogleClick}
>
<FcGoogle className="mr-2 h-5 w-5" />
Sign Up with Google
<Trans>Sign Up with Google</Trans>
</Button>
</>
)}
@ -245,7 +263,9 @@ export const SignUpForm = ({
<>
<div className="relative flex items-center justify-center gap-x-4 py-2 text-xs uppercase">
<div className="bg-border h-px flex-1" />
<span className="text-muted-foreground bg-transparent">Or</span>
<span className="text-muted-foreground bg-transparent">
<Trans>Or</Trans>
</span>
<div className="bg-border h-px flex-1" />
</div>
@ -258,7 +278,7 @@ export const SignUpForm = ({
onClick={onSignUpWithOIDCClick}
>
<FcGoogle className="mr-2 h-5 w-5" />
Sign Up with OIDC
<Trans>Sign Up with OIDC</Trans>
</Button>
</>
)}

View File

@ -5,6 +5,8 @@ import { useState, useTransition } 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 { AnimatePresence, motion } from 'framer-motion';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
@ -62,6 +64,8 @@ export const ApiTokenForm = ({ className, teamId, tokens }: ApiTokenFormProps) =
const [isTransitionPending, startTransition] = useTransition();
const [, copy] = useCopyToClipboard();
const { _ } = useLingui();
const { toast } = useToast();
const [newlyCreatedToken, setNewlyCreatedToken] = useState<NewlyCreatedToken | null>();
@ -98,13 +102,13 @@ export const ApiTokenForm = ({ className, teamId, tokens }: ApiTokenFormProps) =
}
toast({
title: 'Token copied to clipboard',
description: 'The token was copied to your clipboard.',
title: _(msg`Token copied to clipboard`),
description: _(msg`The token was copied to your clipboard.`),
});
} catch (error) {
toast({
title: 'Unable to copy token',
description: 'We were unable to copy the token to your clipboard. Please try again.',
title: _(msg`Unable to copy token`),
description: _(msg`We were unable to copy the token to your clipboard. Please try again.`),
variant: 'destructive',
});
}
@ -119,8 +123,8 @@ export const ApiTokenForm = ({ className, teamId, tokens }: ApiTokenFormProps) =
});
toast({
title: 'Token created',
description: 'A new token was created successfully.',
title: _(msg`Token created`),
description: _(msg`A new token was created successfully.`),
duration: 5000,
});
@ -130,17 +134,18 @@ export const ApiTokenForm = ({ className, teamId, tokens }: ApiTokenFormProps) =
} catch (error) {
if (error instanceof TRPCClientError && error.data?.code === 'BAD_REQUEST') {
toast({
title: 'An error occurred',
title: _(msg`An error occurred`),
description: error.message,
variant: 'destructive',
});
} else {
toast({
title: 'An unknown error occurred',
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting create the new token. Please try again later.`,
),
variant: 'destructive',
duration: 5000,
description:
'We encountered an unknown error while attempting create the new token. Please try again later.',
});
}
}
@ -156,7 +161,9 @@ export const ApiTokenForm = ({ className, teamId, tokens }: ApiTokenFormProps) =
name="tokenName"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel className="text-muted-foreground">Token name</FormLabel>
<FormLabel className="text-muted-foreground">
<Trans>Token name</Trans>
</FormLabel>
<div className="flex items-center gap-x-4">
<FormControl className="flex-1">
@ -165,8 +172,10 @@ export const ApiTokenForm = ({ className, teamId, tokens }: ApiTokenFormProps) =
</div>
<FormDescription className="text-xs italic">
Please enter a meaningful name for your token. This will help you identify it
later.
<Trans>
Please enter a meaningful name for your token. This will help you identify it
later.
</Trans>
</FormDescription>
<FormMessage />
@ -180,13 +189,15 @@ export const ApiTokenForm = ({ className, teamId, tokens }: ApiTokenFormProps) =
name="expirationDate"
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel className="text-muted-foreground">Token expiration date</FormLabel>
<FormLabel className="text-muted-foreground">
<Trans>Token expiration date</Trans>
</FormLabel>
<div className="flex items-center gap-x-4">
<FormControl className="flex-1">
<Select onValueChange={field.onChange} disabled={noExpirationDate}>
<SelectTrigger className="w-full">
<SelectValue placeholder="Choose..." />
<SelectValue placeholder={_(msg`Choose...`)} />
</SelectTrigger>
<SelectContent>
{Object.entries(EXPIRATION_DATES).map(([key, date]) => (
@ -209,7 +220,9 @@ export const ApiTokenForm = ({ className, teamId, tokens }: ApiTokenFormProps) =
name="enabled"
render={({ field }) => (
<FormItem className="">
<FormLabel className="text-muted-foreground mt-2">Never expire</FormLabel>
<FormLabel className="text-muted-foreground mt-2">
<Trans>Never expire</Trans>
</FormLabel>
<FormControl>
<div className="block md:py-1.5">
<Switch
@ -234,7 +247,7 @@ export const ApiTokenForm = ({ className, teamId, tokens }: ApiTokenFormProps) =
disabled={!form.formState.isDirty}
loading={form.formState.isSubmitting || isTransitionPending}
>
Create token
<Trans>Create token</Trans>
</Button>
<div className="md:hidden">
@ -243,7 +256,7 @@ export const ApiTokenForm = ({ className, teamId, tokens }: ApiTokenFormProps) =
disabled={!form.formState.isDirty}
loading={form.formState.isSubmitting || isTransitionPending}
>
Create token
<Trans>Create token</Trans>
</Button>
</div>
</fieldset>
@ -261,8 +274,10 @@ export const ApiTokenForm = ({ className, teamId, tokens }: ApiTokenFormProps) =
<Card gradient>
<CardContent className="p-4">
<p className="text-muted-foreground mt-2 text-sm">
Your token was created successfully! Make sure to copy it because you won't be
able to see it again!
<Trans>
Your token was created successfully! Make sure to copy it because you won't be
able to see it again!
</Trans>
</p>
<p className="bg-muted-foreground/10 my-4 rounded-md px-2.5 py-1 font-mono text-sm">
@ -270,7 +285,7 @@ export const ApiTokenForm = ({ className, teamId, tokens }: ApiTokenFormProps) =
</p>
<Button variant="outline" onClick={() => void copyToken(newlyCreatedToken.token)}>
Copy token
<Trans>Copy token</Trans>
</Button>
</CardContent>
</Card>

View File

@ -7,6 +7,8 @@ import Link from 'next/link';
import { useRouter, useSearchParams } 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 { signIn } from 'next-auth/react';
import { useForm } from 'react-hook-form';
@ -83,7 +85,9 @@ export const SignUpFormV2 = ({
isGoogleSSOEnabled,
isOIDCSSOEnabled,
}: SignUpFormV2Props) => {
const { _ } = useLingui();
const { toast } = useToast();
const analytics = useAnalytics();
const router = useRouter();
const searchParams = useSearchParams();
@ -120,9 +124,10 @@ export const SignUpFormV2 = ({
router.push(`/unverified-account`);
toast({
title: 'Registration Successful',
description:
'You have successfully registered. Please verify your account by clicking on the link you received in the email.',
title: _(msg`Registration Successful`),
description: _(
msg`You have successfully registered. Please verify your account by clicking on the link you received in the email.`,
),
duration: 5000,
});
@ -137,7 +142,7 @@ export const SignUpFormV2 = ({
if (error.code === AppErrorCode.PROFILE_URL_TAKEN) {
form.setError('url', {
type: 'manual',
message: 'This username has already been taken',
message: _(msg`This username has already been taken`),
});
} else if (error.code === AppErrorCode.PREMIUM_PROFILE_URL) {
form.setError('url', {
@ -146,15 +151,16 @@ export const SignUpFormV2 = ({
});
} else if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') {
toast({
title: 'An error occurred',
title: _(msg`An error occurred`),
description: err.message,
variant: 'destructive',
});
} else {
toast({
title: 'An unknown error occurred',
description:
'We encountered an unknown error while attempting to sign you up. Please try again later.',
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to sign you up. Please try again later.`,
),
variant: 'destructive',
});
}
@ -174,9 +180,10 @@ export const SignUpFormV2 = ({
await signIn('google', { callbackUrl: SIGN_UP_REDIRECT_PATH });
} catch (err) {
toast({
title: 'An unknown error occurred',
description:
'We encountered an unknown error while attempting to sign you Up. Please try again later.',
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to sign you Up. Please try again later.`,
),
variant: 'destructive',
});
}
@ -187,9 +194,10 @@ export const SignUpFormV2 = ({
await signIn('oidc', { callbackUrl: SIGN_UP_REDIRECT_PATH });
} catch (err) {
toast({
title: 'An unknown error occurred',
description:
'We encountered an unknown error while attempting to sign you Up. Please try again later.',
title: _(msg`An unknown error occurred`),
description: _(
msg`We encountered an unknown error while attempting to sign you Up. Please try again later.`,
),
variant: 'destructive',
});
}
@ -211,7 +219,7 @@ export const SignUpFormV2 = ({
<div className="relative flex h-full w-full flex-col items-center justify-evenly">
<div className="bg-background rounded-2xl border px-4 py-1 text-sm font-medium">
User profiles are here!
<Trans>User profiles are here!</Trans>
</div>
<AnimatePresence>
@ -240,22 +248,30 @@ export const SignUpFormV2 = ({
<div className="border-border dark:bg-background relative z-10 flex min-h-[min(850px,80vh)] w-full max-w-lg flex-col rounded-xl border bg-neutral-100 p-6">
{step === 'BASIC_DETAILS' && (
<div className="h-20">
<h1 className="text-xl font-semibold md:text-2xl">Create a new account</h1>
<h1 className="text-xl font-semibold md:text-2xl">
<Trans>Create a new account</Trans>
</h1>
<p className="text-muted-foreground mt-2 text-xs md:text-sm">
Create your account and start using state-of-the-art document signing. Open and
beautiful signing is within your grasp.
<Trans>
Create your account and start using state-of-the-art document signing. Open and
beautiful signing is within your grasp.
</Trans>
</p>
</div>
)}
{step === 'CLAIM_USERNAME' && (
<div className="h-20">
<h1 className="text-xl font-semibold md:text-2xl">Claim your username now</h1>
<h1 className="text-xl font-semibold md:text-2xl">
<Trans>Claim your username now</Trans>
</h1>
<p className="text-muted-foreground mt-2 text-xs md:text-sm">
You will get notified & be able to set up your documenso public profile when we launch
the feature.
<Trans>
You will get notified & be able to set up your documenso public profile when we
launch the feature.
</Trans>
</p>
</div>
)}
@ -280,7 +296,9 @@ export const SignUpFormV2 = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Full Name</FormLabel>
<FormLabel>
<Trans>Full Name</Trans>
</FormLabel>
<FormControl>
<Input type="text" {...field} />
</FormControl>
@ -294,7 +312,9 @@ export const SignUpFormV2 = ({
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email Address</FormLabel>
<FormLabel>
<Trans>Email Address</Trans>
</FormLabel>
<FormControl>
<Input type="email" {...field} />
</FormControl>
@ -308,7 +328,9 @@ export const SignUpFormV2 = ({
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormLabel>
<Trans>Password</Trans>
</FormLabel>
<FormControl>
<PasswordInput {...field} />
@ -324,7 +346,9 @@ export const SignUpFormV2 = ({
name="signature"
render={({ field: { onChange } }) => (
<FormItem>
<FormLabel>Sign Here</FormLabel>
<FormLabel>
<Trans>Sign Here</Trans>
</FormLabel>
<FormControl>
<SignaturePad
className="h-36 w-full"
@ -343,7 +367,9 @@ export const SignUpFormV2 = ({
<>
<div className="relative flex items-center justify-center gap-x-4 py-2 text-xs uppercase">
<div className="bg-border h-px flex-1" />
<span className="text-muted-foreground bg-transparent">Or</span>
<span className="text-muted-foreground bg-transparent">
<Trans>Or</Trans>
</span>
<div className="bg-border h-px flex-1" />
</div>
</>
@ -360,7 +386,7 @@ export const SignUpFormV2 = ({
onClick={onSignUpWithGoogleClick}
>
<FcGoogle className="mr-2 h-5 w-5" />
Sign Up with Google
<Trans>Sign Up with Google</Trans>
</Button>
</>
)}
@ -376,16 +402,21 @@ export const SignUpFormV2 = ({
onClick={onSignUpWithOIDCClick}
>
<FaIdCardClip className="mr-2 h-5 w-5" />
Sign Up with OIDC
<Trans>Sign Up with OIDC</Trans>
</Button>
</>
)}
<p className="text-muted-foreground mt-4 text-sm">
Already have an account?{' '}
<Link href="/signin" className="text-documenso-700 duration-200 hover:opacity-70">
Sign in instead
</Link>
<Trans>
Already have an account?{' '}
<Link
href="/signin"
className="text-documenso-700 duration-200 hover:opacity-70"
>
Sign in instead
</Link>
</Trans>
</p>
</fieldset>
)}
@ -403,7 +434,9 @@ export const SignUpFormV2 = ({
name="url"
render={({ field }) => (
<FormItem>
<FormLabel>Public profile username</FormLabel>
<FormLabel>
<Trans>Public profile username</Trans>
</FormLabel>
<FormControl>
<Input type="text" className="mb-2 mt-2 lowercase" {...field} />
@ -423,13 +456,19 @@ export const SignUpFormV2 = ({
<div className="mt-6">
{step === 'BASIC_DETAILS' && (
<p className="text-muted-foreground text-sm">
<span className="font-medium">Basic details</span> 1/2
<span className="font-medium">
<Trans>Basic details</Trans>
</span>{' '}
1/2
</p>
)}
{step === 'CLAIM_USERNAME' && (
<p className="text-muted-foreground text-sm">
<span className="font-medium">Claim username</span> 2/2
<span className="font-medium">
<Trans>Claim username</Trans>
</span>{' '}
2/2
</p>
)}
@ -455,7 +494,7 @@ export const SignUpFormV2 = ({
disabled={step === 'BASIC_DETAILS' || form.formState.isSubmitting}
onClick={() => setStep('BASIC_DETAILS')}
>
Back
<Trans>Back</Trans>
</Button>
{/* Continue button */}
@ -467,7 +506,7 @@ export const SignUpFormV2 = ({
loading={form.formState.isSubmitting}
onClick={onNextClick}
>
Next
<Trans>Next</Trans>
</Button>
)}
@ -480,7 +519,7 @@ export const SignUpFormV2 = ({
size="lg"
className="flex-1"
>
Complete
<Trans>Complete</Trans>
</Button>
)}
</div>