mirror of
https://github.com/documenso/documenso.git
synced 2026-07-09 04:24:59 +10:00
refactor: unify settings layout, navigation and copy
Consolidate the personal, team and organisation settings surfaces behind a single system so every page shares one width, one navigation implementation and one copy standard. - add shared SettingsNav (aria-current active state, exact matching for the team General route, non-interactive section labels) and delete the four divergent nav implementations it replaces - constrain the settings content column to max-w-3xl in all three layouts and strip the per-page max-w-2xl/max-w-xl wrappers so forms and tables render identically - rewrite SettingsHeader subtitles to remove "Here you can" / "On this page" filler, normalize title casing and punctuation, and translate the hardcoded team general header - align the billing header with SettingsHeader typography and drop redundant fieldset width constraints in the document/email preference forms - fix the wrong team group success toast, team email meta title, misnamed page exports, untranslated Manage label and Create Team casing mismatch - update manage-organisation e2e assertions for the corrected copy
This commit is contained in:
@@ -155,7 +155,7 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
|
||||
<DialogTrigger onClick={(e) => e.stopPropagation()} asChild={true}>
|
||||
{trigger ?? (
|
||||
<Button className="flex-shrink-0" variant="secondary">
|
||||
<Trans>Create team</Trans>
|
||||
<Trans>Create Team</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</DialogTrigger>
|
||||
@@ -163,7 +163,7 @@ export const TeamCreateDialog = ({ trigger, onCreated, ...props }: TeamCreateDia
|
||||
<DialogContent position="center">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans>Create team</Trans>
|
||||
<Trans>Create Team</Trans>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription>
|
||||
|
||||
@@ -79,7 +79,7 @@ export const TeamGroupCreateDialog = ({ ...props }: TeamGroupCreateDialogProps)
|
||||
|
||||
toast({
|
||||
title: t`Success`,
|
||||
description: t`Team members have been added.`,
|
||||
description: t`Team groups have been added.`,
|
||||
duration: 5000,
|
||||
});
|
||||
|
||||
@@ -87,7 +87,7 @@ export const TeamGroupCreateDialog = ({ ...props }: TeamGroupCreateDialogProps)
|
||||
} catch {
|
||||
toast({
|
||||
title: t`An unknown error occurred`,
|
||||
description: t`We encountered an unknown error while attempting to add team members. Please try again later.`,
|
||||
description: t`We encountered an unknown error while attempting to add the groups. Please try again later.`,
|
||||
variant: 'destructive',
|
||||
});
|
||||
}
|
||||
@@ -134,7 +134,7 @@ export const TeamGroupCreateDialog = ({ ...props }: TeamGroupCreateDialogProps)
|
||||
</DialogTitle>
|
||||
|
||||
<DialogDescription>
|
||||
<Trans>Configure the team roles for each group</Trans>
|
||||
<Trans>Configure the team roles for each group.</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
))
|
||||
@@ -260,7 +260,7 @@ export const TeamGroupCreateDialog = ({ ...props }: TeamGroupCreateDialogProps)
|
||||
</Button>
|
||||
|
||||
<Button type="submit" loading={form.formState.isSubmitting}>
|
||||
<Trans>Create Groups</Trans>
|
||||
<Trans>Add Groups</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</>
|
||||
|
||||
@@ -106,7 +106,7 @@ export const WebhookCreateDialog = ({ trigger, ...props }: WebhookCreateDialogPr
|
||||
<Trans>Create webhook</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<Trans>On this page, you can create a new webhook.</Trans>
|
||||
<Trans>Receive real-time notifications when document events occur.</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ export const DocumentPreferencesForm = ({
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={handleFormSubmit}>
|
||||
<fieldset className="flex h-full max-w-2xl flex-col gap-y-6" disabled={form.formState.isSubmitting}>
|
||||
<fieldset className="flex h-full flex-col gap-y-6" disabled={form.formState.isSubmitting}>
|
||||
{!isPersonalLayoutMode && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -75,7 +75,7 @@ export const EmailPreferencesForm = ({ settings, onFormSubmit, canInherit }: Ema
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={handleFormSubmit}>
|
||||
<fieldset className="flex h-full max-w-2xl flex-col gap-y-6" disabled={form.formState.isSubmitting}>
|
||||
<fieldset className="flex h-full flex-col gap-y-6" disabled={form.formState.isSubmitting}>
|
||||
{organisation.organisationClaim.flags.emailDomains && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { canExecuteOrganisationAction, isPersonalLayout } from '@documenso/lib/utils/organisations';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { BracesIcon, CreditCardIcon, Globe2Icon, Lock, Settings2Icon, User, Users, WebhookIcon } from 'lucide-react';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import { Link, useLocation } from 'react-router';
|
||||
|
||||
export type SettingsDesktopNavProps = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
export const SettingsDesktopNav = ({ className, ...props }: SettingsDesktopNavProps) => {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const { organisations } = useSession();
|
||||
|
||||
const isPersonalLayoutMode = isPersonalLayout(organisations);
|
||||
|
||||
const hasManageableBillingOrgs = organisations.some((org) =>
|
||||
canExecuteOrganisationAction('MANAGE_BILLING', org.currentOrganisationRole),
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-col gap-y-2', className)} {...props}>
|
||||
<Link to="/settings/profile">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/profile') && 'bg-secondary')}
|
||||
>
|
||||
<User className="mr-2 h-5 w-5" />
|
||||
<Trans>Profile</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
{isPersonalLayoutMode && (
|
||||
<>
|
||||
<Link 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">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/document') && 'bg-secondary')}
|
||||
>
|
||||
<Trans>Document</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link className="w-full pl-8" to="/settings/branding">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/branding') && 'bg-secondary')}
|
||||
>
|
||||
<Trans>Branding</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link className="w-full pl-8" to="/settings/email">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/email') && 'bg-secondary')}
|
||||
>
|
||||
<Trans>Email</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link to="/settings/public-profile">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/public-profile') && 'bg-secondary')}
|
||||
>
|
||||
<Globe2Icon className="mr-2 h-5 w-5" />
|
||||
<Trans>Public Profile</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link to="/settings/tokens">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/tokens') && 'bg-secondary')}
|
||||
>
|
||||
<BracesIcon className="mr-2 h-5 w-5" />
|
||||
<Trans>API Tokens</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link to="/settings/webhooks">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/webhooks') && 'bg-secondary')}
|
||||
>
|
||||
<WebhookIcon className="mr-2 h-5 w-5" />
|
||||
<Trans>Webhooks</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Link to="/settings/organisations">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/organisations') && 'bg-secondary')}
|
||||
>
|
||||
<Users className="mr-2 h-5 w-5" />
|
||||
<Trans>Organisations</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
{IS_BILLING_ENABLED() && hasManageableBillingOrgs && (
|
||||
<Link to={isPersonalLayoutMode ? '/settings/billing-personal' : `/settings/billing`}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/billing') && 'bg-secondary')}
|
||||
>
|
||||
<CreditCardIcon className="mr-2 h-5 w-5" />
|
||||
<Trans>Billing</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<Link to="/settings/security">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/security') && 'bg-secondary')}
|
||||
>
|
||||
<Lock className="mr-2 h-5 w-5" />
|
||||
<Trans>Security</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,144 +0,0 @@
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { canExecuteOrganisationAction, isPersonalLayout } from '@documenso/lib/utils/organisations';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import {
|
||||
BracesIcon,
|
||||
CreditCardIcon,
|
||||
Globe2Icon,
|
||||
Lock,
|
||||
MailIcon,
|
||||
PaletteIcon,
|
||||
Settings2Icon,
|
||||
User,
|
||||
Users,
|
||||
WebhookIcon,
|
||||
} from 'lucide-react';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import { Link, useLocation } from 'react-router';
|
||||
|
||||
export type SettingsMobileNavProps = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
export const SettingsMobileNav = ({ className, ...props }: SettingsMobileNavProps) => {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const { organisations } = useSession();
|
||||
|
||||
const isPersonalLayoutMode = isPersonalLayout(organisations);
|
||||
|
||||
const hasManageableBillingOrgs = organisations.some((org) =>
|
||||
canExecuteOrganisationAction('MANAGE_BILLING', org.currentOrganisationRole),
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={cn('flex flex-wrap items-center justify-start gap-x-2 gap-y-4', className)} {...props}>
|
||||
<Link to="/settings/profile">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/profile') && 'bg-secondary')}
|
||||
>
|
||||
<User className="mr-2 h-5 w-5" />
|
||||
<Trans>Profile</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
{isPersonalLayoutMode && (
|
||||
<>
|
||||
<Link to="/settings/document">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/document') && 'bg-secondary')}
|
||||
>
|
||||
<Settings2Icon className="mr-2 h-5 w-5" />
|
||||
<Trans>Document Preferences</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link to="/settings/branding">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/branding') && 'bg-secondary')}
|
||||
>
|
||||
<PaletteIcon className="mr-2 h-5 w-5" />
|
||||
<Trans>Branding Preferences</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link to="/settings/email">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/email') && 'bg-secondary')}
|
||||
>
|
||||
<MailIcon className="mr-2 h-5 w-5" />
|
||||
<Trans>Email Preferences</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link to="/settings/public-profile">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/public-profile') && 'bg-secondary')}
|
||||
>
|
||||
<Globe2Icon className="mr-2 h-5 w-5" />
|
||||
<Trans>Public Profile</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link to="/settings/tokens">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/tokens') && 'bg-secondary')}
|
||||
>
|
||||
<BracesIcon className="mr-2 h-5 w-5" />
|
||||
<Trans>API Tokens</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
<Link to="/settings/webhooks">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/webhooks') && 'bg-secondary')}
|
||||
>
|
||||
<WebhookIcon className="mr-2 h-5 w-5" />
|
||||
<Trans>Webhooks</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Link to="/settings/organisations">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/organisations') && 'bg-secondary')}
|
||||
>
|
||||
<Users className="mr-2 h-5 w-5" />
|
||||
<Trans>Organisations</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
|
||||
{IS_BILLING_ENABLED() && hasManageableBillingOrgs && (
|
||||
<Link to={isPersonalLayoutMode ? '/settings/billing-personal' : `/settings/billing`}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/billing') && 'bg-secondary')}
|
||||
>
|
||||
<CreditCardIcon className="mr-2 h-5 w-5" />
|
||||
<Trans>Billing</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<Link to="/settings/security">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', pathname?.startsWith('/settings/security') && 'bg-secondary')}
|
||||
>
|
||||
<Lock className="mr-2 h-5 w-5" />
|
||||
<Trans>Security</Trans>
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
import { NavLink } from 'react-router';
|
||||
|
||||
export type SettingsNavRoute = {
|
||||
path: string;
|
||||
label: string;
|
||||
icon?: LucideIcon;
|
||||
/**
|
||||
* Renders the route as a non-interactive group label, only visible on desktop.
|
||||
*/
|
||||
isSectionLabel?: boolean;
|
||||
/**
|
||||
* Indents the route under the preceding section label on desktop.
|
||||
*/
|
||||
isSubNav?: boolean;
|
||||
/**
|
||||
* Only mark the route as active on an exact path match.
|
||||
*/
|
||||
end?: boolean;
|
||||
};
|
||||
|
||||
export type SettingsNavProps = {
|
||||
routes: SettingsNavRoute[];
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const SettingsNav = ({ routes, className }: SettingsNavProps) => {
|
||||
return (
|
||||
<nav
|
||||
className={cn(
|
||||
'flex flex-wrap items-center justify-start gap-x-2 gap-y-4 md:w-full md:flex-col md:items-start md:gap-y-2',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{routes.map((route) =>
|
||||
route.isSectionLabel ? (
|
||||
<div
|
||||
key={`${route.path}-${route.label}`}
|
||||
className="flex h-10 w-full items-center px-4 font-medium text-sm max-md:hidden"
|
||||
>
|
||||
{route.icon && <route.icon className="mr-2 h-5 w-5" />}
|
||||
{route.label}
|
||||
</div>
|
||||
) : (
|
||||
<NavLink
|
||||
key={`${route.path}-${route.label}`}
|
||||
to={route.path}
|
||||
end={route.end}
|
||||
className={cn('group justify-start md:w-full', route.isSubNav && 'md:pl-8')}
|
||||
>
|
||||
<Button variant="ghost" className="w-full justify-start group-aria-[current]:bg-secondary">
|
||||
{route.icon && <route.icon className="mr-2 h-5 w-5" />}
|
||||
{route.label}
|
||||
</Button>
|
||||
</NavLink>
|
||||
),
|
||||
)}
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
@@ -102,7 +102,9 @@ 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}`}>Manage</Link>
|
||||
<Link to={`/o/${organisation.url}/settings/email-domains/${row.original.id}`}>
|
||||
<Trans>Manage</Trans>
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
<OrganisationEmailDomainDeleteDialog
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { canExecuteOrganisationAction } from '@documenso/lib/utils/organisations';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
@@ -13,11 +12,12 @@ import {
|
||||
Settings2Icon,
|
||||
ShieldCheckIcon,
|
||||
Users2Icon,
|
||||
UsersIcon,
|
||||
} from 'lucide-react';
|
||||
import { FaUsers } from 'react-icons/fa6';
|
||||
import { Link, NavLink, Outlet } from 'react-router';
|
||||
import { Link, Outlet } from 'react-router';
|
||||
|
||||
import { GenericErrorLayout } from '~/components/general/generic-error-layout';
|
||||
import { SettingsNav, type SettingsNavRoute } from '~/components/general/settings-nav';
|
||||
import { appMetaTags } from '~/utils/meta';
|
||||
|
||||
export function meta() {
|
||||
@@ -30,7 +30,7 @@ export default function SettingsLayout() {
|
||||
const isBillingEnabled = IS_BILLING_ENABLED();
|
||||
const organisation = useCurrentOrganisation();
|
||||
|
||||
const organisationSettingRoutes = [
|
||||
const organisationSettingRoutes: SettingsNavRoute[] = [
|
||||
{
|
||||
path: `/o/${organisation.url}/settings/general`,
|
||||
label: t`General`,
|
||||
@@ -40,7 +40,7 @@ export default function SettingsLayout() {
|
||||
path: `/o/${organisation.url}/settings/document`,
|
||||
label: t`Preferences`,
|
||||
icon: Settings2Icon,
|
||||
hideHighlight: true,
|
||||
isSectionLabel: true,
|
||||
},
|
||||
{
|
||||
path: `/o/${organisation.url}/settings/document`,
|
||||
@@ -65,7 +65,7 @@ export default function SettingsLayout() {
|
||||
{
|
||||
path: `/o/${organisation.url}/settings/teams`,
|
||||
label: t`Teams`,
|
||||
icon: FaUsers,
|
||||
icon: UsersIcon,
|
||||
},
|
||||
{
|
||||
path: `/o/${organisation.url}/settings/members`,
|
||||
@@ -139,32 +139,9 @@ export default function SettingsLayout() {
|
||||
</h1>
|
||||
|
||||
<div className="mt-4 grid grid-cols-12 gap-x-8 md:mt-8">
|
||||
{/* Navigation */}
|
||||
<div
|
||||
className={cn(
|
||||
'col-span-12 mb-8 flex flex-wrap items-center justify-start gap-x-2 gap-y-4 md:col-span-3 md:w-full md:flex-col md:items-start md:gap-y-2',
|
||||
)}
|
||||
>
|
||||
{organisationSettingRoutes.map((route) => (
|
||||
<NavLink
|
||||
to={route.path}
|
||||
className={cn('group w-full justify-start', route.isSubNav && 'pl-8')}
|
||||
key={route.path}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', {
|
||||
'group-aria-[current]:bg-secondary': !route.hideHighlight,
|
||||
})}
|
||||
>
|
||||
{route.icon && <route.icon className="mr-2 h-5 w-5" />}
|
||||
<Trans>{route.label}</Trans>
|
||||
</Button>
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
<SettingsNav routes={organisationSettingRoutes} className="col-span-12 mb-8 md:col-span-3 md:mb-0" />
|
||||
|
||||
<div className="col-span-12 md:col-span-9">
|
||||
<div className="col-span-12 max-w-3xl md:col-span-9">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -89,9 +89,9 @@ export default function TeamsSettingBillingPage() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-row items-end justify-between">
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-semibold text-2xl">
|
||||
<h3 className="font-medium text-lg">
|
||||
<Trans>Billing</Trans>
|
||||
</h3>
|
||||
|
||||
|
||||
@@ -123,13 +123,13 @@ export default function OrganisationSettingsBrandingPage() {
|
||||
const settingsHeaderText = t`Branding Preferences`;
|
||||
|
||||
const settingsHeaderSubtitle = isPersonalLayoutMode
|
||||
? t`Here you can set your general branding preferences.`
|
||||
? t`Set the default branding for documents you send.`
|
||||
: team
|
||||
? t`Here you can set branding preferences for your team.`
|
||||
: t`Here you can set branding preferences for your organisation. Teams will inherit these settings by default.`;
|
||||
? t`Set the default branding for documents sent by your team.`
|
||||
: t`Set the default branding for your organisation. Teams inherit these settings by default.`;
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl">
|
||||
<div>
|
||||
<SettingsHeader title={settingsHeaderText} subtitle={settingsHeaderSubtitle} />
|
||||
|
||||
{organisationWithSettings.organisationClaim.flags.allowCustomBranding || !IS_BILLING_ENABLED() ? (
|
||||
|
||||
@@ -120,11 +120,11 @@ export default function OrganisationSettingsDocumentPage() {
|
||||
|
||||
const settingsHeaderText = t`Document Preferences`;
|
||||
const settingsHeaderSubtitle = isPersonalLayoutMode
|
||||
? t`Here you can set your general document preferences.`
|
||||
: t`Here you can set document preferences for your organisation. Teams will inherit these settings by default.`;
|
||||
? t`Set your default document settings.`
|
||||
: t`Set the default document settings for your organisation. Teams inherit these settings by default.`;
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl">
|
||||
<div>
|
||||
<SettingsHeader title={settingsHeaderText} subtitle={settingsHeaderSubtitle} />
|
||||
|
||||
<section>
|
||||
|
||||
@@ -132,7 +132,7 @@ export default function OrganisationEmailDomainSettingsPage({ params }: Route.Co
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader title={t`Email Domain Settings`} subtitle={t`Manage your email domain settings.`}>
|
||||
<SettingsHeader title={t`Email Domain Settings`} subtitle={t`Manage the email addresses for this domain.`}>
|
||||
<OrganisationEmailCreateDialog emailDomain={emailDomain} />
|
||||
</SettingsHeader>
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export default function OrganisationSettingsEmailDomains() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader title={t`Email Domains`} subtitle={t`Here you can add email domains to your organisation.`}>
|
||||
<SettingsHeader title={t`Email Domains`} subtitle={t`Add and verify email domains for your organisation.`}>
|
||||
{isEmailDomainsEnabled && <OrganisationEmailDomainCreateDialog />}
|
||||
</SettingsHeader>
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { isPersonalLayout } from '@documenso/lib/utils/organisations';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { SpinnerBox } from '@documenso/ui/primitives/spinner';
|
||||
import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
@@ -13,12 +15,16 @@ export function meta() {
|
||||
return appMetaTags(msg`Email Preferences`);
|
||||
}
|
||||
|
||||
export default function OrganisationSettingsGeneral() {
|
||||
export default function OrganisationSettingsEmailPage() {
|
||||
const { t } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
const organisation = useCurrentOrganisation();
|
||||
|
||||
const { organisations } = useSession();
|
||||
|
||||
const isPersonalLayoutMode = isPersonalLayout(organisations);
|
||||
|
||||
const { data: organisationWithSettings, isLoading: isLoadingOrganisation } = trpc.organisation.get.useQuery({
|
||||
organisationReference: organisation.url,
|
||||
});
|
||||
@@ -59,8 +65,15 @@ export default function OrganisationSettingsGeneral() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl">
|
||||
<SettingsHeader title={t`Email Preferences`} subtitle={t`You can manage your email preferences here.`} />
|
||||
<div>
|
||||
<SettingsHeader
|
||||
title={t`Email Preferences`}
|
||||
subtitle={
|
||||
isPersonalLayoutMode
|
||||
? t`Manage your default email settings.`
|
||||
: t`Manage the default email settings for your organisation. Teams inherit these settings by default.`
|
||||
}
|
||||
/>
|
||||
|
||||
<section>
|
||||
<EmailPreferencesForm
|
||||
|
||||
@@ -21,8 +21,8 @@ export default function OrganisationSettingsGeneral() {
|
||||
const organisation = useCurrentOrganisation();
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl">
|
||||
<SettingsHeader title={_(msg`General`)} subtitle={_(msg`Here you can edit your organisation details.`)} />
|
||||
<div>
|
||||
<SettingsHeader title={_(msg`General`)} subtitle={_(msg`Update your organisation's details.`)} />
|
||||
|
||||
<div className="space-y-8">
|
||||
<AvatarImageForm organisation={organisation} />
|
||||
|
||||
@@ -94,7 +94,7 @@ export default function OrganisationGroupSettingsPage({ params }: Route.Componen
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader title={t`Organisation Group Settings`} subtitle={t`Manage your organisation group settings.`}>
|
||||
<SettingsHeader title={t`Group Settings`} subtitle={t`Update the group's name, role and members.`}>
|
||||
<OrganisationGroupDeleteDialog
|
||||
organisationGroupId={groupId}
|
||||
organisationGroupName={group.name || ''}
|
||||
|
||||
@@ -10,7 +10,7 @@ export default function TeamsSettingsMembersPage() {
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader
|
||||
title={t`Custom Organisation Groups`}
|
||||
title={t`Organisation Groups`}
|
||||
subtitle={t`Manage the custom groups of members for your organisation.`}
|
||||
>
|
||||
<OrganisationGroupCreateDialog />
|
||||
|
||||
@@ -46,7 +46,10 @@ export default function TeamsSettingsMembersPage() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader title={_(msg`Organisation Members`)} subtitle={_(msg`Manage the members or invite new members.`)}>
|
||||
<SettingsHeader
|
||||
title={_(msg`Organisation Members`)}
|
||||
subtitle={_(msg`Manage the members and invitations for your organisation.`)}
|
||||
>
|
||||
<OrganisationMemberInviteDialog />
|
||||
</SettingsHeader>
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ export default function OrganisationSettingSSOLoginPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl">
|
||||
<div>
|
||||
<SettingsHeader
|
||||
title={t`Organisation SSO Portal`}
|
||||
subtitle={t`Manage a custom SSO login portal for your organisation.`}
|
||||
|
||||
@@ -1,9 +1,21 @@
|
||||
import { useSession } from '@documenso/lib/client-only/providers/session';
|
||||
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
|
||||
import { canExecuteOrganisationAction, isPersonalLayout } from '@documenso/lib/utils/organisations';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import {
|
||||
BracesIcon,
|
||||
CreditCardIcon,
|
||||
Globe2Icon,
|
||||
LockIcon,
|
||||
Settings2Icon,
|
||||
UserIcon,
|
||||
UsersIcon,
|
||||
WebhookIcon,
|
||||
} from 'lucide-react';
|
||||
import { Outlet } from 'react-router';
|
||||
|
||||
import { SettingsDesktopNav } from '~/components/general/settings-nav-desktop';
|
||||
import { SettingsMobileNav } from '~/components/general/settings-nav-mobile';
|
||||
import { SettingsNav, type SettingsNavRoute } from '~/components/general/settings-nav';
|
||||
import { appMetaTags } from '~/utils/meta';
|
||||
|
||||
export function meta() {
|
||||
@@ -11,6 +23,83 @@ export function meta() {
|
||||
}
|
||||
|
||||
export default function SettingsLayout() {
|
||||
const { t } = useLingui();
|
||||
|
||||
const { organisations } = useSession();
|
||||
|
||||
const isPersonalLayoutMode = isPersonalLayout(organisations);
|
||||
|
||||
const hasManageableBillingOrgs = organisations.some((org) =>
|
||||
canExecuteOrganisationAction('MANAGE_BILLING', org.currentOrganisationRole),
|
||||
);
|
||||
|
||||
const settingRoutes: SettingsNavRoute[] = [
|
||||
{
|
||||
path: '/settings/profile',
|
||||
label: t`Profile`,
|
||||
icon: UserIcon,
|
||||
},
|
||||
...(isPersonalLayoutMode
|
||||
? [
|
||||
{
|
||||
path: '/settings/document',
|
||||
label: t`Preferences`,
|
||||
icon: Settings2Icon,
|
||||
isSectionLabel: true,
|
||||
},
|
||||
{
|
||||
path: '/settings/document',
|
||||
label: t`Document`,
|
||||
isSubNav: true,
|
||||
},
|
||||
{
|
||||
path: '/settings/branding',
|
||||
label: t`Branding`,
|
||||
isSubNav: true,
|
||||
},
|
||||
{
|
||||
path: '/settings/email',
|
||||
label: t`Email`,
|
||||
isSubNav: true,
|
||||
},
|
||||
{
|
||||
path: '/settings/public-profile',
|
||||
label: t`Public Profile`,
|
||||
icon: Globe2Icon,
|
||||
},
|
||||
{
|
||||
path: '/settings/tokens',
|
||||
label: t`API Tokens`,
|
||||
icon: BracesIcon,
|
||||
},
|
||||
{
|
||||
path: '/settings/webhooks',
|
||||
label: t`Webhooks`,
|
||||
icon: WebhookIcon,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
path: '/settings/organisations',
|
||||
label: t`Organisations`,
|
||||
icon: UsersIcon,
|
||||
},
|
||||
...(IS_BILLING_ENABLED() && hasManageableBillingOrgs
|
||||
? [
|
||||
{
|
||||
path: isPersonalLayoutMode ? '/settings/billing-personal' : '/settings/billing',
|
||||
label: t`Billing`,
|
||||
icon: CreditCardIcon,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
path: '/settings/security',
|
||||
label: t`Security`,
|
||||
icon: LockIcon,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-screen-xl px-4 md:px-8">
|
||||
<h1 className="font-semibold text-4xl">
|
||||
@@ -18,10 +107,9 @@ export default function SettingsLayout() {
|
||||
</h1>
|
||||
|
||||
<div className="mt-4 grid grid-cols-12 gap-x-8 md:mt-8">
|
||||
<SettingsDesktopNav className="hidden md:col-span-3 md:flex" />
|
||||
<SettingsMobileNav className="col-span-12 mb-8 md:hidden" />
|
||||
<SettingsNav routes={settingRoutes} className="col-span-12 mb-8 md:col-span-3 md:mb-0" />
|
||||
|
||||
<div className="col-span-12 md:col-span-9">
|
||||
<div className="col-span-12 max-w-3xl md:col-span-9">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,10 +11,7 @@ export default function TeamsSettingsPage() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader
|
||||
title={_(msg`Organisations`)}
|
||||
subtitle={_(msg`Manage all organisations you are currently associated with.`)}
|
||||
>
|
||||
<SettingsHeader title={_(msg`Organisations`)} subtitle={_(msg`Manage the organisations you belong to.`)}>
|
||||
<OrganisationCreateDialog />
|
||||
</SettingsHeader>
|
||||
|
||||
|
||||
@@ -27,14 +27,14 @@ export default function SettingsProfile() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader title={_(msg`Profile`)} subtitle={_(msg`Here you can edit your personal details.`)} />
|
||||
<SettingsHeader title={_(msg`Profile`)} subtitle={_(msg`Update your personal details.`)} />
|
||||
|
||||
<AvatarImageForm className="mb-8 max-w-xl" />
|
||||
<ProfileForm className="mb-8 max-w-xl" />
|
||||
<AvatarImageForm className="mb-8" />
|
||||
<ProfileForm className="mb-8" />
|
||||
|
||||
<hr className="my-4 max-w-xl" />
|
||||
<hr className="my-4" />
|
||||
|
||||
<div className="max-w-xl space-y-8">
|
||||
<div className="space-y-8">
|
||||
<AnimatePresence>
|
||||
{(!isPersonalLayoutMode || user.email !== teamEmail?.email) && teamEmail && (
|
||||
<AnimateGenericFadeInOut>
|
||||
|
||||
@@ -62,10 +62,7 @@ export default function SettingsSecurity({ loaderData }: Route.ComponentProps) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader
|
||||
title={_(msg`Security`)}
|
||||
subtitle={_(msg`Here you can manage your password and security settings.`)}
|
||||
/>
|
||||
<SettingsHeader title={_(msg`Security`)} subtitle={_(msg`Manage your password and security settings.`)} />
|
||||
{hasEmailPasswordAccount && (
|
||||
<>
|
||||
<PasswordForm user={user} />
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function SettingsSecurityActivity() {
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader
|
||||
title={_(msg`Security activity`)}
|
||||
title={_(msg`Security Activity`)}
|
||||
subtitle={_(msg`View all security activity related to your account.`)}
|
||||
hideDivider={true}
|
||||
/>
|
||||
|
||||
@@ -94,7 +94,7 @@ export default function SettingsSecuritySessions() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader title={t`Active sessions`} subtitle={t`View and manage all active sessions for your account.`}>
|
||||
<SettingsHeader title={t`Active Sessions`} subtitle={t`View and manage all active sessions for your account.`}>
|
||||
<SessionLogoutAllDialog onSuccess={refetch} disabled={results.length === 1 || isLoading} />
|
||||
</SettingsHeader>
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { extractInitials } from '@documenso/lib/utils/recipient-formatter';
|
||||
import { canExecuteTeamAction } from '@documenso/lib/utils/teams';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
|
||||
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { CheckCircle2, Clock } from 'lucide-react';
|
||||
import { match, P } from 'ts-pattern';
|
||||
|
||||
@@ -33,13 +33,15 @@ export async function loader({ request, params }: Route.LoaderArgs) {
|
||||
}
|
||||
|
||||
export default function TeamsSettingsPage({ loaderData }: Route.ComponentProps) {
|
||||
const { t } = useLingui();
|
||||
|
||||
const { team } = loaderData;
|
||||
|
||||
const currentTeam = useCurrentTeam();
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl">
|
||||
<SettingsHeader title="General settings" subtitle="Here you can edit your team's details." />
|
||||
<div>
|
||||
<SettingsHeader title={t`General`} subtitle={t`Update your team's details.`} />
|
||||
|
||||
<AvatarImageForm team={currentTeam} className="mb-8" />
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { getSession } from '@documenso/auth/server/lib/utils/get-session';
|
||||
import { getTeamByUrl } from '@documenso/lib/server-only/team/get-team';
|
||||
import { canExecuteTeamAction } from '@documenso/lib/utils/teams';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { BracesIcon, Globe2Icon, GroupIcon, Settings2Icon, SettingsIcon, Users2Icon, WebhookIcon } from 'lucide-react';
|
||||
import { Link, NavLink, Outlet, redirect } from 'react-router';
|
||||
import { Link, Outlet, redirect } from 'react-router';
|
||||
|
||||
import { GenericErrorLayout } from '~/components/general/generic-error-layout';
|
||||
import { SettingsNav, type SettingsNavRoute } from '~/components/general/settings-nav';
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
import { appMetaTags } from '~/utils/meta';
|
||||
|
||||
@@ -40,17 +40,18 @@ export default function TeamsSettingsLayout() {
|
||||
|
||||
const team = useCurrentTeam();
|
||||
|
||||
const teamSettingRoutes = [
|
||||
const teamSettingRoutes: SettingsNavRoute[] = [
|
||||
{
|
||||
path: `/t/${team.url}/settings`,
|
||||
label: t`General`,
|
||||
icon: SettingsIcon,
|
||||
end: true,
|
||||
},
|
||||
{
|
||||
path: `/t/${team.url}/settings/document`,
|
||||
label: t`Preferences`,
|
||||
icon: Settings2Icon,
|
||||
isSubNavParent: true,
|
||||
isSectionLabel: true,
|
||||
},
|
||||
{
|
||||
path: `/t/${team.url}/settings/document`,
|
||||
@@ -124,31 +125,9 @@ export default function TeamsSettingsLayout() {
|
||||
</h1>
|
||||
|
||||
<div className="mt-4 grid grid-cols-12 gap-x-8 md:mt-8">
|
||||
<div
|
||||
className={cn(
|
||||
'col-span-12 mb-8 flex flex-wrap items-center justify-start gap-x-2 gap-y-4 md:col-span-3 md:w-full md:flex-col md:items-start md:gap-y-2',
|
||||
)}
|
||||
>
|
||||
{teamSettingRoutes.map((route) => (
|
||||
<NavLink
|
||||
to={route.path}
|
||||
className={cn('group w-full justify-start', route.isSubNav && 'pl-8')}
|
||||
key={route.path}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn('w-full justify-start', {
|
||||
'group-aria-[current]:bg-secondary': !route.isSubNavParent,
|
||||
})}
|
||||
>
|
||||
{route.icon && <route.icon className="mr-2 h-5 w-5" />}
|
||||
<Trans>{route.label}</Trans>
|
||||
</Button>
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
<SettingsNav routes={teamSettingRoutes} className="col-span-12 mb-8 md:col-span-3 md:mb-0" />
|
||||
|
||||
<div className="col-span-12 md:col-span-9">
|
||||
<div className="col-span-12 max-w-3xl md:col-span-9">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -115,10 +115,10 @@ export default function TeamsSettingsPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl">
|
||||
<div>
|
||||
<SettingsHeader
|
||||
title={t`Branding Preferences`}
|
||||
subtitle={t`Here you can set preferences and defaults for branding.`}
|
||||
subtitle={t`Set the default branding for documents sent by your team.`}
|
||||
/>
|
||||
|
||||
{canConfigureBranding ? (
|
||||
|
||||
@@ -110,11 +110,8 @@ export default function TeamsSettingsPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl">
|
||||
<SettingsHeader
|
||||
title={t`Document Preferences`}
|
||||
subtitle={t`Here you can set preferences and defaults for your team.`}
|
||||
/>
|
||||
<div>
|
||||
<SettingsHeader title={t`Document Preferences`} subtitle={t`Set the default document settings for your team.`} />
|
||||
|
||||
<section>
|
||||
<DocumentPreferencesForm
|
||||
|
||||
@@ -10,10 +10,10 @@ import { useCurrentTeam } from '~/providers/team';
|
||||
import { appMetaTags } from '~/utils/meta';
|
||||
|
||||
export function meta() {
|
||||
return appMetaTags(msg`Settings`);
|
||||
return appMetaTags(msg`Email Preferences`);
|
||||
}
|
||||
|
||||
export default function TeamEmailSettingsGeneral() {
|
||||
export default function TeamEmailSettingsPage() {
|
||||
const { t } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -59,8 +59,8 @@ export default function TeamEmailSettingsGeneral() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl">
|
||||
<SettingsHeader title={t`Email Preferences`} subtitle={t`You can manage your email preferences here.`} />
|
||||
<div>
|
||||
<SettingsHeader title={t`Email Preferences`} subtitle={t`Manage the default email settings for your team.`} />
|
||||
|
||||
<section>
|
||||
<EmailPreferencesForm
|
||||
|
||||
@@ -128,11 +128,8 @@ export default function PublicProfilePage({ loaderData }: Route.ComponentProps)
|
||||
}, [profile.enabled]);
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl">
|
||||
<SettingsHeader
|
||||
title={t`Public Profile`}
|
||||
subtitle={t`You can choose to enable or disable the profile for public view.`}
|
||||
>
|
||||
<div>
|
||||
<SettingsHeader title={t`Public Profile`} subtitle={t`Manage your public profile and the templates shown on it.`}>
|
||||
<Tooltip open={isTooltipOpen} onOpenChange={setIsTooltipOpen}>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
@@ -190,7 +187,7 @@ export default function PublicProfilePage({ loaderData }: Route.ComponentProps)
|
||||
<div className="mt-4">
|
||||
<SettingsHeader
|
||||
title={t`Templates`}
|
||||
subtitle={t`Show templates in your public profile for your audience to sign and get started quickly`}
|
||||
subtitle={t`Show templates in your public profile for your audience to sign and get started quickly.`}
|
||||
hideDivider={true}
|
||||
className="mt-8 [&>*>h3]:text-base"
|
||||
>
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function ApiTokensPage() {
|
||||
title={<Trans>API Tokens</Trans>}
|
||||
subtitle={
|
||||
<Trans>
|
||||
On this page, you can create and manage API tokens. See our{' '}
|
||||
Create and manage API tokens. See our{' '}
|
||||
<a
|
||||
className="text-primary underline"
|
||||
href={'https://docs.documenso.com/developers/public-api'}
|
||||
@@ -57,7 +57,7 @@ export default function ApiTokensPage() {
|
||||
</Alert>
|
||||
) : (
|
||||
<>
|
||||
<ApiTokenForm className="max-w-xl" tokens={tokens} />
|
||||
<ApiTokenForm tokens={tokens} />
|
||||
|
||||
<hr className="mt-8 mb-4" />
|
||||
|
||||
@@ -74,7 +74,7 @@ export default function ApiTokensPage() {
|
||||
)}
|
||||
|
||||
{tokens && tokens.length > 0 && (
|
||||
<div className="mt-4 flex max-w-xl flex-col gap-y-4">
|
||||
<div className="mt-4 flex flex-col gap-y-4">
|
||||
{tokens.map((token) => (
|
||||
<div key={token.id} className="rounded-lg border border-border p-4">
|
||||
<div className="flex items-center justify-between gap-x-4">
|
||||
|
||||
@@ -88,10 +88,7 @@ export default function WebhookPage() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader
|
||||
title={t`Webhooks`}
|
||||
subtitle={t`On this page, you can create new Webhooks and manage the existing ones.`}
|
||||
>
|
||||
<SettingsHeader title={t`Webhooks`} subtitle={t`Create and manage webhooks for document events.`}>
|
||||
<WebhookCreateDialog />
|
||||
</SettingsHeader>
|
||||
{isLoading && (
|
||||
|
||||
@@ -355,8 +355,8 @@ test('[ORGANISATIONS]: manage groups and members', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
await page.getByRole('combobox').click();
|
||||
await page.getByRole('option', { name: 'Manager' }).click();
|
||||
await page.getByRole('button', { name: 'Create Groups' }).click();
|
||||
await expect(page.getByText('Team members have been added').first()).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Add Groups' }).click();
|
||||
await expect(page.getByText('Team groups have been added').first()).toBeVisible();
|
||||
|
||||
// Assign CUSTOM_GROUP_B to TeamA
|
||||
await page.goto(`/t/${teamA}/settings/groups`);
|
||||
@@ -368,8 +368,8 @@ test('[ORGANISATIONS]: manage groups and members', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Next' }).click();
|
||||
await page.getByRole('combobox').click();
|
||||
await page.getByRole('option', { name: 'Manager' }).click();
|
||||
await page.getByRole('button', { name: 'Create Groups' }).click();
|
||||
await expect(page.getByText('Team members have been added').first()).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Add Groups' }).click();
|
||||
await expect(page.getByText('Team groups have been added').first()).toBeVisible();
|
||||
|
||||
// Update CUSTOM_GROUP_B
|
||||
const updateBtn = page.getByRole('row', { name: 'CUSTOM_GROUP_B' }).getByRole('button');
|
||||
|
||||
Reference in New Issue
Block a user