chore: flattern routes

This commit is contained in:
David Nguyen
2025-02-21 15:53:23 +11:00
parent 483d7caef7
commit 01dccb7916
40 changed files with 1749 additions and 1522 deletions

View File

@ -33,7 +33,7 @@ import {
import { StackAvatarsWithTooltip } from '~/components/general/stack-avatars-with-tooltip';
import { superLoaderJson, useSuperLoaderData } from '~/utils/super-json-loader';
import type { Route } from './+types/$id._index';
import type { Route } from './+types/documents.$id._index';
export async function loader({ params, request }: Route.LoaderArgs) {
const { user } = await getSession(request);

View File

@ -16,7 +16,7 @@ import { DocumentStatus } from '~/components/general/document/document-status';
import { StackAvatarsWithTooltip } from '~/components/general/stack-avatars-with-tooltip';
import { superLoaderJson, useSuperLoaderData } from '~/utils/super-json-loader';
import type { Route } from './+types/$id.edit';
import type { Route } from './+types/documents.$id.edit';
export async function loader({ params, request }: Route.LoaderArgs) {
const { user } = await getSession(request);

View File

@ -22,7 +22,7 @@ import {
} from '~/components/general/document/document-status';
import { DocumentLogsTable } from '~/components/tables/document-logs-table';
import type { Route } from './+types/$id.logs';
import type { Route } from './+types/documents.$id.logs';
export async function loader({ params, request }: Route.LoaderArgs) {
const { user } = await getSession(request);

View File

@ -21,10 +21,10 @@ import { ManagePublicTemplateDialog } from '~/components/dialogs/public-profile-
import type { TPublicProfileFormSchema } from '~/components/forms/public-profile-form';
import { PublicProfileForm } from '~/components/forms/public-profile-form';
import { SettingsHeader } from '~/components/general/settings-header';
import { SettingsPublicProfileTemplatesTable } from '~/components/tables/settings-public-profile-templates-table';
import { useOptionalCurrentTeam } from '~/providers/team';
import { SettingsPublicProfileTemplatesTable } from '../../../../components/tables/settings-public-profile-templates-table';
import type { Route } from './+types/index';
import type { Route } from './+types/public-profile';
type DirectTemplate = FindTemplateRow & {
directLink: Pick<TemplateDirectLink, 'token' | 'enabled'>;

View File

@ -16,7 +16,7 @@ import { PasswordForm } from '~/components/forms/password';
import { SettingsHeader } from '~/components/general/settings-header';
import { appMetaTags } from '~/utils/meta';
import type { Route } from './+types';
import type { Route } from './+types/security';
export function meta() {
return appMetaTags('Security');

View File

@ -1,125 +0,0 @@
import { useState } from 'react';
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Trans } from '@lingui/react/macro';
import type { TeamEmail } from '@prisma/client';
import { trpc } from '@documenso/trpc/react';
import { Alert, AlertDescription, AlertTitle } from '@documenso/ui/primitives/alert';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
export type TeamEmailUsageProps = {
teamEmail: TeamEmail & { team: { name: string; url: string } };
};
export const TeamEmailUsage = ({ teamEmail }: TeamEmailUsageProps) => {
const [open, setOpen] = useState(false);
const { _ } = useLingui();
const { toast } = useToast();
const { mutateAsync: deleteTeamEmail, isPending: isDeletingTeamEmail } =
trpc.team.deleteTeamEmail.useMutation({
onSuccess: () => {
toast({
title: _(msg`Success`),
description: _(msg`You have successfully revoked access.`),
duration: 5000,
});
},
onError: () => {
toast({
title: _(msg`Something went wrong`),
description: _(
msg`We encountered an unknown error while attempting to revoke access. Please try again or contact support.`,
),
variant: 'destructive',
duration: 10000,
});
},
});
return (
<Alert variant="neutral" className="flex flex-row items-center justify-between p-6">
<div>
<AlertTitle className="mb-0">
<Trans>Team Email</Trans>
</AlertTitle>
<AlertDescription>
<p>
<Trans>
Your email is currently being used by team{' '}
<span className="font-semibold">{teamEmail.team.name}</span> ({teamEmail.team.url}
).
</Trans>
</p>
<p className="mt-1">
<Trans>They have permission on your behalf to:</Trans>
</p>
<ul className="mt-0.5 list-inside list-disc">
<li>
<Trans>Display your name and email in documents</Trans>
</li>
<li>
<Trans>View all documents sent to your account</Trans>
</li>
</ul>
</AlertDescription>
</div>
<Dialog open={open} onOpenChange={(value) => !isDeletingTeamEmail && setOpen(value)}>
<DialogTrigger asChild>
<Button variant="destructive">
<Trans>Revoke access</Trans>
</Button>
</DialogTrigger>
<DialogContent position="center">
<DialogHeader>
<DialogTitle>
<Trans>Are you sure?</Trans>
</DialogTitle>
<DialogDescription className="mt-4">
<Trans>
You are about to revoke access for team{' '}
<span className="font-semibold">{teamEmail.team.name}</span> ({teamEmail.team.url})
to use your email.
</Trans>
</DialogDescription>
</DialogHeader>
<fieldset disabled={isDeletingTeamEmail}>
<DialogFooter>
<Button type="button" variant="secondary" onClick={() => setOpen(false)}>
<Trans>Cancel</Trans>
</Button>
<Button
type="submit"
variant="destructive"
loading={isDeletingTeamEmail}
onClick={async () => deleteTeamEmail({ teamId: teamEmail.teamId })}
>
<Trans>Revoke</Trans>
</Button>
</DialogFooter>
</fieldset>
</DialogContent>
</Dialog>
</Alert>
);
};

View File

@ -1,187 +0,0 @@
import { msg } from '@lingui/core/macro';
import { useLingui } from '@lingui/react';
import { Plural, Trans } from '@lingui/react/macro';
import { AnimatePresence } from 'framer-motion';
import { BellIcon } from 'lucide-react';
import { formatAvatarUrl } from '@documenso/lib/utils/avatars';
import { formatTeamUrl } from '@documenso/lib/utils/teams';
import { trpc } from '@documenso/trpc/react';
import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animate-generic-fade-in-out';
import { Alert, AlertDescription } from '@documenso/ui/primitives/alert';
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
import { Button } from '@documenso/ui/primitives/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@documenso/ui/primitives/dialog';
import { useToast } from '@documenso/ui/primitives/use-toast';
export const TeamInvitations = () => {
const { data, isLoading } = trpc.team.getTeamInvitations.useQuery();
return (
<AnimatePresence>
{data && data.length > 0 && !isLoading && (
<AnimateGenericFadeInOut>
<Alert variant="secondary">
<div className="flex h-full flex-row items-center p-2">
<BellIcon className="mr-4 h-5 w-5 text-blue-800" />
<AlertDescription className="mr-2">
<Plural
value={data.length}
one={
<span>
You have <strong>1</strong> pending team invitation
</span>
}
other={
<span>
You have <strong>#</strong> pending team invitations
</span>
}
/>
</AlertDescription>
<Dialog>
<DialogTrigger asChild>
<button className="ml-auto text-sm font-medium text-blue-700 hover:text-blue-600">
<Trans>View invites</Trans>
</button>
</DialogTrigger>
<DialogContent position="center">
<DialogHeader>
<DialogTitle>
<Trans>Pending invitations</Trans>
</DialogTitle>
<DialogDescription className="mt-4">
<Plural
value={data.length}
one={
<span>
You have <strong>1</strong> pending team invitation
</span>
}
other={
<span>
You have <strong>#</strong> pending team invitations
</span>
}
/>
</DialogDescription>
</DialogHeader>
<ul className="-mx-6 -mb-6 max-h-[80vh] divide-y overflow-auto px-6 pb-6 xl:max-h-[70vh]">
{data.map((invitation) => (
<li key={invitation.teamId}>
<AvatarWithText
avatarSrc={formatAvatarUrl(invitation.team.avatarImageId)}
className="w-full max-w-none py-4"
avatarFallback={invitation.team.name.slice(0, 1)}
primaryText={
<span className="text-foreground/80 font-semibold">
{invitation.team.name}
</span>
}
secondaryText={formatTeamUrl(invitation.team.url)}
rightSideComponent={
<div className="ml-auto space-x-2">
<DeclineTeamInvitationButton teamId={invitation.team.id} />
<AcceptTeamInvitationButton teamId={invitation.team.id} />
</div>
}
/>
</li>
))}
</ul>
</DialogContent>
</Dialog>
</div>
</Alert>
</AnimateGenericFadeInOut>
)}
</AnimatePresence>
);
};
const AcceptTeamInvitationButton = ({ teamId }: { teamId: number }) => {
const { _ } = useLingui();
const { toast } = useToast();
const {
mutateAsync: acceptTeamInvitation,
isPending,
isSuccess,
} = trpc.team.acceptTeamInvitation.useMutation({
onSuccess: () => {
toast({
title: _(msg`Success`),
description: _(msg`Accepted team invitation`),
duration: 5000,
});
},
onError: () => {
toast({
title: _(msg`Something went wrong`),
description: _(msg`Unable to join this team at this time.`),
variant: 'destructive',
duration: 10000,
});
},
});
return (
<Button
onClick={async () => acceptTeamInvitation({ teamId })}
loading={isPending}
disabled={isPending || isSuccess}
>
<Trans>Accept</Trans>
</Button>
);
};
const DeclineTeamInvitationButton = ({ teamId }: { teamId: number }) => {
const { _ } = useLingui();
const { toast } = useToast();
const {
mutateAsync: declineTeamInvitation,
isPending,
isSuccess,
} = trpc.team.declineTeamInvitation.useMutation({
onSuccess: () => {
toast({
title: _(msg`Success`),
description: _(msg`Declined team invitation`),
duration: 5000,
});
},
onError: () => {
toast({
title: _(msg`Something went wrong`),
description: _(msg`Unable to decline this team invitation at this time.`),
variant: 'destructive',
duration: 10000,
});
},
});
return (
<Button
onClick={async () => declineTeamInvitation({ teamId })}
loading={isPending}
disabled={isPending || isSuccess}
variant="ghost"
>
<Trans>Decline</Trans>
</Button>
);
};

View File

@ -7,11 +7,10 @@ import { AnimateGenericFadeInOut } from '@documenso/ui/components/animate/animat
import { TeamCreateDialog } from '~/components/dialogs/team-create-dialog';
import { SettingsHeader } from '~/components/general/settings-header';
import { TeamEmailUsage } from '~/components/general/teams/team-email-usage';
import { TeamInvitations } from '~/components/general/teams/team-invitations';
import { UserSettingsTeamsPageDataTable } from '~/components/tables/user-settings-teams-page-table';
import { TeamEmailUsage } from './team-email-usage';
import { TeamInvitations } from './team-invitations';
export default function TeamsSettingsPage() {
const { _ } = useLingui();

View File

@ -1,4 +1,4 @@
import DocumentPage, { loader } from '~/routes/_authenticated+/documents+/$id._index';
import DocumentPage, { loader } from '~/routes/_authenticated+/documents.$id._index';
export { loader };

View File

@ -1,4 +1,4 @@
import DocumentEditPage, { loader } from '~/routes/_authenticated+/documents+/$id.edit';
import DocumentEditPage, { loader } from '~/routes/_authenticated+/documents.$id.edit';
export { loader };

View File

@ -1,4 +1,4 @@
import DocumentLogsPage, { loader } from '~/routes/_authenticated+/documents+/$id.logs';
import DocumentLogsPage, { loader } from '~/routes/_authenticated+/documents.$id.logs';
export { loader };

View File

@ -1,4 +1,4 @@
import DocumentsPage, { meta } from '~/routes/_authenticated+/documents+/_index';
import DocumentsPage, { meta } from '~/routes/_authenticated+/documents._index';
export { meta };

View File

@ -20,7 +20,7 @@ import { SettingsHeader } from '~/components/general/settings-header';
import { TeamEmailDropdown } from '~/components/general/teams/team-email-dropdown';
import { TeamTransferStatus } from '~/components/general/teams/team-transfer-status';
import type { Route } from './+types/_index';
import type { Route } from './+types/settings._index';
export async function loader({ request, params }: Route.LoaderArgs) {
const { user } = await getSession(request);

View File

@ -9,7 +9,7 @@ import { TeamSettingsNavDesktop } from '~/components/general/teams/team-settings
import { TeamSettingsNavMobile } from '~/components/general/teams/team-settings-nav-mobile';
import { appMetaTags } from '~/utils/meta';
import type { Route } from './+types/_layout';
import type { Route } from './+types/settings._layout';
export function meta() {
return appMetaTags('Team Settings');

View File

@ -15,7 +15,7 @@ import { SettingsHeader } from '~/components/general/settings-header';
import { TeamBillingPortalButton } from '~/components/general/teams/team-billing-portal-button';
import { TeamSettingsBillingInvoicesTable } from '~/components/tables/team-settings-billing-invoices-table';
import type { Route } from './+types/billing';
import type { Route } from './+types/settings.billing';
export async function loader({ request, params }: Route.LoaderArgs) {
const session = await getSession(request);

View File

@ -8,7 +8,7 @@ import { TeamBrandingPreferencesForm } from '~/components/forms/team-branding-pr
import { TeamDocumentPreferencesForm } from '~/components/forms/team-document-preferences-form';
import { SettingsHeader } from '~/components/general/settings-header';
import type { Route } from './+types/preferences';
import type { Route } from './+types/settings.preferences';
export async function loader({ request, params }: Route.LoaderArgs) {
const { user } = await getSession(request);

View File

@ -2,9 +2,9 @@ import { getSession } from '@documenso/auth/server/lib/utils/get-session';
import { getTeamByUrl } from '@documenso/lib/server-only/team/get-team';
import { getTeamPublicProfile } from '@documenso/lib/server-only/team/get-team-public-profile';
import PublicProfilePage from '~/routes/_authenticated+/settings+/public-profile+/index';
import PublicProfilePage from '~/routes/_authenticated+/settings+/public-profile';
import type { Route } from './+types/public-profile';
import type { Route } from './+types/settings.public-profile';
// Todo: This can be optimized.
export async function loader({ request, params }: Route.LoaderArgs) {

View File

@ -1,3 +1,3 @@
import ApiTokensPage from '~/routes/_authenticated+/settings+/tokens+/index';
import ApiTokensPage from '~/routes/_authenticated+/settings+/tokens';
export default ApiTokensPage;

View File

@ -28,7 +28,7 @@ import { SettingsHeader } from '~/components/general/settings-header';
import { WebhookMultiSelectCombobox } from '~/components/general/webhook-multiselect-combobox';
import { useCurrentTeam } from '~/providers/team';
import type { Route } from './+types/webhooks.$id';
import type { Route } from './+types/settings.webhooks.$id';
const ZEditWebhookFormSchema = ZEditWebhookMutationSchema.omit({ id: true });

View File

@ -1,4 +1,4 @@
import TemplatePage, { loader } from '~/routes/_authenticated+/templates+/$id._index';
import TemplatePage, { loader } from '~/routes/_authenticated+/templates.$id._index';
export { loader };

View File

@ -1,4 +1,4 @@
import TemplateEditPage, { loader } from '~/routes/_authenticated+/templates+/$id.edit';
import TemplateEditPage, { loader } from '~/routes/_authenticated+/templates.$id.edit';
export { loader };

View File

@ -1,4 +1,4 @@
import TemplatesPage, { meta } from '~/routes/_authenticated+/templates+/_index';
import TemplatesPage, { meta } from '~/routes/_authenticated+/templates._index';
export { meta };

View File

@ -24,7 +24,7 @@ import { TemplateType } from '~/components/general/template/template-type';
import { TemplatesTableActionDropdown } from '~/components/tables/templates-table-action-dropdown';
import { superLoaderJson, useSuperLoaderData } from '~/utils/super-json-loader';
import type { Route } from './+types/$id._index';
import type { Route } from './+types/templates.$id._index';
export async function loader({ params, request }: Route.LoaderArgs) {
const { user } = await getSession(request);

View File

@ -13,8 +13,8 @@ import { TemplateEditForm } from '~/components/general/template/template-edit-fo
import { TemplateType } from '~/components/general/template/template-type';
import { superLoaderJson, useSuperLoaderData } from '~/utils/super-json-loader';
import { TemplateDirectLinkDialogWrapper } from '../../../components/dialogs/template-direct-link-dialog-wrapper';
import type { Route } from './+types/$id.edit';
import { TemplateDirectLinkDialogWrapper } from '../../components/dialogs/template-direct-link-dialog-wrapper';
import type { Route } from './+types/templates.$id.edit';
export async function loader({ params, request }: Route.LoaderArgs) {
const { user } = await getSession(request);