mirror of
https://github.com/documenso/documenso.git
synced 2025-11-17 10:11:35 +10:00
feat: web i18n (#1286)
This commit is contained in:
@ -2,6 +2,9 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard';
|
||||
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
@ -21,6 +24,8 @@ export type AvatarWithRecipientProps = {
|
||||
|
||||
export function AvatarWithRecipient({ recipient, documentStatus }: AvatarWithRecipientProps) {
|
||||
const [, copy] = useCopyToClipboard();
|
||||
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const signingToken = documentStatus === DocumentStatus.PENDING ? recipient.token : null;
|
||||
@ -32,8 +37,8 @@ export function AvatarWithRecipient({ recipient, documentStatus }: AvatarWithRec
|
||||
|
||||
void copy(`${NEXT_PUBLIC_WEBAPP_URL()}/sign/${signingToken}`).then(() => {
|
||||
toast({
|
||||
title: 'Copied to clipboard',
|
||||
description: 'The signing link has been copied to your clipboard.',
|
||||
title: _(msg`Copied to clipboard`),
|
||||
description: _(msg`The signing link has been copied to your clipboard.`),
|
||||
});
|
||||
});
|
||||
};
|
||||
@ -44,7 +49,7 @@ export function AvatarWithRecipient({ recipient, documentStatus }: AvatarWithRec
|
||||
'cursor-pointer hover:underline': signingToken,
|
||||
})}
|
||||
role={signingToken ? 'button' : undefined}
|
||||
title={signingToken ? 'Click to copy signing link for sending to recipient' : undefined}
|
||||
title={signingToken ? _(msg`Click to copy signing link for sending to recipient`) : undefined}
|
||||
onClick={onRecipientClick}
|
||||
>
|
||||
<StackAvatar
|
||||
@ -56,11 +61,13 @@ export function AvatarWithRecipient({ recipient, documentStatus }: AvatarWithRec
|
||||
|
||||
<div
|
||||
className="text-muted-foreground text-sm"
|
||||
title={signingToken ? 'Click to copy signing link for sending to recipient' : undefined}
|
||||
title={
|
||||
signingToken ? _(msg`Click to copy signing link for sending to recipient`) : undefined
|
||||
}
|
||||
>
|
||||
<p>{recipient.email}</p>
|
||||
<p className="text-muted-foreground/70 text-xs">
|
||||
{RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName}
|
||||
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
'use client';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
|
||||
import { getRecipientType } from '@documenso/lib/client-only/recipient-type';
|
||||
import { RECIPIENT_ROLES_DESCRIPTION } from '@documenso/lib/constants/recipient-roles';
|
||||
import { recipientAbbreviation } from '@documenso/lib/utils/recipient-formatter';
|
||||
@ -23,6 +26,8 @@ export const StackAvatarsWithTooltip = ({
|
||||
position,
|
||||
children,
|
||||
}: StackAvatarsWithTooltipProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const waitingRecipients = recipients.filter(
|
||||
(recipient) => getRecipientType(recipient) === 'waiting',
|
||||
);
|
||||
@ -49,7 +54,9 @@ export const StackAvatarsWithTooltip = ({
|
||||
>
|
||||
{completedRecipients.length > 0 && (
|
||||
<div>
|
||||
<h1 className="text-base font-medium">Completed</h1>
|
||||
<h1 className="text-base font-medium">
|
||||
<Trans>Completed</Trans>
|
||||
</h1>
|
||||
{completedRecipients.map((recipient: Recipient) => (
|
||||
<div key={recipient.id} className="my-1 flex items-center gap-2">
|
||||
<StackAvatar
|
||||
@ -61,7 +68,7 @@ export const StackAvatarsWithTooltip = ({
|
||||
<div className="">
|
||||
<p className="text-muted-foreground text-sm">{recipient.email}</p>
|
||||
<p className="text-muted-foreground/70 text-xs">
|
||||
{RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName}
|
||||
{_(RECIPIENT_ROLES_DESCRIPTION[recipient.role].roleName)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -71,7 +78,9 @@ export const StackAvatarsWithTooltip = ({
|
||||
|
||||
{waitingRecipients.length > 0 && (
|
||||
<div>
|
||||
<h1 className="text-base font-medium">Waiting</h1>
|
||||
<h1 className="text-base font-medium">
|
||||
<Trans>Waiting</Trans>
|
||||
</h1>
|
||||
{waitingRecipients.map((recipient: Recipient) => (
|
||||
<AvatarWithRecipient
|
||||
key={recipient.id}
|
||||
@ -84,7 +93,9 @@ export const StackAvatarsWithTooltip = ({
|
||||
|
||||
{openedRecipients.length > 0 && (
|
||||
<div>
|
||||
<h1 className="text-base font-medium">Opened</h1>
|
||||
<h1 className="text-base font-medium">
|
||||
<Trans>Opened</Trans>
|
||||
</h1>
|
||||
{openedRecipients.map((recipient: Recipient) => (
|
||||
<AvatarWithRecipient
|
||||
key={recipient.id}
|
||||
@ -97,7 +108,9 @@ export const StackAvatarsWithTooltip = ({
|
||||
|
||||
{uncompletedRecipients.length > 0 && (
|
||||
<div>
|
||||
<h1 className="text-base font-medium">Uncompleted</h1>
|
||||
<h1 className="text-base font-medium">
|
||||
<Trans>Uncompleted</Trans>
|
||||
</h1>
|
||||
{uncompletedRecipients.map((recipient: Recipient) => (
|
||||
<AvatarWithRecipient
|
||||
key={recipient.id}
|
||||
|
||||
@ -4,6 +4,9 @@ import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Loader, Monitor, Moon, Sun } from 'lucide-react';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
@ -31,22 +34,22 @@ import { THEMES_TYPE } from '@documenso/ui/primitives/constants';
|
||||
|
||||
const DOCUMENTS_PAGES = [
|
||||
{
|
||||
label: 'All documents',
|
||||
label: msg`All documents`,
|
||||
path: '/documents?status=ALL',
|
||||
shortcut: DOCUMENTS_PAGE_SHORTCUT.replace('+', ''),
|
||||
},
|
||||
{ label: 'Draft documents', path: '/documents?status=DRAFT' },
|
||||
{ label: msg`Draft documents`, path: '/documents?status=DRAFT' },
|
||||
{
|
||||
label: 'Completed documents',
|
||||
label: msg`Completed documents`,
|
||||
path: '/documents?status=COMPLETED',
|
||||
},
|
||||
{ label: 'Pending documents', path: '/documents?status=PENDING' },
|
||||
{ label: 'Inbox documents', path: '/documents?status=INBOX' },
|
||||
{ label: msg`Pending documents`, path: '/documents?status=PENDING' },
|
||||
{ label: msg`Inbox documents`, path: '/documents?status=INBOX' },
|
||||
];
|
||||
|
||||
const TEMPLATES_PAGES = [
|
||||
{
|
||||
label: 'All templates',
|
||||
label: msg`All templates`,
|
||||
path: '/templates',
|
||||
shortcut: TEMPLATES_PAGE_SHORTCUT.replace('+', ''),
|
||||
},
|
||||
@ -54,12 +57,12 @@ const TEMPLATES_PAGES = [
|
||||
|
||||
const SETTINGS_PAGES = [
|
||||
{
|
||||
label: 'Settings',
|
||||
label: msg`Settings`,
|
||||
path: '/settings',
|
||||
shortcut: SETTINGS_PAGE_SHORTCUT.replace('+', ''),
|
||||
},
|
||||
{ label: 'Profile', path: '/settings/profile' },
|
||||
{ label: 'Password', path: '/settings/password' },
|
||||
{ label: msg`Profile`, path: '/settings/profile' },
|
||||
{ label: msg`Password`, path: '/settings/password' },
|
||||
];
|
||||
|
||||
export type CommandMenuProps = {
|
||||
@ -68,6 +71,7 @@ export type CommandMenuProps = {
|
||||
};
|
||||
|
||||
export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
|
||||
const { _ } = useLingui();
|
||||
const { setTheme } = useTheme();
|
||||
|
||||
const router = useRouter();
|
||||
@ -174,7 +178,7 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
|
||||
<CommandInput
|
||||
value={search}
|
||||
onValueChange={setSearch}
|
||||
placeholder="Type a command or search..."
|
||||
placeholder={_(msg`Type a command or search...`)}
|
||||
/>
|
||||
|
||||
<CommandList>
|
||||
@ -187,26 +191,28 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) {
|
||||
</div>
|
||||
</CommandEmpty>
|
||||
) : (
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandEmpty>
|
||||
<Trans>No results found.</Trans>
|
||||
</CommandEmpty>
|
||||
)}
|
||||
{!currentPage && (
|
||||
<>
|
||||
<CommandGroup className="mx-2 p-0 pb-2" heading="Documents">
|
||||
<CommandGroup className="mx-2 p-0 pb-2" heading={_(msg`Documents`)}>
|
||||
<Commands push={push} pages={DOCUMENTS_PAGES} />
|
||||
</CommandGroup>
|
||||
<CommandGroup className="mx-2 p-0 pb-2" heading="Templates">
|
||||
<CommandGroup className="mx-2 p-0 pb-2" heading={_(msg`Templates`)}>
|
||||
<Commands push={push} pages={TEMPLATES_PAGES} />
|
||||
</CommandGroup>
|
||||
<CommandGroup className="mx-2 p-0 pb-2" heading="Settings">
|
||||
<CommandGroup className="mx-2 p-0 pb-2" heading={_(msg`Settings`)}>
|
||||
<Commands push={push} pages={SETTINGS_PAGES} />
|
||||
</CommandGroup>
|
||||
<CommandGroup className="mx-2 p-0 pb-2" heading="Preferences">
|
||||
<CommandGroup className="mx-2 p-0 pb-2" heading={_(msg`Preferences`)}>
|
||||
<CommandItem className="-mx-2 -my-1 rounded-lg" onSelect={() => addPage('theme')}>
|
||||
Change theme
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
{searchResults.length > 0 && (
|
||||
<CommandGroup className="mx-2 p-0 pb-2" heading="Your documents">
|
||||
<CommandGroup className="mx-2 p-0 pb-2" heading={_(msg`Your documents`)}>
|
||||
<Commands push={push} pages={searchResults} />
|
||||
</CommandGroup>
|
||||
)}
|
||||
@ -223,27 +229,31 @@ const Commands = ({
|
||||
pages,
|
||||
}: {
|
||||
push: (_path: string) => void;
|
||||
pages: { label: string; path: string; shortcut?: string; value?: string }[];
|
||||
pages: { label: MessageDescriptor | string; path: string; shortcut?: string; value?: string }[];
|
||||
}) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
return pages.map((page, idx) => (
|
||||
<CommandItem
|
||||
className="-mx-2 -my-1 rounded-lg"
|
||||
key={page.path + idx}
|
||||
value={page.value ?? page.label}
|
||||
value={page.value ?? (typeof page.label === 'string' ? page.label : _(page.label))}
|
||||
onSelect={() => push(page.path)}
|
||||
>
|
||||
{page.label}
|
||||
{typeof page.label === 'string' ? page.label : _(page.label)}
|
||||
{page.shortcut && <CommandShortcut>{page.shortcut}</CommandShortcut>}
|
||||
</CommandItem>
|
||||
));
|
||||
};
|
||||
|
||||
const ThemeCommands = ({ setTheme }: { setTheme: (_theme: string) => void }) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const THEMES = useMemo(
|
||||
() => [
|
||||
{ label: 'Light Mode', theme: THEMES_TYPE.LIGHT, icon: Sun },
|
||||
{ label: 'Dark Mode', theme: THEMES_TYPE.DARK, icon: Moon },
|
||||
{ label: 'System Theme', theme: THEMES_TYPE.SYSTEM, icon: Monitor },
|
||||
{ label: msg`Light Mode`, theme: THEMES_TYPE.LIGHT, icon: Sun },
|
||||
{ label: msg`Dark Mode`, theme: THEMES_TYPE.DARK, icon: Moon },
|
||||
{ label: msg`System Theme`, theme: THEMES_TYPE.SYSTEM, icon: Monitor },
|
||||
],
|
||||
[],
|
||||
);
|
||||
@ -255,7 +265,7 @@ const ThemeCommands = ({ setTheme }: { setTheme: (_theme: string) => void }) =>
|
||||
className="-my-1 mx-2 rounded-lg first:mt-2 last:mb-2"
|
||||
>
|
||||
<theme.icon className="mr-2" />
|
||||
{theme.label}
|
||||
{_(theme.label)}
|
||||
</CommandItem>
|
||||
));
|
||||
};
|
||||
|
||||
@ -4,6 +4,8 @@ import { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useParams, usePathname } from 'next/navigation';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Search } from 'lucide-react';
|
||||
|
||||
import { getRootHref } from '@documenso/lib/utils/params';
|
||||
@ -13,11 +15,11 @@ import { Button } from '@documenso/ui/primitives/button';
|
||||
const navigationLinks = [
|
||||
{
|
||||
href: '/documents',
|
||||
label: 'Documents',
|
||||
label: msg`Documents`,
|
||||
},
|
||||
{
|
||||
href: '/templates',
|
||||
label: 'Templates',
|
||||
label: msg`Templates`,
|
||||
},
|
||||
];
|
||||
|
||||
@ -26,6 +28,8 @@ export type DesktopNavProps = HTMLAttributes<HTMLDivElement> & {
|
||||
};
|
||||
|
||||
export const DesktopNav = ({ className, setIsCommandMenuOpen, ...props }: DesktopNavProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const pathname = usePathname();
|
||||
const params = useParams();
|
||||
|
||||
@ -62,7 +66,7 @@ export const DesktopNav = ({ className, setIsCommandMenuOpen, ...props }: Deskto
|
||||
},
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
{_(label)}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
@ -74,7 +78,7 @@ export const DesktopNav = ({ className, setIsCommandMenuOpen, ...props }: Deskto
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<Search className="mr-2 h-5 w-5" />
|
||||
Search
|
||||
<Trans>Search</Trans>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { CheckCircle2, ChevronsUpDown, Plus, Settings2 } from 'lucide-react';
|
||||
import { signOut } from 'next-auth/react';
|
||||
@ -35,6 +37,8 @@ export type MenuSwitcherProps = {
|
||||
};
|
||||
|
||||
export const MenuSwitcher = ({ user, teams: initialTeamsData }: MenuSwitcherProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const pathname = usePathname();
|
||||
|
||||
const isUserAdmin = isAdmin(user);
|
||||
@ -65,14 +69,14 @@ export const MenuSwitcher = ({ user, teams: initialTeamsData }: MenuSwitcherProp
|
||||
|
||||
const formatSecondaryAvatarText = (team?: typeof selectedTeam) => {
|
||||
if (!team) {
|
||||
return 'Personal Account';
|
||||
return _(msg`Personal Account`);
|
||||
}
|
||||
|
||||
if (team.ownerUserId === user.id) {
|
||||
return 'Owner';
|
||||
return _(msg`Owner`);
|
||||
}
|
||||
|
||||
return TEAM_MEMBER_ROLE_MAP[team.currentTeamMember.role];
|
||||
return _(TEAM_MEMBER_ROLE_MAP[team.currentTeamMember.role]);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -121,7 +125,9 @@ export const MenuSwitcher = ({ user, teams: initialTeamsData }: MenuSwitcherProp
|
||||
>
|
||||
{teams ? (
|
||||
<>
|
||||
<DropdownMenuLabel>Personal</DropdownMenuLabel>
|
||||
<DropdownMenuLabel>
|
||||
<Trans>Personal</Trans>
|
||||
</DropdownMenuLabel>
|
||||
|
||||
<DropdownMenuItem asChild>
|
||||
<Link href={formatRedirectUrlOnSwitch()}>
|
||||
@ -147,12 +153,14 @@ export const MenuSwitcher = ({ user, teams: initialTeamsData }: MenuSwitcherProp
|
||||
|
||||
<DropdownMenuLabel>
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<p>Teams</p>
|
||||
<p>
|
||||
<Trans>Teams</Trans>
|
||||
</p>
|
||||
|
||||
<div className="flex flex-row space-x-2">
|
||||
<DropdownMenuItem asChild>
|
||||
<Button
|
||||
title="Manage teams"
|
||||
title={_(msg`Manage teams`)}
|
||||
variant="ghost"
|
||||
className="text-muted-foreground flex h-5 w-5 items-center justify-center p-0"
|
||||
asChild
|
||||
@ -165,7 +173,7 @@ export const MenuSwitcher = ({ user, teams: initialTeamsData }: MenuSwitcherProp
|
||||
|
||||
<DropdownMenuItem asChild>
|
||||
<Button
|
||||
title="Create team"
|
||||
title={_(msg`Create team`)}
|
||||
variant="ghost"
|
||||
className="text-muted-foreground flex h-5 w-5 items-center justify-center p-0"
|
||||
asChild
|
||||
@ -235,7 +243,7 @@ export const MenuSwitcher = ({ user, teams: initialTeamsData }: MenuSwitcherProp
|
||||
href="/settings/teams?action=add-team"
|
||||
className="flex items-center justify-between"
|
||||
>
|
||||
Create team
|
||||
<Trans>Create team</Trans>
|
||||
<Plus className="ml-2 h-4 w-4" />
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
@ -245,18 +253,24 @@ export const MenuSwitcher = ({ user, teams: initialTeamsData }: MenuSwitcherProp
|
||||
|
||||
{isUserAdmin && (
|
||||
<DropdownMenuItem className="text-muted-foreground px-4 py-2" asChild>
|
||||
<Link href="/admin">Admin panel</Link>
|
||||
<Link href="/admin">
|
||||
<Trans>Admin panel</Trans>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
<DropdownMenuItem className="text-muted-foreground px-4 py-2" asChild>
|
||||
<Link href="/settings/profile">User settings</Link>
|
||||
<Link href="/settings/profile">
|
||||
<Trans>User settings</Trans>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
|
||||
{selectedTeam &&
|
||||
canExecuteTeamAction('MANAGE_TEAM', selectedTeam.currentTeamMember.role) && (
|
||||
<DropdownMenuItem className="text-muted-foreground px-4 py-2" asChild>
|
||||
<Link href={`/t/${selectedTeam.url}/settings/`}>Team settings</Link>
|
||||
<Link href={`/t/${selectedTeam.url}/settings/`}>
|
||||
<Trans>Team settings</Trans>
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
|
||||
@ -268,7 +282,7 @@ export const MenuSwitcher = ({ user, teams: initialTeamsData }: MenuSwitcherProp
|
||||
})
|
||||
}
|
||||
>
|
||||
Sign Out
|
||||
<Trans>Sign Out</Trans>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@ -4,6 +4,8 @@ import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { useParams } from 'next/navigation';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { signOut } from 'next-auth/react';
|
||||
|
||||
import LogoImage from '@documenso/assets/logo.png';
|
||||
@ -17,6 +19,8 @@ export type MobileNavigationProps = {
|
||||
};
|
||||
|
||||
export const MobileNavigation = ({ isMenuOpen, onMenuOpenChange }: MobileNavigationProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const params = useParams();
|
||||
|
||||
const handleMenuItemClick = () => {
|
||||
@ -28,19 +32,19 @@ export const MobileNavigation = ({ isMenuOpen, onMenuOpenChange }: MobileNavigat
|
||||
const menuNavigationLinks = [
|
||||
{
|
||||
href: `${rootHref}/documents`,
|
||||
text: 'Documents',
|
||||
text: msg`Documents`,
|
||||
},
|
||||
{
|
||||
href: `${rootHref}/templates`,
|
||||
text: 'Templates',
|
||||
text: msg`Templates`,
|
||||
},
|
||||
{
|
||||
href: '/settings/teams',
|
||||
text: 'Teams',
|
||||
text: msg`Teams`,
|
||||
},
|
||||
{
|
||||
href: '/settings/profile',
|
||||
text: 'Settings',
|
||||
text: msg`Settings`,
|
||||
},
|
||||
];
|
||||
|
||||
@ -65,7 +69,7 @@ export const MobileNavigation = ({ isMenuOpen, onMenuOpenChange }: MobileNavigat
|
||||
href={href}
|
||||
onClick={() => handleMenuItemClick()}
|
||||
>
|
||||
{text}
|
||||
{_(text)}
|
||||
</Link>
|
||||
))}
|
||||
|
||||
@ -77,7 +81,7 @@ export const MobileNavigation = ({ isMenuOpen, onMenuOpenChange }: MobileNavigat
|
||||
})
|
||||
}
|
||||
>
|
||||
Sign Out
|
||||
<Trans>Sign Out</Trans>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { AlertTriangle } from 'lucide-react';
|
||||
|
||||
import { ONE_DAY, ONE_SECOND } from '@documenso/lib/constants/time';
|
||||
@ -22,7 +24,9 @@ export type VerifyEmailBannerProps = {
|
||||
const RESEND_CONFIRMATION_EMAIL_TIMEOUT = 20 * ONE_SECOND;
|
||||
|
||||
export const VerifyEmailBanner = ({ email }: VerifyEmailBannerProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const [isButtonDisabled, setIsButtonDisabled] = useState(false);
|
||||
@ -37,8 +41,8 @@ export const VerifyEmailBanner = ({ email }: VerifyEmailBannerProps) => {
|
||||
await sendConfirmationEmail({ email: email });
|
||||
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Verification email sent successfully.',
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`Verification email sent successfully.`),
|
||||
});
|
||||
|
||||
setIsOpen(false);
|
||||
@ -47,8 +51,8 @@ export const VerifyEmailBanner = ({ email }: VerifyEmailBannerProps) => {
|
||||
setIsButtonDisabled(false);
|
||||
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'Something went wrong while sending the confirmation email.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`Something went wrong while sending the confirmation email.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -81,7 +85,7 @@ export const VerifyEmailBanner = ({ email }: VerifyEmailBannerProps) => {
|
||||
<div className="mx-auto flex max-w-screen-xl items-center justify-center gap-x-4 px-4 py-2 text-sm font-medium text-yellow-900">
|
||||
<div className="flex items-center">
|
||||
<AlertTriangle className="mr-2.5 h-5 w-5" />
|
||||
Verify your email address to unlock all features.
|
||||
<Trans>Verify your email address to unlock all features.</Trans>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@ -92,7 +96,11 @@ export const VerifyEmailBanner = ({ email }: VerifyEmailBannerProps) => {
|
||||
onClick={() => setIsOpen(true)}
|
||||
size="sm"
|
||||
>
|
||||
{isButtonDisabled ? 'Verification Email Sent' : 'Verify Now'}
|
||||
{isButtonDisabled ? (
|
||||
<Trans>Verification Email Sent</Trans>
|
||||
) : (
|
||||
<Trans>Verify Now</Trans>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@ -100,11 +108,15 @@ export const VerifyEmailBanner = ({ email }: VerifyEmailBannerProps) => {
|
||||
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogContent>
|
||||
<DialogTitle>Verify your email address</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Verify your email address</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription>
|
||||
We've sent a confirmation email to <strong>{email}</strong>. Please check your inbox and
|
||||
click the link in the email to verify your account.
|
||||
<Trans>
|
||||
We've sent a confirmation email to <strong>{email}</strong>. Please check your inbox
|
||||
and click the link in the email to verify your account.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
|
||||
<div>
|
||||
@ -113,7 +125,7 @@ export const VerifyEmailBanner = ({ email }: VerifyEmailBannerProps) => {
|
||||
loading={isLoading}
|
||||
onClick={onResendConfirmationEmail}
|
||||
>
|
||||
{isLoading ? 'Sending...' : 'Resend Confirmation Email'}
|
||||
{isLoading ? <Trans>Sending...</Trans> : <Trans>Resend Confirmation Email</Trans>}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@ -4,6 +4,8 @@ import { useMemo } from 'react';
|
||||
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@ -49,10 +51,18 @@ export const PeriodSelector = () => {
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent position="popper">
|
||||
<SelectItem value="all">All Time</SelectItem>
|
||||
<SelectItem value="7d">Last 7 days</SelectItem>
|
||||
<SelectItem value="14d">Last 14 days</SelectItem>
|
||||
<SelectItem value="30d">Last 30 days</SelectItem>
|
||||
<SelectItem value="all">
|
||||
<Trans>All Time</Trans>
|
||||
</SelectItem>
|
||||
<SelectItem value="7d">
|
||||
<Trans>Last 7 days</Trans>
|
||||
</SelectItem>
|
||||
<SelectItem value="14d">
|
||||
<Trans>Last 14 days</Trans>
|
||||
</SelectItem>
|
||||
<SelectItem value="30d">
|
||||
<Trans>Last 30 days</Trans>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
|
||||
export default function ActivityPageBackButton() {
|
||||
@ -15,7 +17,7 @@ export default function ActivityPageBackButton() {
|
||||
void router.back();
|
||||
}}
|
||||
>
|
||||
Back
|
||||
<Trans>Back</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -5,6 +5,7 @@ import type { HTMLAttributes } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { Braces, CreditCard, Globe2Icon, Lock, User, Users, Webhook } from 'lucide-react';
|
||||
|
||||
import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag';
|
||||
@ -32,7 +33,7 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
|
||||
)}
|
||||
>
|
||||
<User className="mr-2 h-5 w-5" />
|
||||
Profile
|
||||
<Trans>Profile</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -46,7 +47,7 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
|
||||
)}
|
||||
>
|
||||
<Globe2Icon className="mr-2 h-5 w-5" />
|
||||
Public Profile
|
||||
<Trans>Public Profile</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
@ -60,7 +61,7 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
|
||||
)}
|
||||
>
|
||||
<Users className="mr-2 h-5 w-5" />
|
||||
Teams
|
||||
<Trans>Teams</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -73,7 +74,7 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
|
||||
)}
|
||||
>
|
||||
<Lock className="mr-2 h-5 w-5" />
|
||||
Security
|
||||
<Trans>Security</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -86,7 +87,7 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
|
||||
)}
|
||||
>
|
||||
<Braces className="mr-2 h-5 w-5" />
|
||||
API Tokens
|
||||
<Trans>API Tokens</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -99,7 +100,7 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
|
||||
)}
|
||||
>
|
||||
<Webhook className="mr-2 h-5 w-5" />
|
||||
Webhooks
|
||||
<Trans>Webhooks</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -113,7 +114,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 { usePathname } from 'next/navigation';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { Braces, CreditCard, Globe2Icon, Lock, User, Users, Webhook } from 'lucide-react';
|
||||
|
||||
import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag';
|
||||
@ -35,7 +36,7 @@ export const MobileNav = ({ className, ...props }: MobileNavProps) => {
|
||||
)}
|
||||
>
|
||||
<User className="mr-2 h-5 w-5" />
|
||||
Profile
|
||||
<Trans>Profile</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -49,7 +50,7 @@ export const MobileNav = ({ className, ...props }: MobileNavProps) => {
|
||||
)}
|
||||
>
|
||||
<Globe2Icon className="mr-2 h-5 w-5" />
|
||||
Public Profile
|
||||
<Trans>Public Profile</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
@ -63,7 +64,7 @@ export const MobileNav = ({ className, ...props }: MobileNavProps) => {
|
||||
)}
|
||||
>
|
||||
<Users className="mr-2 h-5 w-5" />
|
||||
Teams
|
||||
<Trans>Teams</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -76,7 +77,7 @@ export const MobileNav = ({ className, ...props }: MobileNavProps) => {
|
||||
)}
|
||||
>
|
||||
<Lock className="mr-2 h-5 w-5" />
|
||||
Security
|
||||
<Trans>Security</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -89,7 +90,7 @@ export const MobileNav = ({ className, ...props }: MobileNavProps) => {
|
||||
)}
|
||||
>
|
||||
<Braces className="mr-2 h-5 w-5" />
|
||||
API Tokens
|
||||
<Trans>API Tokens</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -102,7 +103,7 @@ export const MobileNav = ({ className, ...props }: MobileNavProps) => {
|
||||
)}
|
||||
>
|
||||
<Webhook className="mr-2 h-5 w-5" />
|
||||
Webhooks
|
||||
<Trans>Webhooks</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
@ -116,7 +117,7 @@ export const MobileNav = ({ className, ...props }: MobileNavProps) => {
|
||||
)}
|
||||
>
|
||||
<CreditCard className="mr-2 h-5 w-5" />
|
||||
Billing
|
||||
<Trans>Billing</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -44,16 +46,18 @@ export default function DeleteTokenDialog({
|
||||
onDelete,
|
||||
children,
|
||||
}: DeleteTokenDialogProps) {
|
||||
const router = useRouter();
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const deleteMessage = `delete ${token.name}`;
|
||||
|
||||
const ZDeleteTokenDialogSchema = z.object({
|
||||
tokenName: z.literal(deleteMessage, {
|
||||
errorMap: () => ({ message: `You must enter '${deleteMessage}' to proceed` }),
|
||||
errorMap: () => ({ message: _(msg`You must enter '${deleteMessage}' to proceed`) }),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -80,8 +84,8 @@ export default function DeleteTokenDialog({
|
||||
});
|
||||
|
||||
toast({
|
||||
title: 'Token deleted',
|
||||
description: 'The token was deleted successfully.',
|
||||
title: _(msg`Token deleted`),
|
||||
description: _(msg`The token was deleted successfully.`),
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
@ -90,11 +94,12 @@ export default function DeleteTokenDialog({
|
||||
router.refresh();
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: 'An unknown error occurred',
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to delete this token. Please try again later.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
duration: 5000,
|
||||
description:
|
||||
'We encountered an unknown error while attempting to delete this token. Please try again later.',
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -113,18 +118,22 @@ export default function DeleteTokenDialog({
|
||||
<DialogTrigger asChild={true}>
|
||||
{children ?? (
|
||||
<Button className="mr-4" variant="destructive">
|
||||
Delete
|
||||
<Trans>Delete</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Are you sure you want to delete this token?</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Are you sure you want to delete this token?</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription>
|
||||
Please note that this action is irreversible. Once confirmed, your token will be
|
||||
permanently deleted.
|
||||
<Trans>
|
||||
Please note that this action is irreversible. Once confirmed, your token will be
|
||||
permanently deleted.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@ -140,10 +149,12 @@ export default function DeleteTokenDialog({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Confirm by typing:{' '}
|
||||
<span className="font-sm text-destructive font-semibold">
|
||||
{deleteMessage}
|
||||
</span>
|
||||
<Trans>
|
||||
Confirm by typing:{' '}
|
||||
<span className="font-sm text-destructive font-semibold">
|
||||
{deleteMessage}
|
||||
</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
@ -162,7 +173,7 @@ export default function DeleteTokenDialog({
|
||||
className="flex-1"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -172,7 +183,7 @@ export default function DeleteTokenDialog({
|
||||
disabled={!form.formState.isValid}
|
||||
loading={form.formState.isSubmitting}
|
||||
>
|
||||
I'm sure! Delete it
|
||||
<Trans>I'm sure! Delete it</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
|
||||
@ -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 type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import type { z } from 'zod';
|
||||
@ -48,9 +50,11 @@ export type CreateWebhookDialogProps = {
|
||||
} & Omit<DialogPrimitive.DialogProps, 'children'>;
|
||||
|
||||
export const CreateWebhookDialog = ({ trigger, ...props }: CreateWebhookDialogProps) => {
|
||||
const router = useRouter();
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const team = useOptionalCurrentTeam();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
@ -85,8 +89,8 @@ export const CreateWebhookDialog = ({ trigger, ...props }: CreateWebhookDialogPr
|
||||
setOpen(false);
|
||||
|
||||
toast({
|
||||
title: 'Webhook created',
|
||||
description: 'The webhook was successfully created.',
|
||||
title: _(msg`Webhook created`),
|
||||
description: _(msg`The webhook was successfully created.`),
|
||||
});
|
||||
|
||||
form.reset();
|
||||
@ -94,8 +98,8 @@ export const CreateWebhookDialog = ({ trigger, ...props }: CreateWebhookDialogPr
|
||||
router.refresh();
|
||||
} catch (err) {
|
||||
toast({
|
||||
title: 'Error',
|
||||
description: 'An error occurred while creating the webhook. Please try again.',
|
||||
title: _(msg`Error`),
|
||||
description: _(msg`An error occurred while creating the webhook. Please try again.`),
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@ -108,13 +112,21 @@ export const CreateWebhookDialog = ({ trigger, ...props }: CreateWebhookDialogPr
|
||||
{...props}
|
||||
>
|
||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild>
|
||||
{trigger ?? <Button className="flex-shrink-0">Create Webhook</Button>}
|
||||
{trigger ?? (
|
||||
<Button className="flex-shrink-0">
|
||||
<Trans>Create Webhook</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent className="max-w-lg" position="center">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create webhook</DialogTitle>
|
||||
<DialogDescription>On this page, you can create a new webhook.</DialogDescription>
|
||||
<DialogTitle>
|
||||
<Trans>Create webhook</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<Trans>On this page, you can create a new webhook.</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
@ -129,13 +141,15 @@ export const CreateWebhookDialog = ({ trigger, ...props }: CreateWebhookDialogPr
|
||||
name="webhookUrl"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormLabel required>Webhook URL</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Webhook URL</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input className="bg-background" {...field} />
|
||||
</FormControl>
|
||||
|
||||
<FormDescription>
|
||||
The URL for Documenso to send webhook events to.
|
||||
<Trans>The URL for Documenso to send webhook events to.</Trans>
|
||||
</FormDescription>
|
||||
|
||||
<FormMessage />
|
||||
@ -148,7 +162,9 @@ export const CreateWebhookDialog = ({ trigger, ...props }: CreateWebhookDialogPr
|
||||
name="enabled"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Enabled</FormLabel>
|
||||
<FormLabel>
|
||||
<Trans>Enabled</Trans>
|
||||
</FormLabel>
|
||||
|
||||
<div>
|
||||
<FormControl>
|
||||
@ -171,7 +187,9 @@ export const CreateWebhookDialog = ({ trigger, ...props }: CreateWebhookDialogPr
|
||||
name="eventTriggers"
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<FormItem className="flex flex-col gap-2">
|
||||
<FormLabel required>Triggers</FormLabel>
|
||||
<FormLabel required>
|
||||
<Trans>Triggers</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<TriggerMultiSelectCombobox
|
||||
listValues={value}
|
||||
@ -182,7 +200,7 @@ export const CreateWebhookDialog = ({ trigger, ...props }: CreateWebhookDialogPr
|
||||
</FormControl>
|
||||
|
||||
<FormDescription>
|
||||
The events that will trigger a webhook to be sent to your URL.
|
||||
<Trans>The events that will trigger a webhook to be sent to your URL.</Trans>
|
||||
</FormDescription>
|
||||
|
||||
<FormMessage />
|
||||
@ -195,7 +213,9 @@ export const CreateWebhookDialog = ({ trigger, ...props }: CreateWebhookDialogPr
|
||||
name="secret"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Secret</FormLabel>
|
||||
<FormLabel>
|
||||
<Trans>Secret</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<PasswordInput
|
||||
className="bg-background"
|
||||
@ -205,8 +225,11 @@ export const CreateWebhookDialog = ({ trigger, ...props }: CreateWebhookDialogPr
|
||||
</FormControl>
|
||||
|
||||
<FormDescription>
|
||||
A secret that will be sent to your URL so you can verify that the request has
|
||||
been sent by Documenso.
|
||||
<Trans>
|
||||
A secret that will be sent to your URL so you can verify that the request
|
||||
has been sent by Documenso
|
||||
</Trans>
|
||||
.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -216,10 +239,10 @@ export const CreateWebhookDialog = ({ trigger, ...props }: CreateWebhookDialogPr
|
||||
<DialogFooter>
|
||||
<div className="flex w-full flex-nowrap gap-4">
|
||||
<Button type="button" variant="secondary" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
<Button type="submit" loading={form.formState.isSubmitting}>
|
||||
Create
|
||||
<Trans>Create</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -40,9 +42,11 @@ export type DeleteWebhookDialogProps = {
|
||||
};
|
||||
|
||||
export const DeleteWebhookDialog = ({ webhook, children }: DeleteWebhookDialogProps) => {
|
||||
const router = useRouter();
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const team = useOptionalCurrentTeam();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
@ -51,7 +55,7 @@ export const DeleteWebhookDialog = ({ webhook, children }: DeleteWebhookDialogPr
|
||||
|
||||
const ZDeleteWebhookFormSchema = z.object({
|
||||
webhookUrl: z.literal(deleteMessage, {
|
||||
errorMap: () => ({ message: `You must enter '${deleteMessage}' to proceed` }),
|
||||
errorMap: () => ({ message: _(msg`You must enter '${deleteMessage}' to proceed`) }),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -71,9 +75,9 @@ export const DeleteWebhookDialog = ({ webhook, children }: DeleteWebhookDialogPr
|
||||
await deleteWebhook({ id: webhook.id, teamId: team?.id });
|
||||
|
||||
toast({
|
||||
title: 'Webhook deleted',
|
||||
title: _(msg`Webhook deleted`),
|
||||
description: _(msg`The webhook has been successfully deleted.`),
|
||||
duration: 5000,
|
||||
description: 'The webhook has been successfully deleted.',
|
||||
});
|
||||
|
||||
setOpen(false);
|
||||
@ -81,11 +85,12 @@ export const DeleteWebhookDialog = ({ webhook, children }: DeleteWebhookDialogPr
|
||||
router.refresh();
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: 'An unknown error occurred',
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to delete it. Please try again later.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
duration: 5000,
|
||||
description:
|
||||
'We encountered an unknown error while attempting to delete it. Please try again later.',
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -101,18 +106,22 @@ export const DeleteWebhookDialog = ({ webhook, children }: DeleteWebhookDialogPr
|
||||
<DialogTrigger asChild>
|
||||
{children ?? (
|
||||
<Button className="mr-4" variant="destructive">
|
||||
Delete
|
||||
<Trans>Delete</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete Webhook</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Delete Webhook</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription>
|
||||
Please note that this action is irreversible. Once confirmed, your webhook will be
|
||||
permanently deleted.
|
||||
<Trans>
|
||||
Please note that this action is irreversible. Once confirmed, your webhook will be
|
||||
permanently deleted.
|
||||
</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@ -128,10 +137,12 @@ export const DeleteWebhookDialog = ({ webhook, children }: DeleteWebhookDialogPr
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Confirm by typing:{' '}
|
||||
<span className="font-sm text-destructive font-semibold">
|
||||
{deleteMessage}
|
||||
</span>
|
||||
<Trans>
|
||||
Confirm by typing:{' '}
|
||||
<span className="font-sm text-destructive font-semibold">
|
||||
{deleteMessage}
|
||||
</span>
|
||||
</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input className="bg-background" type="text" {...field} />
|
||||
@ -149,7 +160,7 @@ export const DeleteWebhookDialog = ({ webhook, children }: DeleteWebhookDialogPr
|
||||
className="flex-1"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@ -159,7 +170,7 @@ export const DeleteWebhookDialog = ({ webhook, children }: DeleteWebhookDialogPr
|
||||
disabled={!form.formState.isValid}
|
||||
loading={form.formState.isSubmitting}
|
||||
>
|
||||
I'm sure! Delete it
|
||||
<Trans>I'm sure! Delete it</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { Plural, Trans } from '@lingui/macro';
|
||||
import { WebhookTriggerEvents } from '@prisma/client/';
|
||||
import { Check, ChevronsUpDown } from 'lucide-react';
|
||||
|
||||
@ -60,7 +61,7 @@ export const TriggerMultiSelectCombobox = ({
|
||||
aria-expanded={isOpen}
|
||||
className="w-[200px] justify-between"
|
||||
>
|
||||
{selectedValues.length > 0 ? selectedValues.length + ' selected...' : 'Select values...'}
|
||||
<Plural value={selectedValues.length} zero="Select values" other="# selected..." />
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
@ -72,7 +73,9 @@ export const TriggerMultiSelectCombobox = ({
|
||||
15,
|
||||
)}
|
||||
/>
|
||||
<CommandEmpty>No value found.</CommandEmpty>
|
||||
<CommandEmpty>
|
||||
<Trans>No value found.</Trans>
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{allEvents.map((value: string, i: number) => (
|
||||
<CommandItem key={i} onSelect={() => handleSelect(value)}>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { ArrowRightIcon, Loader } from 'lucide-react';
|
||||
import { match } from 'ts-pattern';
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
@ -97,12 +98,18 @@ export const DocumentHistorySheet = ({
|
||||
className="flex w-full max-w-[500px] flex-col overflow-y-auto p-0"
|
||||
>
|
||||
<div className="text-foreground px-6 pt-6">
|
||||
<h1 className="text-lg font-medium">Document history</h1>
|
||||
<h1 className="text-lg font-medium">
|
||||
<Trans>Document history</Trans>
|
||||
</h1>
|
||||
<button
|
||||
className="text-muted-foreground text-sm"
|
||||
onClick={() => setIsUserDetailsVisible(!isUserDetailsVisible)}
|
||||
>
|
||||
{isUserDetailsVisible ? 'Hide' : 'Show'} additional information
|
||||
{isUserDetailsVisible ? (
|
||||
<Trans>Hide additional information</Trans>
|
||||
) : (
|
||||
<Trans>Show additional information</Trans>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -114,12 +121,14 @@ export const DocumentHistorySheet = ({
|
||||
|
||||
{isLoadingError && (
|
||||
<div className="flex h-full flex-col items-center justify-center">
|
||||
<p className="text-foreground/80 text-sm">Unable to load document history</p>
|
||||
<p className="text-foreground/80 text-sm">
|
||||
<Trans>Unable to load document history</Trans>
|
||||
</p>
|
||||
<button
|
||||
onClick={async () => refetch()}
|
||||
className="text-foreground/70 hover:text-muted-foreground mt-2 text-sm"
|
||||
>
|
||||
Click here to retry
|
||||
<Trans>Click here to retry</Trans>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import type { HTMLAttributes } from 'react';
|
||||
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { CheckCircle2, Clock, File } from 'lucide-react';
|
||||
import type { LucideIcon } from 'lucide-react/dist/lucide-react';
|
||||
|
||||
@ -8,34 +11,40 @@ import { SignatureIcon } from '@documenso/ui/icons/signature';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
|
||||
type FriendlyStatus = {
|
||||
label: string;
|
||||
label: MessageDescriptor;
|
||||
labelExtended: MessageDescriptor;
|
||||
icon?: LucideIcon;
|
||||
color: string;
|
||||
};
|
||||
|
||||
export const FRIENDLY_STATUS_MAP: Record<ExtendedDocumentStatus, FriendlyStatus> = {
|
||||
PENDING: {
|
||||
label: 'Pending',
|
||||
label: msg`Pending`,
|
||||
labelExtended: msg`Document pending`,
|
||||
icon: Clock,
|
||||
color: 'text-blue-600 dark:text-blue-300',
|
||||
},
|
||||
COMPLETED: {
|
||||
label: 'Completed',
|
||||
label: msg`Completed`,
|
||||
labelExtended: msg`Document completed`,
|
||||
icon: CheckCircle2,
|
||||
color: 'text-green-500 dark:text-green-300',
|
||||
},
|
||||
DRAFT: {
|
||||
label: 'Draft',
|
||||
label: msg`Draft`,
|
||||
labelExtended: msg`Document draft`,
|
||||
icon: File,
|
||||
color: 'text-yellow-500 dark:text-yellow-200',
|
||||
},
|
||||
INBOX: {
|
||||
label: 'Inbox',
|
||||
label: msg`Inbox`,
|
||||
labelExtended: msg`Document inbox`,
|
||||
icon: SignatureIcon,
|
||||
color: 'text-muted-foreground',
|
||||
},
|
||||
ALL: {
|
||||
label: 'All',
|
||||
label: msg`All`,
|
||||
labelExtended: msg`Document All`,
|
||||
color: 'text-muted-foreground',
|
||||
},
|
||||
};
|
||||
@ -51,6 +60,8 @@ export const DocumentStatus = ({
|
||||
inheritColor,
|
||||
...props
|
||||
}: DocumentStatusProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const { label, icon: Icon, color } = FRIENDLY_STATUS_MAP[status];
|
||||
|
||||
return (
|
||||
@ -62,7 +73,7 @@ export const DocumentStatus = ({
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{label}
|
||||
{_(label)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import type { HTMLAttributes } from 'react';
|
||||
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Globe2, Lock } from 'lucide-react';
|
||||
import type { LucideIcon } from 'lucide-react/dist/lucide-react';
|
||||
|
||||
@ -7,7 +10,7 @@ import type { TemplateType as TemplateTypePrisma } from '@documenso/prisma/clien
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
|
||||
type TemplateTypeIcon = {
|
||||
label: string;
|
||||
label: MessageDescriptor;
|
||||
icon?: LucideIcon;
|
||||
color: string;
|
||||
};
|
||||
@ -16,12 +19,12 @@ type TemplateTypes = (typeof TemplateTypePrisma)[keyof typeof TemplateTypePrisma
|
||||
|
||||
const TEMPLATE_TYPES: Record<TemplateTypes, TemplateTypeIcon> = {
|
||||
PRIVATE: {
|
||||
label: 'Private',
|
||||
label: msg`Private`,
|
||||
icon: Lock,
|
||||
color: 'text-blue-600 dark:text-blue-300',
|
||||
},
|
||||
PUBLIC: {
|
||||
label: 'Public',
|
||||
label: msg`Public`,
|
||||
icon: Globe2,
|
||||
color: 'text-green-500 dark:text-green-300',
|
||||
},
|
||||
@ -33,6 +36,8 @@ export type TemplateTypeProps = HTMLAttributes<HTMLSpanElement> & {
|
||||
};
|
||||
|
||||
export const TemplateType = ({ className, type, inheritColor, ...props }: TemplateTypeProps) => {
|
||||
const { _ } = useLingui();
|
||||
|
||||
const { label, icon: Icon, color } = TEMPLATE_TYPES[type];
|
||||
|
||||
return (
|
||||
@ -44,7 +49,7 @@ export const TemplateType = ({ className, type, inheritColor, ...props }: Templa
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
{label}
|
||||
{_(label)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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',
|
||||
});
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
import Image from 'next/image';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { motion } from 'framer-motion';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
|
||||
@ -40,12 +41,18 @@ export default function NotFoundPartial({ children }: NotFoundPartialProps) {
|
||||
|
||||
<div className="container mx-auto flex h-full min-h-screen items-center px-6 py-32">
|
||||
<div>
|
||||
<p className="text-muted-foreground font-semibold">404 Page not found</p>
|
||||
<p className="text-muted-foreground font-semibold">
|
||||
<Trans>404 Page not found</Trans>
|
||||
</p>
|
||||
|
||||
<h1 className="mt-3 text-2xl font-bold md:text-3xl">Oops! Something went wrong.</h1>
|
||||
<h1 className="mt-3 text-2xl font-bold md:text-3xl">
|
||||
<Trans>Oops! Something went wrong.</Trans>
|
||||
</h1>
|
||||
|
||||
<p className="text-muted-foreground mt-4 text-sm">
|
||||
The page you are looking for was moved, removed, renamed or might never have existed.
|
||||
<Trans>
|
||||
The page you are looking for was moved, removed, renamed or might never have existed.
|
||||
</Trans>
|
||||
</p>
|
||||
|
||||
<div className="mt-6 flex gap-x-2.5 gap-y-4 md:items-center">
|
||||
@ -57,7 +64,7 @@ export default function NotFoundPartial({ children }: NotFoundPartialProps) {
|
||||
}}
|
||||
>
|
||||
<ChevronLeft className="mr-2 h-4 w-4" />
|
||||
Go Back
|
||||
<Trans>Go Back</Trans>
|
||||
</Button>
|
||||
|
||||
{children}
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Plural, Trans, msg } from '@lingui/macro';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import type * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { CheckCircle2Icon, CircleIcon } from 'lucide-react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
@ -91,6 +93,7 @@ export const ManagePublicTemplateDialog = ({
|
||||
onIsOpenChange,
|
||||
...props
|
||||
}: ManagePublicTemplateDialogProps) => {
|
||||
const { _ } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const [open, onOpenChange] = useState(isOpen);
|
||||
@ -129,18 +132,19 @@ export const ManagePublicTemplateDialog = ({
|
||||
});
|
||||
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Template has been removed from your public profile.',
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`Template has been removed from your public profile.`),
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
handleOnOpenChange(false);
|
||||
} catch {
|
||||
toast({
|
||||
title: 'An unknown error occurred',
|
||||
title: _(msg`An unknown error occurred`),
|
||||
description: _(
|
||||
msg`We encountered an unknown error while attempting to remove this template from your profile. Please try again later.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
description:
|
||||
'We encountered an unknown error while attempting to remove this template from your profile. Please try again later.',
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -165,18 +169,19 @@ export const ManagePublicTemplateDialog = ({
|
||||
});
|
||||
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'Template has been updated.',
|
||||
title: _(msg`Success`),
|
||||
description: _(msg`Template has been updated.`),
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
onOpenChange(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 the template. Please try again later.`,
|
||||
),
|
||||
variant: 'destructive',
|
||||
description:
|
||||
'We encountered an unknown error while attempting to update the template. Please try again later.',
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -241,11 +246,22 @@ export const ManagePublicTemplateDialog = ({
|
||||
.with({ currentStep: 'SELECT_TEMPLATE' }, () => (
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{team?.name || 'Your'} direct signing templates</DialogTitle>
|
||||
<DialogTitle>
|
||||
{team?.name ? (
|
||||
<Trans>{team.name} direct signing templates</Trans>
|
||||
) : (
|
||||
<Trans>Your direct signing templates</Trans>
|
||||
)}
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription>
|
||||
Select a template you'd like to display on your {team && `team's`} public
|
||||
profile
|
||||
{team ? (
|
||||
<Trans>
|
||||
Select a template you'd like to display on your team's public profile
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans>Select a template you'd like to display on your public profile</Trans>
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
@ -253,8 +269,12 @@ export const ManagePublicTemplateDialog = ({
|
||||
<Table overflowHidden>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Template</TableHead>
|
||||
<TableHead>Created</TableHead>
|
||||
<TableHead>
|
||||
<Trans>Template</Trans>
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
<Trans>Created</Trans>
|
||||
</TableHead>
|
||||
<TableHead></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@ -262,7 +282,9 @@ export const ManagePublicTemplateDialog = ({
|
||||
{directTemplates.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={3} className="h-16 text-center">
|
||||
<p className="text-muted-foreground">No valid direct templates found</p>
|
||||
<p className="text-muted-foreground">
|
||||
<Trans>No valid direct templates found</Trans>
|
||||
</p>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
@ -296,7 +318,7 @@ export const ManagePublicTemplateDialog = ({
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button type="button" variant="secondary">
|
||||
Close
|
||||
<Trans>Close</Trans>
|
||||
</Button>
|
||||
</DialogClose>
|
||||
|
||||
@ -305,7 +327,7 @@ export const ManagePublicTemplateDialog = ({
|
||||
disabled={selectedTemplateId === null}
|
||||
onClick={() => onManageStep()}
|
||||
>
|
||||
Continue
|
||||
<Trans>Continue</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
@ -313,9 +335,13 @@ export const ManagePublicTemplateDialog = ({
|
||||
.with({ templateId: P.number, currentStep: 'MANAGE' }, () => (
|
||||
<DialogContent className="relative">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Configure template</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Configure template</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription>Manage details for this public template</DialogDescription>
|
||||
<DialogDescription>
|
||||
<Trans>Manage details for this public template</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
@ -330,7 +356,10 @@ export const ManagePublicTemplateDialog = ({
|
||||
<FormItem>
|
||||
<FormLabel required>Title</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="The public name for your template" {...field} />
|
||||
<Input
|
||||
placeholder={_(msg`The public name for your template`)}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -343,24 +372,34 @@ export const ManagePublicTemplateDialog = ({
|
||||
render={({ field }) => {
|
||||
const remaningLength =
|
||||
MAX_TEMPLATE_PUBLIC_DESCRIPTION_LENGTH - (field.value || '').length;
|
||||
const pluralWord =
|
||||
Math.abs(remaningLength) === 1 ? 'character' : 'characters';
|
||||
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel required>Description</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder="The public description that will be displayed with this template"
|
||||
placeholder={_(
|
||||
msg`The public description that will be displayed with this template`,
|
||||
)}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
{!form.formState.errors.publicDescription && (
|
||||
<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>
|
||||
)}
|
||||
|
||||
@ -377,16 +416,18 @@ export const ManagePublicTemplateDialog = ({
|
||||
className="mr-auto w-full sm:w-auto"
|
||||
onClick={() => setCurrentStep('CONFIRM_DISABLE')}
|
||||
>
|
||||
Disable
|
||||
<Trans>Disable</Trans>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<DialogClose asChild>
|
||||
<Button variant="secondary">Close</Button>
|
||||
<Button variant="secondary">
|
||||
<Trans>Close</Trans>
|
||||
</Button>
|
||||
</DialogClose>
|
||||
|
||||
<Button type="submit" loading={isUpdatingTemplateSettings}>
|
||||
Update
|
||||
<Trans>Update</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
@ -396,17 +437,19 @@ export const ManagePublicTemplateDialog = ({
|
||||
.with({ templateId: P.number, currentStep: 'CONFIRM_DISABLE' }, ({ templateId }) => (
|
||||
<DialogContent className="relative">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Are you sure?</DialogTitle>
|
||||
<DialogTitle>
|
||||
<Trans>Are you sure?</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription>
|
||||
The template will be removed from your profile
|
||||
<Trans>The template will be removed from your profile</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button type="button" variant="secondary">
|
||||
Cancel
|
||||
<Trans>Cancel</Trans>
|
||||
</Button>
|
||||
</DialogClose>
|
||||
|
||||
@ -416,7 +459,7 @@ export const ManagePublicTemplateDialog = ({
|
||||
loading={isUpdatingTemplateSettings}
|
||||
onClick={() => void setTemplateToPrivate(templateId)}
|
||||
>
|
||||
Confirm
|
||||
<Trans>Confirm</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { File, User2 } from 'lucide-react';
|
||||
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
|
||||
@ -71,7 +72,7 @@ export const UserProfileSkeleton = ({ className, user, rows = 2 }: UserProfileSk
|
||||
|
||||
<div className="flex-shrink-0">
|
||||
<Button type="button" size="sm" className="pointer-events-none w-32">
|
||||
Sign
|
||||
<Trans>Sign</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
import Image from 'next/image';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import { File } from 'lucide-react';
|
||||
|
||||
import timurImage from '@documenso/assets/images/timur.png';
|
||||
@ -44,17 +45,19 @@ export const UserProfileTimur = ({ className, rows = 2 }: UserProfileTimurProps)
|
||||
<VerifiedIcon className="text-primary h-8 w-8" />
|
||||
</div>
|
||||
|
||||
<p className="text-muted-foreground mt-4 max-w-[40ch] text-center text-sm">Hey I’m Timur</p>
|
||||
<p className="text-muted-foreground mt-4 max-w-[40ch] text-center text-sm">
|
||||
<Trans>Hey I’m Timur</Trans>
|
||||
</p>
|
||||
|
||||
<p className="text-muted-foreground mt-1 max-w-[40ch] text-center text-sm">
|
||||
Pick any of the following agreements below and start signing to get started
|
||||
<Trans>Pick any of the following agreements below and start signing to get started</Trans>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 w-full">
|
||||
<div className="dark:divide-foreground/30 dark:border-foreground/30 divide-y-2 divide-neutral-200 overflow-hidden rounded-lg border-2 border-neutral-200">
|
||||
<div className="text-muted-foreground dark:bg-foreground/20 bg-neutral-50 p-4 font-medium">
|
||||
Documents
|
||||
<Trans>Documents</Trans>
|
||||
</div>
|
||||
|
||||
{Array(rows)
|
||||
@ -75,7 +78,7 @@ export const UserProfileTimur = ({ className, rows = 2 }: UserProfileTimurProps)
|
||||
|
||||
<div className="flex-shrink-0">
|
||||
<Button type="button" size="sm" className="pointer-events-none w-32">
|
||||
Sign
|
||||
<Trans>Sign</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user