feat: unify settings (#3128)

This commit is contained in:
David Nguyen
2026-08-09 16:00:55 +10:00
committed by GitHub
parent f0ab7c112e
commit d6cf3fec4b
120 changed files with 4181 additions and 1859 deletions
@@ -1,54 +0,0 @@
import { OrganisationProvider } from '@documenso/lib/client-only/providers/organisation';
import { useSession } from '@documenso/lib/client-only/providers/session';
import { isPersonalLayout } from '@documenso/lib/utils/organisations';
import { TrpcProvider } from '@documenso/trpc/react';
import { useEffect } from 'react';
import { Outlet, useNavigate } from 'react-router';
import { TeamProvider } from '~/providers/team';
/**
* These routes should only render if the user has:
*
* - 1 Personal organisation
* - Nothing else
*
* This removes the UX complexity for users who only have a single personal organisation, instead of showing them multiple settings pages:
*
* - Organisation settings
* - Teams settings
*/
export default function Layout() {
const { organisations } = useSession();
const navigate = useNavigate();
const isPersonalLayoutMode = isPersonalLayout(organisations);
const currentOrganisation = organisations[0];
const team = currentOrganisation?.teams[0] || null;
useEffect(() => {
if (!isPersonalLayoutMode || !team) {
void navigate('/settings/profile');
}
}, []);
if (!isPersonalLayoutMode || !team) {
return null;
}
const trpcHeaders = {
'x-team-Id': team.id.toString(),
};
return (
<TrpcProvider headers={trpcHeaders}>
<OrganisationProvider organisation={currentOrganisation}>
<TeamProvider team={team}>
<Outlet />
</TeamProvider>
</OrganisationProvider>
</TrpcProvider>
);
}
@@ -1,5 +0,0 @@
import BillingPage, { meta } from '../../o.$orgUrl.settings.billing';
export { meta };
export default BillingPage;
@@ -1,5 +0,0 @@
import BrandingPage, { meta } from '../../o.$orgUrl.settings.branding';
export { meta };
export default BrandingPage;
@@ -1,5 +0,0 @@
import DocumentPage, { loader, meta } from '../../o.$orgUrl.settings.document';
export { loader, meta };
export default DocumentPage;
@@ -1,5 +0,0 @@
import EmailPage, { meta } from '../../o.$orgUrl.settings.email';
export { meta };
export default EmailPage;
@@ -1,5 +0,0 @@
import PublicProfilePage, { loader, meta } from '../../t.$teamUrl+/settings.public-profile';
export { loader, meta };
export default PublicProfilePage;
@@ -1,5 +0,0 @@
import TokensPage, { meta } from '../../t.$teamUrl+/settings.tokens';
export { meta };
export default TokensPage;
@@ -1,5 +0,0 @@
import WebhookPage, { meta } from '../../t.$teamUrl+/settings.webhooks.$id._index';
export { meta };
export default WebhookPage;
@@ -1,5 +0,0 @@
import WebhookPage, { meta } from '../../t.$teamUrl+/settings.webhooks._index';
export { meta };
export default WebhookPage;
@@ -1,30 +1,27 @@
import { extractCookieFromHeaders } from '@documenso/auth/server/lib/utils/cookies';
import type { RouteHandle } from '@documenso/lib/client-only/hooks/use-child-route-flags';
import { PREFERRED_TEAM_URL_COOKIE } from '@documenso/lib/constants/cookies';
import { msg } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import { Outlet } from 'react-router';
import { SettingsDesktopNav } from '~/components/general/settings-nav-desktop';
import { SettingsMobileNav } from '~/components/general/settings-nav-mobile';
import { UnifiedSettingsLayout } from '~/components/general/unified-settings-layout';
import { appMetaTags } from '~/utils/meta';
import type { Route } from './+types/_layout';
export function meta() {
return appMetaTags(msg`Settings`);
}
export default function SettingsLayout() {
return (
<div className="mx-auto w-full max-w-screen-xl px-4 md:px-8">
<h1 className="font-semibold text-4xl">
<Trans>Settings</Trans>
</h1>
export const handle: RouteHandle = {
layoutMode: 'settings',
};
<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" />
export function loader({ request }: Route.LoaderArgs) {
return {
preferredTeamUrl: extractCookieFromHeaders(PREFERRED_TEAM_URL_COOKIE, request.headers),
};
}
<div className="col-span-12 md:col-span-9">
<Outlet />
</div>
</div>
</div>
);
export default function SettingsLayout({ loaderData }: Route.ComponentProps) {
return <UnifiedSettingsLayout activeScope="account" preferredTeamUrl={loaderData.preferredTeamUrl} />;
}
@@ -17,6 +17,7 @@ export default function SettingsBilling() {
<SettingsHeader
title={t`Billing`}
subtitle={t`Manage billing and subscriptions for organisations where you have billing management permissions.`}
hideDivider
/>
<UserBillingOrganisationsTable />
@@ -14,6 +14,7 @@ export default function TeamsSettingsPage() {
<SettingsHeader
title={_(msg`Organisations`)}
subtitle={_(msg`Manage all organisations you are currently associated with.`)}
hideDivider
>
<OrganisationCreateDialog />
</SettingsHeader>
@@ -32,8 +32,6 @@ export default function SettingsProfile() {
<AvatarImageForm className="mb-8 max-w-xl" />
<ProfileForm className="mb-8 max-w-xl" />
<hr className="my-4 max-w-xl" />
<div className="max-w-xl space-y-8">
<AnimatePresence>
{(!isPersonalLayoutMode || user.email !== teamEmail?.email) && teamEmail && (
@@ -66,13 +66,7 @@ export default function SettingsSecurity({ loaderData }: Route.ComponentProps) {
title={_(msg`Security`)}
subtitle={_(msg`Here you can manage your password and security settings.`)}
/>
{hasEmailPasswordAccount && (
<>
<PasswordForm user={user} />
<hr className="mt-6 border-border/50" />
</>
)}
{hasEmailPasswordAccount && <PasswordForm user={user} />}
<Alert className="mt-6 flex flex-col justify-between p-6 sm:flex-row sm:items-center" variant="neutral">
<div className="mb-4 sm:mb-0">
@@ -15,9 +15,9 @@ export default function SettingsSecurityActivity() {
return (
<div>
<SettingsHeader
hideDivider
title={_(msg`Security activity`)}
subtitle={_(msg`View all security activity related to your account.`)}
hideDivider={true}
/>
<div className="mt-4">
@@ -15,7 +15,7 @@ export default function SettingsPasskeys() {
return (
<div>
<SettingsHeader title={_(msg`Passkeys`)} subtitle={_(msg`Manage your passkeys.`)} hideDivider={true}>
<SettingsHeader hideDivider title={_(msg`Passkeys`)} subtitle={_(msg`Manage your passkeys.`)}>
<PasskeyCreateDialog />
</SettingsHeader>