perf: add prefetch="intent" to navigation Link components

Enables React Router's intent-based prefetching on Link components
across the app, preloading route data and modules on hover/focus
for faster perceived navigation.
This commit is contained in:
ephraimduncan
2026-03-16 10:02:32 +00:00
parent 943a0b50e3
commit 807ad95354
48 changed files with 256 additions and 206 deletions
@@ -52,7 +52,7 @@ export const Header = ({ className, ...props }: HeaderProps) => {
return (
<header
className={cn(
'supports-backdrop-blur:bg-background/60 bg-background/95 sticky top-0 z-[60] flex h-16 w-full items-center border-b border-b-transparent backdrop-blur duration-200',
'supports-backdrop-blur:bg-background/60 sticky top-0 z-[60] flex h-16 w-full items-center border-b border-b-transparent bg-background/95 backdrop-blur duration-200',
scrollY > 5 && 'border-b-border',
className,
)}
@@ -60,8 +60,9 @@ export const Header = ({ className, ...props }: HeaderProps) => {
>
<div className="mx-auto flex w-full max-w-screen-xl items-center justify-between gap-x-4 px-4 md:justify-normal md:px-8">
<Link
prefetch="intent"
to={getRootHref(params)}
className="focus-visible:ring-ring ring-offset-background hidden rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 md:inline"
className="hidden rounded-md ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 md:inline"
>
<BrandingLogo className="h-6 w-auto" />
</Link>
@@ -69,11 +70,11 @@ export const Header = ({ className, ...props }: HeaderProps) => {
<AppNavDesktop setIsCommandMenuOpen={setIsCommandMenuOpen} />
<Button asChild variant="outline" className="relative hidden h-10 w-10 rounded-lg md:flex">
<Link to="/inbox" className="relative block h-10 w-10">
<InboxIcon className="text-muted-foreground hover:text-foreground h-5 w-5 flex-shrink-0 transition-colors" />
<Link prefetch="intent" to="/inbox" className="relative block h-10 w-10">
<InboxIcon className="h-5 w-5 flex-shrink-0 text-muted-foreground transition-colors hover:text-foreground" />
{unreadCountData && unreadCountData.count > 0 && (
<span className="bg-primary text-primary-foreground absolute -right-1.5 -top-1.5 flex h-5 w-5 items-center justify-center rounded-full text-[10px] font-semibold">
<span className="absolute -right-1.5 -top-1.5 flex h-5 w-5 items-center justify-center rounded-full bg-primary text-[10px] font-semibold text-primary-foreground">
{unreadCountData.count > 99 ? '99+' : unreadCountData.count}
</span>
)}
@@ -86,11 +87,11 @@ export const Header = ({ className, ...props }: HeaderProps) => {
<div className="flex flex-row items-center space-x-4 md:hidden">
<button onClick={() => setIsCommandMenuOpen(true)}>
<SearchIcon className="text-muted-foreground h-6 w-6" />
<SearchIcon className="h-6 w-6 text-muted-foreground" />
</button>
<button onClick={() => setIsHamburgerMenuOpen(true)}>
<MenuIcon className="text-muted-foreground h-6 w-6" />
<MenuIcon className="h-6 w-6 text-muted-foreground" />
</button>
<AppCommandMenu open={isCommandMenuOpen} onOpenChange={setIsCommandMenuOpen} />
@@ -83,10 +83,11 @@ export const AppNavDesktop = ({
>
{menuNavigationLinks.map(({ href, label }) => (
<Link
prefetch="intent"
key={href}
to={href}
className={cn(
'text-muted-foreground dark:text-muted-foreground/60 focus-visible:ring-ring ring-offset-background rounded-md font-medium leading-5 hover:opacity-80 focus-visible:outline-none focus-visible:ring-2',
'rounded-md font-medium leading-5 text-muted-foreground ring-offset-background hover:opacity-80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring dark:text-muted-foreground/60',
{
'text-foreground dark:text-muted-foreground': pathname?.startsWith(href),
},
@@ -102,7 +103,7 @@ export const AppNavDesktop = ({
<Button
variant="outline"
className="text-muted-foreground flex w-full max-w-96 items-center justify-between rounded-lg"
className="flex w-full max-w-96 items-center justify-between rounded-lg text-muted-foreground"
onClick={() => setIsCommandMenuOpen(true)}
>
<div className="flex items-center">
@@ -111,7 +112,7 @@ export const AppNavDesktop = ({
</div>
<div>
<div className="text-muted-foreground bg-muted flex items-center rounded-md px-1.5 py-0.5 text-xs tracking-wider">
<div className="flex items-center rounded-md bg-muted px-1.5 py-0.5 text-xs tracking-wider text-muted-foreground">
{modifierKey}+K
</div>
</div>
@@ -83,7 +83,7 @@ export const AppNavMobile = ({ isMenuOpen, onMenuOpenChange }: AppNavMobileProps
return (
<Sheet open={isMenuOpen} onOpenChange={onMenuOpenChange}>
<SheetContent className="flex w-full max-w-[350px] flex-col">
<Link to="/" onClick={handleMenuItemClick}>
<Link prefetch="intent" to="/" onClick={handleMenuItemClick}>
<img
src={LogoImage}
alt="Documenso Logo"
@@ -96,14 +96,15 @@ export const AppNavMobile = ({ isMenuOpen, onMenuOpenChange }: AppNavMobileProps
<div className="mt-8 flex w-full flex-col items-start gap-y-4">
{menuNavigationLinks.map(({ href, text }) => (
<Link
prefetch="intent"
key={href}
className="text-foreground hover:text-foreground/80 flex items-center gap-2 text-2xl font-semibold"
className="flex items-center gap-2 text-2xl font-semibold text-foreground hover:text-foreground/80"
to={href}
onClick={() => handleMenuItemClick()}
>
{text}
{href === '/inbox' && unreadCountData && unreadCountData.count > 0 && (
<span className="bg-primary text-primary-foreground flex h-6 min-w-[1.5rem] items-center justify-center rounded-full px-1.5 text-xs font-semibold">
<span className="flex h-6 min-w-[1.5rem] items-center justify-center rounded-full bg-primary px-1.5 text-xs font-semibold text-primary-foreground">
{unreadCountData.count > 99 ? '99+' : unreadCountData.count}
</span>
)}
@@ -111,7 +112,7 @@ export const AppNavMobile = ({ isMenuOpen, onMenuOpenChange }: AppNavMobileProps
))}
<button
className="text-foreground hover:text-foreground/80 text-2xl font-semibold"
className="text-2xl font-semibold text-foreground hover:text-foreground/80"
onClick={async () => authClient.signOut()}
>
<Trans>Sign Out</Trans>
@@ -123,7 +124,7 @@ export const AppNavMobile = ({ isMenuOpen, onMenuOpenChange }: AppNavMobileProps
<ThemeSwitcher />
</div>
<p className="text-muted-foreground text-sm">
<p className="text-sm text-muted-foreground">
© {new Date().getFullYear()} Documenso, Inc.
<br />
<Trans>All rights reserved.</Trans>
@@ -64,7 +64,7 @@ export const DocumentPageViewButton = ({ envelope }: DocumentPageViewButtonProps
))
.with({ isComplete: false }, () => (
<Button className="w-full" asChild>
<Link to={formatPath}>
<Link prefetch="intent" to={formatPath}>
<Trans>Edit</Trans>
</Link>
</Button>
@@ -69,7 +69,7 @@ export const DocumentPageViewDropdown = ({ envelope }: DocumentPageViewDropdownP
return (
<DropdownMenu>
<DropdownMenuTrigger>
<MoreHorizontal className="text-muted-foreground h-5 w-5" />
<MoreHorizontal className="h-5 w-5 text-muted-foreground" />
</DropdownMenuTrigger>
<DropdownMenuContent className="w-52" align="end" forceMount>
@@ -79,7 +79,7 @@ export const DocumentPageViewDropdown = ({ envelope }: DocumentPageViewDropdownP
{(isOwner || isCurrentTeamDocument) && !isComplete && (
<DropdownMenuItem asChild>
<Link to={`${documentsPath}/${envelope.id}/edit`}>
<Link prefetch="intent" to={`${documentsPath}/${envelope.id}/edit`}>
<Edit className="mr-2 h-4 w-4" />
<Trans>Edit</Trans>
</Link>
@@ -102,7 +102,7 @@ export const DocumentPageViewDropdown = ({ envelope }: DocumentPageViewDropdownP
/>
<DropdownMenuItem asChild>
<Link to={`${documentsPath}/${envelope.id}/logs`}>
<Link prefetch="intent" to={`${documentsPath}/${envelope.id}/logs`}>
<ScrollTextIcon className="mr-2 h-4 w-4" />
<Trans>Audit Logs</Trans>
</Link>
@@ -72,7 +72,7 @@ export default function EnvelopeEditorHeader() {
className="h-6 w-auto"
/>
) : (
<Link to="/">
<Link prefetch="intent" to="/">
<BrandingLogo className="h-6 w-auto" />
</Link>
)}
@@ -544,7 +544,7 @@ export const EnvelopeEditor = () => {
})}
asChild
>
<Link to={relativePath.basePath}>
<Link prefetch="intent" to={relativePath.basePath}>
<ArrowLeftIcon className="h-4 w-4 flex-shrink-0" />
{!minimizeLeftSidebar && (
@@ -57,20 +57,25 @@ export const FolderCard = ({ folder, onMove, onSettings, onDelete }: FolderCardP
};
return (
<Link to={formatPath()} data-folder-id={folder.id} data-folder-name={folder.name}>
<Card className="hover:bg-muted/50 border-border h-full border transition-all">
<Link
prefetch="intent"
to={formatPath()}
data-folder-id={folder.id}
data-folder-name={folder.name}
>
<Card className="h-full border border-border transition-all hover:bg-muted/50">
<CardContent className="p-4">
<div className="flex min-w-0 items-center gap-3">
<FolderIcon className="text-documenso h-6 w-6 flex-shrink-0" />
<FolderIcon className="h-6 w-6 flex-shrink-0 text-documenso" />
<div className="flex w-full min-w-0 items-center justify-between">
<div className="min-w-0 flex-1">
<h3 className="flex min-w-0 items-center gap-2 font-medium">
<span className="truncate">{folder.name}</span>
{folder.pinned && <PinIcon className="text-documenso h-3 w-3 flex-shrink-0" />}
{folder.pinned && <PinIcon className="h-3 w-3 flex-shrink-0 text-documenso" />}
</h3>
<div className="text-muted-foreground mt-1 flex space-x-2 truncate text-xs">
<div className="mt-1 flex space-x-2 truncate text-xs text-muted-foreground">
<span>
{folder.type === FolderType.TEMPLATE ? (
<Plural
@@ -143,17 +148,17 @@ export const FolderCard = ({ folder, onMove, onSettings, onDelete }: FolderCardP
export const FolderCardEmpty = ({ type }: { type: FolderType }) => {
return (
<Card className="hover:bg-muted/50 border-border h-full border transition-all">
<Card className="h-full border border-border transition-all hover:bg-muted/50">
<CardContent className="p-4">
<div className="flex items-center gap-3">
<FolderPlusIcon className="text-muted-foreground/60 h-6 w-6" />
<FolderPlusIcon className="h-6 w-6 text-muted-foreground/60" />
<div>
<h3 className="text-muted-foreground flex items-center gap-2 font-medium">
<h3 className="flex items-center gap-2 font-medium text-muted-foreground">
<Trans>Create folder</Trans>
</h3>
<div className="text-muted-foreground/60 mt-1 flex space-x-2 truncate text-xs">
<div className="mt-1 flex space-x-2 truncate text-xs text-muted-foreground/60">
{type === FolderType.DOCUMENT ? (
<Trans>Organise your documents</Trans>
) : (
@@ -72,7 +72,7 @@ export const FolderGrid = ({ type, parentId }: FolderGridProps) => {
className="flex flex-1 items-center text-sm font-medium text-muted-foreground hover:text-muted-foreground/80"
data-testid="folder-grid-breadcrumbs"
>
<Link to={formatRootPath()} className="flex items-center">
<Link prefetch="intent" to={formatRootPath()} className="flex items-center">
<HomeIcon className="mr-2 h-4 w-4" />
<Trans>Home</Trans>
</Link>
@@ -87,7 +87,11 @@ export const FolderGrid = ({ type, parentId }: FolderGridProps) => {
foldersData?.breadcrumbs.map((folder) => (
<div key={folder.id} className="flex items-center">
<span className="px-3">/</span>
<Link to={formatBreadCrumbPath(folder.id)} className="flex items-center">
<Link
prefetch="intent"
to={formatBreadCrumbPath(folder.id)}
className="flex items-center"
>
<FolderIcon className="mr-2 h-4 w-4" />
<span>{folder.name}</span>
</Link>
@@ -192,6 +196,7 @@ export const FolderGrid = ({ type, parentId }: FolderGridProps) => {
{foldersData.folders.length > 12 && (
<div className="mt-2 flex items-center justify-center">
<Link
prefetch="intent"
className="text-sm font-medium text-muted-foreground hover:text-foreground"
to={formatViewAllFoldersPath()}
>
@@ -54,7 +54,7 @@ export const MenuSwitcher = () => {
primaryText={user.name}
secondaryText={_(msg`Personal Account`)}
rightSideComponent={
<ChevronsUpDown className="text-muted-foreground ml-auto h-4 w-4" />
<ChevronsUpDown className="ml-auto h-4 w-4 text-muted-foreground" />
}
textSectionClassName="hidden lg:flex"
/>
@@ -66,8 +66,9 @@ export const MenuSwitcher = () => {
align="end"
forceMount
>
<DropdownMenuItem className="text-muted-foreground px-4 py-2" asChild>
<DropdownMenuItem className="px-4 py-2 text-muted-foreground" asChild>
<Link
prefetch="intent"
to="/settings/organisations?action=add-organisation"
className="flex items-center justify-between"
>
@@ -78,34 +79,34 @@ export const MenuSwitcher = () => {
<DropdownMenuSeparator />
{isUserAdmin && (
<DropdownMenuItem className="text-muted-foreground px-4 py-2" asChild>
<Link to="/admin">
<DropdownMenuItem className="px-4 py-2 text-muted-foreground" asChild>
<Link prefetch="intent" to="/admin">
<Trans>Admin panel</Trans>
</Link>
</DropdownMenuItem>
)}
<DropdownMenuItem className="text-muted-foreground px-4 py-2" asChild>
<Link to="/inbox">
<DropdownMenuItem className="px-4 py-2 text-muted-foreground" asChild>
<Link prefetch="intent" to="/inbox">
<Trans>Personal Inbox</Trans>
</Link>
</DropdownMenuItem>
<DropdownMenuItem className="text-muted-foreground px-4 py-2" asChild>
<Link to="/settings/profile">
<DropdownMenuItem className="px-4 py-2 text-muted-foreground" asChild>
<Link prefetch="intent" to="/settings/profile">
<Trans>User settings</Trans>
</Link>
</DropdownMenuItem>
<DropdownMenuItem
className="text-muted-foreground px-4 py-2"
className="px-4 py-2 text-muted-foreground"
onClick={() => setLanguageSwitcherOpen(true)}
>
<Trans>Language</Trans>
</DropdownMenuItem>
<DropdownMenuItem
className="text-destructive/90 hover:!text-destructive px-4 py-2"
className="px-4 py-2 text-destructive/90 hover:!text-destructive"
onSelect={async () => authClient.signOut()}
>
<Trans>Sign Out</Trans>
@@ -130,7 +130,7 @@ export const OrgMenuSwitcher = () => {
primaryText={dropdownMenuAvatarText.primaryText}
secondaryText={dropdownMenuAvatarText.secondaryText}
rightSideComponent={
<ChevronsUpDown className="text-muted-foreground ml-auto h-4 w-4" />
<ChevronsUpDown className="ml-auto h-4 w-4 text-muted-foreground" />
}
textSectionClassName="hidden lg:flex"
/>
@@ -139,7 +139,7 @@ export const OrgMenuSwitcher = () => {
<DropdownMenuContent
className={cn(
'divide-border z-[60] ml-6 flex w-full divide-x p-0 md:ml-0 md:min-w-[40rem]',
'z-[60] ml-6 flex w-full divide-x divide-border p-0 md:ml-0 md:min-w-[40rem]',
)}
align="end"
forceMount
@@ -148,7 +148,7 @@ export const OrgMenuSwitcher = () => {
{/* Organisations column */}
<div className="flex w-full flex-col md:w-1/3">
<div className="flex h-12 items-center border-b p-2">
<h3 className="text-muted-foreground flex items-center px-2 text-sm font-medium">
<h3 className="flex items-center px-2 text-sm font-medium text-muted-foreground">
<Building2Icon className="mr-2 h-3.5 w-3.5" />
<Trans>Organisations</Trans>
</h3>
@@ -162,13 +162,17 @@ export const OrgMenuSwitcher = () => {
>
<DropdownMenuItem
className={cn(
'text-muted-foreground w-full px-4 py-2',
'w-full px-4 py-2 text-muted-foreground',
org.id === currentOrganisation?.id && !hoveredOrgId && 'bg-accent',
org.id === hoveredOrgId && 'bg-accent',
)}
asChild
>
<Link to={`/o/${org.url}`} className="flex items-center space-x-2 pr-8">
<Link
prefetch="intent"
to={`/o/${org.url}`}
className="flex items-center space-x-2 pr-8"
>
<span
className={cn('min-w-0 flex-1 truncate', {
'font-semibold': org.id === selectedOrg?.id,
@@ -185,8 +189,9 @@ export const OrgMenuSwitcher = () => {
) && (
<div className="absolute bottom-0 right-0 top-0 flex items-center justify-center">
<Link
prefetch="intent"
to={`/o/${org.url}/settings`}
className="text-muted-foreground mr-2 rounded-sm border p-1 transition-opacity duration-200 group-hover:opacity-100 md:opacity-0"
className="mr-2 rounded-sm border p-1 text-muted-foreground transition-opacity duration-200 group-hover:opacity-100 md:opacity-0"
>
<Settings2Icon className="h-3.5 w-3.5" />
</Link>
@@ -196,7 +201,7 @@ export const OrgMenuSwitcher = () => {
))}
<Button variant="ghost" className="w-full justify-start" asChild>
<Link to="/settings/organisations?action=add-organisation">
<Link prefetch="intent" to="/settings/organisations?action=add-organisation">
<Plus className="mr-2 h-4 w-4" />
<Trans>Create Organisation</Trans>
</Link>
@@ -207,7 +212,7 @@ export const OrgMenuSwitcher = () => {
{/* Teams column */}
<div className="hidden w-1/3 flex-col md:flex">
<div className="flex h-12 items-center border-b p-2">
<h3 className="text-muted-foreground flex items-center px-2 text-sm font-medium">
<h3 className="flex items-center px-2 text-sm font-medium text-muted-foreground">
<UsersIcon className="mr-2 h-3.5 w-3.5" />
<Trans>Teams</Trans>
</h3>
@@ -219,12 +224,16 @@ export const OrgMenuSwitcher = () => {
<div className="group relative" key={team.id}>
<DropdownMenuItem
className={cn(
'text-muted-foreground w-full px-4 py-2',
'w-full px-4 py-2 text-muted-foreground',
team.id === currentTeam?.id && 'bg-accent',
)}
asChild
>
<Link to={`/t/${team.url}`} className="flex items-center space-x-2 pr-8">
<Link
prefetch="intent"
to={`/t/${team.url}`}
className="flex items-center space-x-2 pr-8"
>
<span
className={cn('min-w-0 flex-1 truncate', {
'font-semibold': team.id === currentTeam?.id,
@@ -238,8 +247,9 @@ export const OrgMenuSwitcher = () => {
{canExecuteTeamAction('MANAGE_TEAM', team.currentTeamRole) && (
<div className="absolute bottom-0 right-0 top-0 flex items-center justify-center">
<Link
prefetch="intent"
to={`/t/${team.url}/settings`}
className="text-muted-foreground mr-2 rounded-sm border p-1 opacity-0 transition-opacity duration-200 group-hover:opacity-100"
className="mr-2 rounded-sm border p-1 text-muted-foreground opacity-0 transition-opacity duration-200 group-hover:opacity-100"
>
<Settings2Icon className="h-3.5 w-3.5" />
</Link>
@@ -248,14 +258,17 @@ export const OrgMenuSwitcher = () => {
</div>
))
) : (
<div className="text-muted-foreground my-12 flex items-center justify-center px-2 text-center text-sm">
<div className="my-12 flex items-center justify-center px-2 text-center text-sm text-muted-foreground">
<Trans>Select an organisation to view teams</Trans>
</div>
)}
{displayedOrg && (
<Button variant="ghost" className="w-full justify-start" asChild>
<Link to={`/o/${displayedOrg.url}/settings/teams?action=add-team`}>
<Link
prefetch="intent"
to={`/o/${displayedOrg.url}/settings/teams?action=add-team`}
>
<Plus className="mr-2 h-4 w-4" />
<Trans>Create Team</Trans>
</Link>
@@ -268,15 +281,15 @@ export const OrgMenuSwitcher = () => {
{/* Settings column */}
<div className="hidden w-1/3 flex-col md:flex">
<div className="flex h-12 items-center border-b p-2">
<h3 className="text-muted-foreground flex items-center px-2 text-sm font-medium">
<h3 className="flex items-center px-2 text-sm font-medium text-muted-foreground">
<SettingsIcon className="mr-2 h-3.5 w-3.5" />
<Trans>Settings</Trans>
</h3>
</div>
<div className="flex-1 overflow-y-auto p-1.5">
{isUserAdmin && (
<DropdownMenuItem className="text-muted-foreground px-4 py-2" asChild>
<Link to="/admin">
<DropdownMenuItem className="px-4 py-2 text-muted-foreground" asChild>
<Link prefetch="intent" to="/admin">
<Trans>Admin panel</Trans>
</Link>
</DropdownMenuItem>
@@ -287,43 +300,44 @@ export const OrgMenuSwitcher = () => {
'MANAGE_ORGANISATION',
currentOrganisation.currentOrganisationRole,
) && (
<DropdownMenuItem className="text-muted-foreground px-4 py-2" asChild>
<Link to={`/o/${currentOrganisation.url}/settings`}>
<DropdownMenuItem className="px-4 py-2 text-muted-foreground" asChild>
<Link prefetch="intent" to={`/o/${currentOrganisation.url}/settings`}>
<Trans>Organisation settings</Trans>
</Link>
</DropdownMenuItem>
)}
{currentTeam && canExecuteTeamAction('MANAGE_TEAM', currentTeam.currentTeamRole) && (
<DropdownMenuItem className="text-muted-foreground px-4 py-2" asChild>
<Link to={`/t/${currentTeam.url}/settings`}>
<DropdownMenuItem className="px-4 py-2 text-muted-foreground" asChild>
<Link prefetch="intent" to={`/t/${currentTeam.url}/settings`}>
<Trans>Team settings</Trans>
</Link>
</DropdownMenuItem>
)}
<DropdownMenuItem className="text-muted-foreground px-4 py-2" asChild>
<Link to="/inbox">
<DropdownMenuItem className="px-4 py-2 text-muted-foreground" asChild>
<Link prefetch="intent" to="/inbox">
<Trans>Personal Inbox</Trans>
</Link>
</DropdownMenuItem>
<DropdownMenuItem className="text-muted-foreground px-4 py-2" asChild>
<Link to="/settings/profile">
<DropdownMenuItem className="px-4 py-2 text-muted-foreground" asChild>
<Link prefetch="intent" to="/settings/profile">
<Trans>Account</Trans>
</Link>
</DropdownMenuItem>
<DropdownMenuItem
className="text-muted-foreground px-4 py-2"
className="px-4 py-2 text-muted-foreground"
onClick={() => setLanguageSwitcherOpen(true)}
>
<Trans>Language</Trans>
</DropdownMenuItem>
{currentOrganisation && (
<DropdownMenuItem className="text-muted-foreground px-4 py-2" asChild>
<DropdownMenuItem className="px-4 py-2 text-muted-foreground" asChild>
<Link
prefetch="intent"
to={{
pathname: `/o/${currentOrganisation.url}/support`,
search: currentTeam ? `?team=${currentTeam.id}` : '',
@@ -335,7 +349,7 @@ export const OrgMenuSwitcher = () => {
)}
<DropdownMenuItem
className="text-muted-foreground hover:!text-muted-foreground px-4 py-2"
className="px-4 py-2 text-muted-foreground hover:!text-muted-foreground"
onSelect={async () => authClient.signOut()}
>
<Trans>Sign Out</Trans>
@@ -35,7 +35,7 @@ export const SettingsDesktopNav = ({ className, ...props }: SettingsDesktopNavPr
return (
<div className={cn('flex flex-col gap-y-2', className)} {...props}>
<Link to="/settings/profile">
<Link prefetch="intent" to="/settings/profile">
<Button
variant="ghost"
className={cn(
@@ -50,14 +50,14 @@ export const SettingsDesktopNav = ({ className, ...props }: SettingsDesktopNavPr
{isPersonalLayoutMode && (
<>
<Link to="/settings/document">
<Link prefetch="intent" to="/settings/document">
<Button variant="ghost" className={cn('w-full justify-start')}>
<Settings2Icon className="mr-2 h-5 w-5" />
<Trans>Preferences</Trans>
</Button>
</Link>
<Link className="w-full pl-8" to="/settings/document">
<Link prefetch="intent" className="w-full pl-8" to="/settings/document">
<Button
variant="ghost"
className={cn(
@@ -69,7 +69,7 @@ export const SettingsDesktopNav = ({ className, ...props }: SettingsDesktopNavPr
</Button>
</Link>
<Link className="w-full pl-8" to="/settings/branding">
<Link prefetch="intent" className="w-full pl-8" to="/settings/branding">
<Button
variant="ghost"
className={cn(
@@ -81,7 +81,7 @@ export const SettingsDesktopNav = ({ className, ...props }: SettingsDesktopNavPr
</Button>
</Link>
<Link className="w-full pl-8" to="/settings/email">
<Link prefetch="intent" className="w-full pl-8" to="/settings/email">
<Button
variant="ghost"
className={cn(
@@ -93,7 +93,7 @@ export const SettingsDesktopNav = ({ className, ...props }: SettingsDesktopNavPr
</Button>
</Link>
<Link to="/settings/public-profile">
<Link prefetch="intent" to="/settings/public-profile">
<Button
variant="ghost"
className={cn(
@@ -106,7 +106,7 @@ export const SettingsDesktopNav = ({ className, ...props }: SettingsDesktopNavPr
</Button>
</Link>
<Link to="/settings/tokens">
<Link prefetch="intent" to="/settings/tokens">
<Button
variant="ghost"
className={cn(
@@ -119,7 +119,7 @@ export const SettingsDesktopNav = ({ className, ...props }: SettingsDesktopNavPr
</Button>
</Link>
<Link to="/settings/webhooks">
<Link prefetch="intent" to="/settings/webhooks">
<Button
variant="ghost"
className={cn(
@@ -134,7 +134,7 @@ export const SettingsDesktopNav = ({ className, ...props }: SettingsDesktopNavPr
</>
)}
<Link to="/settings/organisations">
<Link prefetch="intent" to="/settings/organisations">
<Button
variant="ghost"
className={cn(
@@ -148,7 +148,10 @@ export const SettingsDesktopNav = ({ className, ...props }: SettingsDesktopNavPr
</Link>
{IS_BILLING_ENABLED() && hasManageableBillingOrgs && (
<Link to={isPersonalLayoutMode ? '/settings/billing-personal' : `/settings/billing`}>
<Link
prefetch="intent"
to={isPersonalLayoutMode ? '/settings/billing-personal' : `/settings/billing`}
>
<Button
variant="ghost"
className={cn(
@@ -162,7 +165,7 @@ export const SettingsDesktopNav = ({ className, ...props }: SettingsDesktopNavPr
</Link>
)}
<Link to="/settings/security">
<Link prefetch="intent" to="/settings/security">
<Button
variant="ghost"
className={cn(
@@ -39,7 +39,7 @@ export const SettingsMobileNav = ({ className, ...props }: SettingsMobileNavProp
className={cn('flex flex-wrap items-center justify-start gap-x-2 gap-y-4', className)}
{...props}
>
<Link to="/settings/profile">
<Link prefetch="intent" to="/settings/profile">
<Button
variant="ghost"
className={cn(
@@ -54,7 +54,7 @@ export const SettingsMobileNav = ({ className, ...props }: SettingsMobileNavProp
{isPersonalLayoutMode && (
<>
<Link to="/settings/document">
<Link prefetch="intent" to="/settings/document">
<Button
variant="ghost"
className={cn(
@@ -67,7 +67,7 @@ export const SettingsMobileNav = ({ className, ...props }: SettingsMobileNavProp
</Button>
</Link>
<Link to="/settings/branding">
<Link prefetch="intent" to="/settings/branding">
<Button
variant="ghost"
className={cn(
@@ -80,7 +80,7 @@ export const SettingsMobileNav = ({ className, ...props }: SettingsMobileNavProp
</Button>
</Link>
<Link to="/settings/email">
<Link prefetch="intent" to="/settings/email">
<Button
variant="ghost"
className={cn(
@@ -93,7 +93,7 @@ export const SettingsMobileNav = ({ className, ...props }: SettingsMobileNavProp
</Button>
</Link>
<Link to="/settings/public-profile">
<Link prefetch="intent" to="/settings/public-profile">
<Button
variant="ghost"
className={cn(
@@ -106,7 +106,7 @@ export const SettingsMobileNav = ({ className, ...props }: SettingsMobileNavProp
</Button>
</Link>
<Link to="/settings/tokens">
<Link prefetch="intent" to="/settings/tokens">
<Button
variant="ghost"
className={cn(
@@ -119,7 +119,7 @@ export const SettingsMobileNav = ({ className, ...props }: SettingsMobileNavProp
</Button>
</Link>
<Link to="/settings/webhooks">
<Link prefetch="intent" to="/settings/webhooks">
<Button
variant="ghost"
className={cn(
@@ -134,7 +134,7 @@ export const SettingsMobileNav = ({ className, ...props }: SettingsMobileNavProp
</>
)}
<Link to="/settings/organisations">
<Link prefetch="intent" to="/settings/organisations">
<Button
variant="ghost"
className={cn(
@@ -148,7 +148,10 @@ export const SettingsMobileNav = ({ className, ...props }: SettingsMobileNavProp
</Link>
{IS_BILLING_ENABLED() && hasManageableBillingOrgs && (
<Link to={isPersonalLayoutMode ? '/settings/billing-personal' : `/settings/billing`}>
<Link
prefetch="intent"
to={isPersonalLayoutMode ? '/settings/billing-personal' : `/settings/billing`}
>
<Button
variant="ghost"
className={cn(
@@ -162,7 +165,7 @@ export const SettingsMobileNav = ({ className, ...props }: SettingsMobileNavProp
</Link>
)}
<Link to="/settings/security">
<Link prefetch="intent" to="/settings/security">
<Button
variant="ghost"
className={cn(
@@ -7,7 +7,11 @@ import { Skeleton } from '@documenso/ui/primitives/skeleton';
export default function DocumentEditSkeleton() {
return (
<div className="mx-auto -mt-4 flex w-full max-w-screen-xl flex-col px-4 md:px-8">
<Link to="/" className="flex grow-0 items-center text-documenso-700 hover:opacity-80">
<Link
prefetch="intent"
to="/"
className="flex grow-0 items-center text-documenso-700 hover:opacity-80"
>
<ChevronLeft className="mr-2 inline-block h-5 w-5" />
<Trans>Documents</Trans>
</Link>
@@ -78,7 +78,7 @@ export const AdminClaimsTable = ({ licenseFlags }: AdminClaimsTableProps) => {
header: t`Name`,
accessorKey: 'name',
cell: ({ row }) => (
<Link to={`/admin/organisations?query=claim:${row.original.id}`}>
<Link prefetch="intent" to={`/admin/organisations?query=claim:${row.original.id}`}>
{row.original.name}
</Link>
),
@@ -81,7 +81,7 @@ export const AdminDashboardUsersTable = ({
cell: ({ row }) => {
return (
<Button className="w-24" asChild>
<Link to={`/admin/users/${row.original.id}`}>
<Link prefetch="intent" to={`/admin/users/${row.original.id}`}>
<Edit className="-ml-1 mr-2 h-4 w-4" />
<Trans>Edit</Trans>
</Link>
@@ -86,7 +86,9 @@ export const AdminOrganisationsTable = ({
header: t`Organisation`,
accessorKey: 'name',
cell: ({ row }) => (
<Link to={`/admin/organisations/${row.original.id}`}>{row.original.name}</Link>
<Link prefetch="intent" to={`/admin/organisations/${row.original.id}`}>
{row.original.name}
</Link>
),
},
{
@@ -98,7 +100,9 @@ export const AdminOrganisationsTable = ({
header: t`Owner`,
accessorKey: 'owner',
cell: ({ row }) => (
<Link to={`/admin/users/${row.original.owner.id}`}>{row.original.owner.name}</Link>
<Link prefetch="intent" to={`/admin/users/${row.original.owner.id}`}>
{row.original.owner.name}
</Link>
),
},
{
@@ -161,14 +165,14 @@ export const AdminOrganisationsTable = ({
</DropdownMenuLabel>
<DropdownMenuItem asChild>
<Link to={`/admin/organisations/${row.original.id}`}>
<Link prefetch="intent" to={`/admin/organisations/${row.original.id}`}>
<SettingsIcon className="mr-2 h-4 w-4" />
<Trans>Manage</Trans>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to={`/admin/users/${row.original.owner.id}`}>
<Link prefetch="intent" to={`/admin/users/${row.original.owner.id}`}>
<UserIcon className="mr-2 h-4 w-4" />
<Trans>View owner</Trans>
</Link>
@@ -56,7 +56,7 @@ export const DocumentsTableActionButton = ({ row }: DocumentsTableActionButtonPr
isOwner ? { isDraft: true, isOwner: true } : { isDraft: true, isCurrentTeamDocument: true },
() => (
<Button className="w-32" asChild>
<Link to={formatPath}>
<Link prefetch="intent" to={formatPath}>
<Edit className="-ml-1 mr-2 h-4 w-4" />
<Trans>Edit</Trans>
</Link>
@@ -76,7 +76,7 @@ export const DocumentsTableActionDropdown = ({
return (
<DropdownMenu>
<DropdownMenuTrigger data-testid="document-table-action-btn">
<MoreHorizontal className="text-muted-foreground h-5 w-5" />
<MoreHorizontal className="h-5 w-5 text-muted-foreground" />
</DropdownMenuTrigger>
<DropdownMenuContent className="w-52" align="start" forceMount>
@@ -115,7 +115,7 @@ export const DocumentsTableActionDropdown = ({
)}
<DropdownMenuItem disabled={!canManageDocument || isComplete} asChild>
<Link to={formatPath}>
<Link prefetch="intent" to={formatPath}>
<Edit className="mr-2 h-4 w-4" />
<Trans>Edit</Trans>
</Link>
@@ -105,7 +105,10 @@ export const OrganisationEmailDomainsDataTable = () => {
cell: ({ row }) => (
<div className="flex justify-end space-x-2">
<Button asChild variant="outline">
<Link to={`/o/${organisation.url}/settings/email-domains/${row.original.id}`}>
<Link
prefetch="intent"
to={`/o/${organisation.url}/settings/email-domains/${row.original.id}`}
>
Manage
</Link>
</Button>
@@ -82,7 +82,10 @@ export const OrganisationGroupsDataTable = () => {
cell: ({ row }) => (
<div className="flex justify-end space-x-2">
<Button asChild variant="outline">
<Link to={`/o/${organisation.url}/settings/groups/${row.original.id}`}>
<Link
prefetch="intent"
to={`/o/${organisation.url}/settings/groups/${row.original.id}`}
>
<Trans>Manage</Trans>
</Link>
</Button>
@@ -64,7 +64,7 @@ export const OrganisationTeamsTable = () => {
avatarClass="h-12 w-12"
avatarFallback={row.original.name.slice(0, 1).toUpperCase()}
primaryText={
<span className="text-foreground/80 font-semibold">{row.original.name}</span>
<span className="font-semibold text-foreground/80">{row.original.name}</span>
}
secondaryText={`${NEXT_PUBLIC_WEBAPP_URL()}/t/${row.original.url}`}
/>
@@ -81,7 +81,7 @@ export const OrganisationTeamsTable = () => {
cell: ({ row }) => (
<div className="flex justify-end space-x-2">
<Button variant="outline" asChild>
<Link to={`/t/${row.original.url}/settings`}>
<Link prefetch="intent" to={`/t/${row.original.url}/settings`}>
<Trans>Manage</Trans>
</Link>
</Button>
@@ -56,14 +56,14 @@ export const TemplatesTableActionDropdown = ({
return (
<DropdownMenu>
<DropdownMenuTrigger data-testid="template-table-action-btn">
<MoreHorizontal className="text-muted-foreground h-5 w-5" />
<MoreHorizontal className="h-5 w-5 text-muted-foreground" />
</DropdownMenuTrigger>
<DropdownMenuContent className="w-52" align="start" forceMount>
<DropdownMenuLabel>Action</DropdownMenuLabel>
<DropdownMenuItem disabled={!isOwner && !isTeamTemplate} asChild>
<Link to={formatPath}>
<Link prefetch="intent" to={formatPath}>
<Edit className="mr-2 h-4 w-4" />
<Trans>Edit</Trans>
</Link>
@@ -84,7 +84,7 @@ export const TemplatesTableActionDropdown = ({
trigger={
<div
data-testid="template-direct-link"
className="hover:bg-accent hover:text-accent-foreground relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors"
className="relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground"
>
<Share2Icon className="mr-2 h-4 w-4" />
<Trans>Direct link</Trans>
@@ -101,7 +101,7 @@ export const TemplatesTableActionDropdown = ({
templateId={row.id}
recipients={row.recipients}
trigger={
<div className="hover:bg-accent hover:text-accent-foreground relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors">
<div className="relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground">
<Upload className="mr-2 h-4 w-4" />
<Trans>Bulk Send via CSV</Trans>
</div>
@@ -57,7 +57,7 @@ export const UserBillingOrganisationsTable = () => {
avatarClass="h-12 w-12"
avatarFallback={row.original.name.slice(0, 1).toUpperCase()}
primaryText={
<span className="text-foreground/80 font-semibold">{row.original.name}</span>
<span className="font-semibold text-foreground/80">{row.original.name}</span>
}
secondaryText={`${NEXT_PUBLIC_WEBAPP_URL()}/o/${row.original.url}`}
/>
@@ -80,7 +80,7 @@ export const UserBillingOrganisationsTable = () => {
id: 'actions',
cell: ({ row }) => (
<Button asChild variant="outline">
<Link to={`/o/${row.original.url}/settings/billing`}>
<Link prefetch="intent" to={`/o/${row.original.url}/settings/billing`}>
<Trans>Manage Billing</Trans>
</Link>
</Button>
@@ -91,7 +91,7 @@ export const UserBillingOrganisationsTable = () => {
if (billingOrganisations.length === 0) {
return (
<div className="text-muted-foreground flex flex-col items-center justify-center rounded-lg border border-dashed py-12 text-center">
<div className="flex flex-col items-center justify-center rounded-lg border border-dashed py-12 text-center text-muted-foreground">
<p className="text-sm">
<Trans>You don't manage billing for any organisations.</Trans>
</p>
@@ -55,7 +55,7 @@ export const UserOrganisationsTable = () => {
avatarClass="h-12 w-12"
avatarFallback={row.original.name.slice(0, 1).toUpperCase()}
primaryText={
<span className="text-foreground/80 font-semibold">
<span className="font-semibold text-foreground/80">
{isPersonalLayoutMode
? _(
msg({
@@ -97,7 +97,7 @@ export const UserOrganisationsTable = () => {
row.original.currentOrganisationRole,
) && (
<Button variant="outline" asChild>
<Link to={`/o/${row.original.url}/settings`}>
<Link prefetch="intent" to={`/o/${row.original.url}/settings`}>
<Trans>Manage</Trans>
</Link>
</Button>
@@ -98,7 +98,7 @@ export default function Layout({ loaderData, params, matches }: Route.ComponentP
}}
primaryButton={
<Button asChild>
<Link to="/">
<Link prefetch="intent" to="/">
<Trans>Go home</Trans>
</Link>
</Button>
@@ -62,7 +62,7 @@ export default function AdminLayout({ loaderData }: Route.ComponentProps) {
)}
asChild
>
<Link to="/admin/stats">
<Link prefetch="intent" to="/admin/stats">
<BarChart3 className="mr-2 h-5 w-5" />
<Trans>Stats</Trans>
</Link>
@@ -76,7 +76,7 @@ export default function AdminLayout({ loaderData }: Route.ComponentProps) {
)}
asChild
>
<Link to="/admin/organisations">
<Link prefetch="intent" to="/admin/organisations">
<Building2Icon className="mr-2 h-5 w-5" />
<Trans>Organisations</Trans>
</Link>
@@ -90,7 +90,7 @@ export default function AdminLayout({ loaderData }: Route.ComponentProps) {
)}
asChild
>
<Link to="/admin/claims">
<Link prefetch="intent" to="/admin/claims">
<Wallet2 className="mr-2 h-5 w-5" />
<Trans>Claims</Trans>
</Link>
@@ -104,7 +104,7 @@ export default function AdminLayout({ loaderData }: Route.ComponentProps) {
)}
asChild
>
<Link to="/admin/users">
<Link prefetch="intent" to="/admin/users">
<Users className="mr-2 h-5 w-5" />
<Trans>Users</Trans>
</Link>
@@ -118,7 +118,7 @@ export default function AdminLayout({ loaderData }: Route.ComponentProps) {
)}
asChild
>
<Link to="/admin/documents">
<Link prefetch="intent" to="/admin/documents">
<FileStack className="mr-2 h-5 w-5" />
<Trans>Documents</Trans>
</Link>
@@ -132,7 +132,7 @@ export default function AdminLayout({ loaderData }: Route.ComponentProps) {
)}
asChild
>
<Link to="/admin/unsealed-documents">
<Link prefetch="intent" to="/admin/unsealed-documents">
<AlertTriangleIcon className="mr-2 h-5 w-5" />
<Trans>Unsealed Documents</Trans>
</Link>
@@ -146,7 +146,7 @@ export default function AdminLayout({ loaderData }: Route.ComponentProps) {
)}
asChild
>
<Link to="/admin/email-domains">
<Link prefetch="intent" to="/admin/email-domains">
<MailIcon className="mr-2 h-5 w-5" />
<Trans>Email Domains</Trans>
</Link>
@@ -160,7 +160,7 @@ export default function AdminLayout({ loaderData }: Route.ComponentProps) {
)}
asChild
>
<Link to="/admin/organisation-insights">
<Link prefetch="intent" to="/admin/organisation-insights">
<Trophy className="mr-2 h-5 w-5" />
<Trans>Organisation Insights</Trans>
</Link>
@@ -174,7 +174,7 @@ export default function AdminLayout({ loaderData }: Route.ComponentProps) {
)}
asChild
>
<Link to="/admin/site-settings">
<Link prefetch="intent" to="/admin/site-settings">
<Settings className="mr-2 h-5 w-5" />
<Trans>Site Settings</Trans>
</Link>
@@ -166,7 +166,7 @@ export default function AdminDocumentDetailsPage({ loaderData }: Route.Component
</TooltipProvider>
<Button variant="outline" asChild>
<Link to={`/admin/users/${envelope.userId}`}>
<Link prefetch="intent" to={`/admin/users/${envelope.userId}`}>
<Trans>Go to owner</Trans>
</Link>
</Button>
@@ -64,6 +64,7 @@ export default function AdminDocumentsPage() {
cell: ({ row }) => {
return (
<Link
prefetch="intent"
to={`/admin/documents/${row.original.envelopeId}`}
className="block max-w-[5rem] truncate font-medium hover:underline md:max-w-[10rem]"
>
@@ -88,9 +89,9 @@ export default function AdminDocumentsPage() {
return (
<Tooltip delayDuration={200}>
<TooltipTrigger>
<Link to={`/admin/users/${row.original.user.id}`}>
<Avatar className="dark:border-border h-12 w-12 border-2 border-solid border-white">
<AvatarFallback className="text-muted-foreground text-xs">
<Link prefetch="intent" to={`/admin/users/${row.original.user.id}`}>
<Avatar className="h-12 w-12 border-2 border-solid border-white dark:border-border">
<AvatarFallback className="text-xs text-muted-foreground">
{avatarFallbackText}
</AvatarFallback>
</Avatar>
@@ -98,13 +99,13 @@ export default function AdminDocumentsPage() {
</TooltipTrigger>
<TooltipContent className="flex max-w-xs items-center gap-2">
<Avatar className="dark:border-border h-12 w-12 border-2 border-solid border-white">
<AvatarFallback className="text-muted-foreground text-xs">
<Avatar className="h-12 w-12 border-2 border-solid border-white dark:border-border">
<AvatarFallback className="text-xs text-muted-foreground">
{avatarFallbackText}
</AvatarFallback>
</Avatar>
<div className="text-muted-foreground flex flex-col text-sm">
<div className="flex flex-col text-sm text-muted-foreground">
<span>{row.original.user.name}</span>
<span>{row.original.user.email}</span>
</div>
@@ -68,6 +68,7 @@ export default function AdminEmailDomainsPage() {
accessorKey: 'domain',
cell: ({ row }) => (
<Link
prefetch="intent"
to={`/admin/email-domains/${row.original.id}`}
className="block max-w-[10rem] truncate font-medium hover:underline md:max-w-[15rem]"
>
@@ -80,6 +81,7 @@ export default function AdminEmailDomainsPage() {
accessorKey: 'organisation',
cell: ({ row }) => (
<Link
prefetch="intent"
to={`/admin/organisations/${row.original.organisation.id}`}
className="hover:underline"
>
@@ -126,7 +128,7 @@ export default function AdminEmailDomainsPage() {
header: _(msg`Actions`),
cell: ({ row }) => (
<Button asChild variant="outline" size="sm">
<Link to={`/admin/email-domains/${row.original.id}`}>
<Link prefetch="intent" to={`/admin/email-domains/${row.original.id}`}>
<Trans>View</Trans>
</Link>
</Button>
@@ -49,7 +49,7 @@ export default function OrganisationInsights({ loaderData }: Route.ComponentProp
<div className="flex items-center justify-between">
<h2 className="text-4xl font-semibold">{organisationName}</h2>
<Button variant="outline" asChild>
<Link to={`/admin/organisations/${organisationId}`}>
<Link prefetch="intent" to={`/admin/organisations/${organisationId}`}>
<Trans>Manage organisation</Trans>
</Link>
</Button>
@@ -106,7 +106,9 @@ export default function OrganisationGroupSettingsPage({
header: t`Member`,
cell: ({ row }) => (
<div className="flex items-center gap-2">
<Link to={`/admin/users/${row.original.user.id}`}>{row.original.user.name}</Link>
<Link prefetch="intent" to={`/admin/users/${row.original.user.id}`}>
{row.original.user.name}
</Link>
{row.original.user.id === organisation?.ownerUserId && (
<Badge>
<Trans>Owner</Trans>
@@ -118,7 +120,9 @@ export default function OrganisationGroupSettingsPage({
{
header: t`Email`,
cell: ({ row }) => (
<Link to={`/admin/users/${row.original.user.id}`}>{row.original.user.email}</Link>
<Link prefetch="intent" to={`/admin/users/${row.original.user.id}`}>
{row.original.user.email}
</Link>
),
},
{
@@ -166,7 +170,7 @@ export default function OrganisationGroupSettingsPage({
}}
primaryButton={
<Button asChild>
<Link to={`/admin/organisations`}>
<Link prefetch="intent" to={`/admin/organisations`}>
<Trans>Go back</Trans>
</Link>
</Button>
@@ -183,7 +187,7 @@ export default function OrganisationGroupSettingsPage({
subtitle={t`Manage the ${organisation.name} organisation`}
>
<Button variant="outline" asChild>
<Link to={`/admin/organisation-insights/${organisationId}`}>
<Link prefetch="intent" to={`/admin/organisation-insights/${organisationId}`}>
<Trans>View insights</Trans>
</Link>
</Button>
@@ -71,7 +71,7 @@ export default function UserPage({ params }: { params: { id: number } }) {
}}
primaryButton={
<Button asChild>
<Link to={`/admin/users`}>
<Link prefetch="intent" to={`/admin/users`}>
<Trans>Go back</Trans>
</Link>
</Button>
@@ -54,7 +54,7 @@ export default function DashboardPage() {
<h1 className="text-3xl font-bold">
<Trans>Dashboard</Trans>
</h1>
<p className="text-muted-foreground mt-1">
<p className="mt-1 text-muted-foreground">
<Trans>Welcome back! Here's an overview of your account.</Trans>
</p>
@@ -69,13 +69,13 @@ export default function DashboardPage() {
<p className="font-semibold">
<Trans>No organisations found</Trans>
</p>
<p className="text-muted-foreground text-sm">
<p className="text-sm text-muted-foreground">
<Trans>Create an organisation to get started.</Trans>
</p>
</div>
<Button asChild className="mt-4" variant="outline">
<Link to="/settings/organisations?action=add-organisation">
<Link prefetch="intent" to="/settings/organisations?action=add-organisation">
<Trans>Create organisation</Trans>
</Link>
</Button>
@@ -87,7 +87,7 @@ export default function DashboardPage() {
<div className="mb-8">
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<Building2Icon className="text-muted-foreground h-5 w-5" />
<Building2Icon className="h-5 w-5 text-muted-foreground" />
<h2 className="text-xl font-semibold">
<Trans>Organisations</Trans>
</h2>
@@ -103,8 +103,8 @@ export default function DashboardPage() {
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{organisations.map((org) => (
<div key={org.id} className="group relative">
<Link to={`/o/${org.url}`}>
<Card className="hover:bg-muted/50 h-full border pr-6 transition-all">
<Link prefetch="intent" to={`/o/${org.url}`}>
<Card className="h-full border pr-6 transition-all hover:bg-muted/50">
<CardContent className="p-4">
<div className="flex items-center gap-3">
<Avatar className="h-10 w-10 border border-solid">
@@ -118,7 +118,7 @@ export default function DashboardPage() {
<div className="flex-1">
<h3 className="font-medium">{org.name}</h3>
<div className="text-muted-foreground mt-1 flex items-center gap-3 text-xs">
<div className="mt-1 flex items-center gap-3 text-xs text-muted-foreground">
<div className="flex items-center gap-1">
<UsersIcon className="h-3 w-3" />
<span>
@@ -148,8 +148,8 @@ export default function DashboardPage() {
'MANAGE_ORGANISATION',
org.currentOrganisationRole,
) && (
<div className="text-muted-foreground absolute right-4 top-4 opacity-0 transition-opacity duration-200 group-hover:opacity-100">
<Link to={`/o/${org.url}/settings`}>
<div className="absolute right-4 top-4 text-muted-foreground opacity-0 transition-opacity duration-200 group-hover:opacity-100">
<Link prefetch="intent" to={`/o/${org.url}/settings`}>
<SettingsIcon className="h-4 w-4" />
</Link>
</div>
@@ -165,7 +165,7 @@ export default function DashboardPage() {
<div className="mb-8">
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<UsersIcon className="text-muted-foreground h-5 w-5" />
<UsersIcon className="h-5 w-5 text-muted-foreground" />
<h2 className="text-xl font-semibold">
<Trans>Teams</Trans>
</h2>
@@ -182,8 +182,8 @@ export default function DashboardPage() {
<div className="flex gap-4">
{teams.map((team) => (
<div key={team.id} className="group relative">
<Link to={`/t/${team.url}`}>
<Card className="hover:bg-muted/50 w-[350px] shrink-0 border transition-all">
<Link prefetch="intent" to={`/t/${team.url}`}>
<Card className="w-[350px] shrink-0 border transition-all hover:bg-muted/50">
<CardContent className="p-4">
<div className="flex items-center gap-3">
<Avatar className="h-10 w-10 border border-solid">
@@ -197,7 +197,7 @@ export default function DashboardPage() {
<div className="flex-1">
<h3 className="font-medium">{team.name}</h3>
<div className="text-muted-foreground mt-1 flex items-center gap-3 text-xs">
<div className="mt-1 flex items-center gap-3 text-xs text-muted-foreground">
<div className="flex items-center gap-1">
<UsersIcon className="h-3 w-3" />
{team.organisation.ownerUserId === user.id
@@ -211,7 +211,7 @@ export default function DashboardPage() {
</div>
</div>
</div>
<div className="text-muted-foreground mt-3 text-xs">
<div className="mt-3 text-xs text-muted-foreground">
<Trans>
Joined{' '}
{DateTime.fromJSDate(team.createdAt).toRelative({ style: 'short' })}
@@ -222,8 +222,8 @@ export default function DashboardPage() {
</Link>
{canExecuteTeamAction('MANAGE_TEAM', team.currentTeamRole) && (
<div className="text-muted-foreground absolute right-4 top-4 opacity-0 transition-opacity duration-200 group-hover:opacity-100">
<Link to={`/t/${team.url}/settings`}>
<div className="absolute right-4 top-4 text-muted-foreground opacity-0 transition-opacity duration-200 group-hover:opacity-100">
<Link prefetch="intent" to={`/t/${team.url}/settings`}>
<SettingsIcon className="h-4 w-4" />
</Link>
</div>
@@ -240,7 +240,7 @@ export default function DashboardPage() {
<div>
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<InboxIcon className="text-muted-foreground h-5 w-5" />
<InboxIcon className="h-5 w-5 text-muted-foreground" />
<h2 className="text-xl font-semibold">
<Trans>Personal Inbox</Trans>
</h2>
@@ -120,7 +120,7 @@ export default function OrganisationSettingsTeamsPage() {
</div>
<Button asChild>
<Link to={`/o/${organisation.url}/settings`}>
<Link prefetch="intent" to={`/o/${organisation.url}/settings`}>
<Trans>Manage Organisation</Trans>
</Link>
</Button>
@@ -128,7 +128,7 @@ export default function OrganisationSettingsTeamsPage() {
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{organisation.teams.map((team) => (
<Link to={`/t/${team.url}`} key={team.id}>
<Link prefetch="intent" to={`/t/${team.url}`} key={team.id}>
<Card className="h-full border border-border transition-all hover:bg-muted/50">
<CardContent className="p-4">
<div className="flex items-center gap-3">
@@ -187,19 +187,19 @@ const TeamDropdownMenu = ({ team }: { team: TGetOrganisationSessionResponse[0]['
</DropdownMenuTrigger>
<DropdownMenuContent align="end" onClick={(e) => e.stopPropagation()}>
<DropdownMenuItem asChild>
<Link to={`/t/${team.url}`}>
<Link prefetch="intent" to={`/t/${team.url}`}>
<ArrowRight className="mr-2 h-4 w-4" />
<Trans>Go to team</Trans>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to={`/t/${team.url}/settings`}>
<Link prefetch="intent" to={`/t/${team.url}/settings`}>
<SettingsIcon className="mr-2 h-4 w-4" />
<Trans>Settings</Trans>
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link to={`/t/${team.url}/settings/members`}>
<Link prefetch="intent" to={`/t/${team.url}/settings/members`}>
<UsersIcon className="mr-2 h-4 w-4" />
<Trans>Members</Trans>
</Link>
@@ -123,7 +123,7 @@ export default function SettingsLayout() {
}}
primaryButton={
<Button asChild>
<Link to={`/o/${organisation.url}`}>
<Link prefetch="intent" to={`/o/${organisation.url}`}>
<Trans>Go Back</Trans>
</Link>
</Button>
@@ -65,7 +65,7 @@ export default function OrganisationEmailDomainSettingsPage({ params }: Route.Co
cell: ({ row }) => (
<DropdownMenu>
<DropdownMenuTrigger>
<MoreHorizontalIcon className="text-muted-foreground h-5 w-5" />
<MoreHorizontalIcon className="h-5 w-5 text-muted-foreground" />
</DropdownMenuTrigger>
<DropdownMenuContent className="w-52" align="start" forceMount>
@@ -122,7 +122,7 @@ export default function OrganisationEmailDomainSettingsPage({ params }: Route.Co
}}
primaryButton={
<Button asChild>
<Link to={`/o/${organisation.url}/settings/email-domains`}>
<Link prefetch="intent" to={`/o/${organisation.url}/settings/email-domains`}>
<Trans>Go back</Trans>
</Link>
</Button>
@@ -75,7 +75,7 @@ export default function OrganisationGroupSettingsPage({ params }: Route.Componen
if (isLoadingGroup || isLoadingMembers) {
return (
<div className="flex items-center justify-center rounded-lg py-32">
<Loader className="text-muted-foreground h-6 w-6 animate-spin" />
<Loader className="h-6 w-6 animate-spin text-muted-foreground" />
</div>
);
}
@@ -94,7 +94,7 @@ export default function OrganisationGroupSettingsPage({ params }: Route.Componen
}}
primaryButton={
<Button asChild>
<Link to={`/o/${organisation.url}/settings/groups`}>
<Link prefetch="intent" to={`/o/${organisation.url}/settings/groups`}>
<Trans>Go back</Trans>
</Link>
</Button>
@@ -71,7 +71,7 @@ export default function SettingsSecurity({ loaderData }: Route.ComponentProps) {
<>
<PasswordForm user={user} />
<hr className="border-border/50 mt-6" />
<hr className="mt-6 border-border/50" />
</>
)}
@@ -145,7 +145,7 @@ export default function SettingsSecurity({ loaderData }: Route.ComponentProps) {
</div>
<Button asChild variant="outline" className="bg-background">
<Link to="/settings/security/passkeys">
<Link prefetch="intent" to="/settings/security/passkeys">
<Trans>Manage passkeys</Trans>
</Link>
</Button>
@@ -166,7 +166,7 @@ export default function SettingsSecurity({ loaderData }: Route.ComponentProps) {
</div>
<Button asChild variant="outline" className="bg-background">
<Link to="/settings/security/activity">
<Link prefetch="intent" to="/settings/security/activity">
<Trans>View activity</Trans>
</Link>
</Button>
@@ -187,7 +187,7 @@ export default function SettingsSecurity({ loaderData }: Route.ComponentProps) {
</div>
<Button asChild variant="outline" className="bg-background">
<Link to="/settings/security/sessions">
<Link prefetch="intent" to="/settings/security/sessions">
<Trans>Manage sessions</Trans>
</Link>
</Button>
@@ -208,7 +208,7 @@ export default function SettingsSecurity({ loaderData }: Route.ComponentProps) {
</div>
<Button asChild variant="outline" className="bg-background">
<Link to="/settings/security/linked-accounts">
<Link prefetch="intent" to="/settings/security/linked-accounts">
<Trans>Manage linked accounts</Trans>
</Link>
</Button>
@@ -65,7 +65,7 @@ export default function Layout() {
}}
primaryButton={
<Button asChild>
<Link to="/settings/teams">
<Link prefetch="intent" to="/settings/teams">
<Trans>View teams</Trans>
</Link>
</Button>
@@ -84,7 +84,7 @@ export default function DocumentPage({ params }: Route.ComponentProps) {
}}
primaryButton={
<Button asChild>
<Link to={`/t/${team.url}/documents`}>
<Link prefetch="intent" to={`/t/${team.url}/documents`}>
<Trans>Go back</Trans>
</Link>
</Button>
@@ -103,7 +103,11 @@ export default function DocumentPage({ params }: Route.ComponentProps) {
<DocumentRecipientLinkCopyDialog recipients={envelope.recipients} />
)}
<Link to={documentRootPath} className="flex items-center text-documenso-700 hover:opacity-80">
<Link
prefetch="intent"
to={documentRootPath}
className="flex items-center text-documenso-700 hover:opacity-80"
>
<ChevronLeft className="mr-2 inline-block h-5 w-5" />
<Trans>Documents</Trans>
</Link>
@@ -84,7 +84,7 @@ export function ErrorBoundary({ error, params }: Route.ErrorBoundaryProps) {
secondaryButton={null}
primaryButton={
<Button asChild className="w-32">
<Link to={`/t/${params.teamUrl}/documents`}>
<Link prefetch="intent" to={`/t/${params.teamUrl}/documents`}>
<ChevronLeftIcon className="mr-2 h-4 w-4" />
<Trans>Go Back</Trans>
</Link>
@@ -89,7 +89,7 @@ export default function EnvelopeEditorPage({ params }: Route.ComponentProps) {
}}
primaryButton={
<Button asChild>
<Link to={`/t/${team.url}/documents`}>
<Link prefetch="intent" to={`/t/${team.url}/documents`}>
<Trans>Go home</Trans>
</Link>
</Button>
@@ -116,7 +116,7 @@ export default function TeamsSettingsLayout() {
}}
primaryButton={
<Button asChild>
<Link to={`/t/${team.url}`}>
<Link prefetch="intent" to={`/t/${team.url}`}>
<Trans>Go Back</Trans>
</Link>
</Button>
@@ -151,10 +151,10 @@ export default function WebhookPage({ params }: Route.ComponentProps) {
accessorKey: 'event',
cell: ({ row }) => (
<div>
<p className="text-foreground text-sm font-semibold">
<p className="text-sm font-semibold text-foreground">
{toFriendlyWebhookEventName(row.original.event)}
</p>
<p className="text-muted-foreground text-xs">{row.original.id}</p>
<p className="text-xs text-muted-foreground">{row.original.id}</p>
</div>
),
},
@@ -219,7 +219,7 @@ export default function WebhookPage({ params }: Route.ComponentProps) {
}}
primaryButton={
<Button asChild>
<Link to={`/t/${team.url}/settings/webhooks`}>
<Link prefetch="intent" to={`/t/${team.url}/settings/webhooks`}>
<Trans>Go back</Trans>
</Link>
</Button>
@@ -276,17 +276,17 @@ export default function WebhookPage({ params }: Route.ComponentProps) {
<Tabs value={parsedSearchParams.status || ''} className="flex-shrink-0">
<TabsList>
<TabsTrigger className="hover:text-foreground min-w-[60px]" value="" asChild>
<TabsTrigger className="min-w-[60px] hover:text-foreground" value="" asChild>
<Link to={getTabHref('')}>
<Trans>All</Trans>
</Link>
</TabsTrigger>
<TabsTrigger className="hover:text-foreground min-w-[60px]" value="SUCCESS" asChild>
<TabsTrigger className="min-w-[60px] hover:text-foreground" value="SUCCESS" asChild>
<Link to={getTabHref(WebhookCallStatus.SUCCESS)}>
<Trans>Success</Trans>
</Link>
</TabsTrigger>
<TabsTrigger className="hover:text-foreground min-w-[60px]" value="FAILED" asChild>
<TabsTrigger className="min-w-[60px] hover:text-foreground" value="FAILED" asChild>
<Link to={getTabHref(WebhookCallStatus.FAILED)}>
<Trans>Failed</Trans>
</Link>
@@ -375,7 +375,7 @@ const WebhookEventCombobox = () => {
return (
<MultiSelectCombobox
emptySelectionPlaceholder={
<p className="text-muted-foreground font-normal">
<p className="font-normal text-muted-foreground">
<Trans>
<span className="text-muted-foreground/70">Events:</span> All
</Trans>
@@ -1,28 +1,15 @@
import { useMemo } from 'react';
import { msg } from '@lingui/core/macro';
import { Plural, useLingui } from '@lingui/react/macro';
import { Trans } from '@lingui/react/macro';
import type { Webhook } from '@prisma/client';
import {
CheckCircle2Icon,
EditIcon,
Loader,
MoreHorizontalIcon,
ScrollTextIcon,
Trash2Icon,
XCircleIcon,
} from 'lucide-react';
import { DateTime } from 'luxon';
import { EditIcon, Loader, MoreHorizontalIcon, ScrollTextIcon, Trash2Icon } from 'lucide-react';
import { Link } from 'react-router';
import { toFriendlyWebhookEventName } from '@documenso/lib/universal/webhook/to-friendly-webhook-event-name';
import { trpc } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils';
import { Badge } from '@documenso/ui/primitives/badge';
import { Button } from '@documenso/ui/primitives/button';
import { DataTable, type DataTableColumnDef } from '@documenso/ui/primitives/data-table';
import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination';
import {
DropdownMenu,
DropdownMenuContent,
@@ -63,10 +50,10 @@ export default function WebhookPage() {
{
header: t`Webhook`,
cell: ({ row }) => (
<Link to={`/t/${team.url}/settings/webhooks/${row.original.id}`}>
<p className="text-muted-foreground text-xs">{row.original.id}</p>
<Link prefetch="intent" to={`/t/${team.url}/settings/webhooks/${row.original.id}`}>
<p className="text-xs text-muted-foreground">{row.original.id}</p>
<p
className="text-foreground max-w-sm truncate text-xs font-semibold"
className="max-w-sm truncate text-xs font-semibold text-foreground"
title={row.original.webhookUrl}
>
{row.original.webhookUrl}
@@ -130,7 +117,7 @@ export default function WebhookPage() {
enable: isError,
}}
emptyState={
<div className="text-muted-foreground/60 flex h-60 flex-col items-center justify-center gap-y-4">
<div className="flex h-60 flex-col items-center justify-center gap-y-4 text-muted-foreground/60">
<p>
<Trans>
You have no webhooks yet. Your webhooks will be shown here once you create them.
@@ -172,7 +159,7 @@ const WebhookTableActionDropdown = ({ webhook }: { webhook: Webhook }) => {
return (
<DropdownMenu>
<DropdownMenuTrigger data-testid="webhook-table-action-btn">
<MoreHorizontalIcon className="text-muted-foreground h-5 w-5" />
<MoreHorizontalIcon className="h-5 w-5 text-muted-foreground" />
</DropdownMenuTrigger>
<DropdownMenuContent align="end" forceMount>
@@ -181,7 +168,7 @@ const WebhookTableActionDropdown = ({ webhook }: { webhook: Webhook }) => {
</DropdownMenuLabel>
<DropdownMenuItem asChild>
<Link to={`/t/${team.url}/settings/webhooks/${webhook.id}`}>
<Link prefetch="intent" to={`/t/${team.url}/settings/webhooks/${webhook.id}`}>
<ScrollTextIcon className="mr-2 h-4 w-4" />
<Trans>Logs</Trans>
</Link>
@@ -73,7 +73,7 @@ export default function TemplatePage({ params }: Route.ComponentProps) {
}}
primaryButton={
<Button asChild>
<Link to={`/t/${team.url}/templates`}>
<Link prefetch="intent" to={`/t/${team.url}/templates`}>
<Trans>Go back</Trans>
</Link>
</Button>
@@ -114,7 +114,11 @@ export default function TemplatePage({ params }: Route.ComponentProps) {
return (
<div className="mx-auto -mt-4 w-full max-w-screen-xl px-4 md:px-8">
<Link to={templateRootPath} className="flex items-center text-documenso-700 hover:opacity-80">
<Link
prefetch="intent"
to={templateRootPath}
className="flex items-center text-documenso-700 hover:opacity-80"
>
<ChevronLeft className="mr-2 inline-block h-5 w-5" />
<Trans>Templates</Trans>
</Link>
@@ -158,7 +162,7 @@ export default function TemplatePage({ params }: Route.ComponentProps) {
/>
<Button className="w-full" asChild>
<Link to={`${templateRootPath}/${envelope.id}/edit`}>
<Link prefetch="intent" to={`${templateRootPath}/${envelope.id}/edit`}>
<LucideEdit className="mr-1.5 h-3.5 w-3.5" />
<Trans>Edit Template</Trans>
</Link>
@@ -89,7 +89,7 @@ export function ErrorBoundary({ error, params }: Route.ErrorBoundaryProps) {
secondaryButton={null}
primaryButton={
<Button asChild className="w-32">
<Link to={`/t/${params.teamUrl}/templates`}>
<Link prefetch="intent" to={`/t/${params.teamUrl}/templates`}>
<ChevronLeftIcon className="mr-2 h-4 w-4" />
<Trans>Go Back</Trans>
</Link>