mirror of
https://github.com/documenso/documenso.git
synced 2026-07-10 21:15:15 +10:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a3dc96e8c1 |
@@ -275,9 +275,9 @@ export const ManagePublicTemplateDialog = ({
|
||||
|
||||
<TableCell>
|
||||
{selectedTemplateId === row.id ? (
|
||||
<CheckCircle2Icon className="h-5 w-5 text-neutral-600 dark:text-neutral-200" />
|
||||
<CheckCircle2Icon className="h-5 w-5 text-foreground" />
|
||||
) : (
|
||||
<CircleIcon className="h-5 w-5 text-neutral-300 dark:text-neutral-600" />
|
||||
<CircleIcon className="h-5 w-5 text-muted-foreground/40" />
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -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 groups have been added.`,
|
||||
description: t`Team members 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 the groups. Please try again later.`,
|
||||
description: t`We encountered an unknown error while attempting to add team members. 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>Add Groups</Trans>
|
||||
<Trans>Create Groups</Trans>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</>
|
||||
|
||||
@@ -215,7 +215,7 @@ export const TemplateDirectLinkDialog = ({
|
||||
{DIRECT_TEMPLATE_DOCUMENTATION.map((step, index) => (
|
||||
<li className="relative" key={index}>
|
||||
<div className="absolute -left-12">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full border-[3px] border-neutral-200 font-bold text-sm">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full border-[3px] border-border font-bold text-sm">
|
||||
{index + 1}
|
||||
</div>
|
||||
</div>
|
||||
@@ -259,8 +259,8 @@ export const TemplateDirectLinkDialog = ({
|
||||
.with({ token: P.nullish, currentStep: 'SELECT_RECIPIENT' }, () => (
|
||||
<DialogContent className="relative">
|
||||
{isCreatingTemplateDirectLink && validDirectTemplateRecipients.length !== 0 && (
|
||||
<div className="absolute inset-0 z-50 flex items-center justify-center rounded bg-white/50 dark:bg-black/50">
|
||||
<LoaderIcon className="h-6 w-6 animate-spin text-gray-500" />
|
||||
<div className="absolute inset-0 z-50 flex items-center justify-center rounded bg-background/50">
|
||||
<LoaderIcon className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -317,9 +317,9 @@ export const TemplateDirectLinkDialog = ({
|
||||
|
||||
<TableCell>
|
||||
{selectedRecipientId === row.id ? (
|
||||
<CircleDotIcon className="h-5 w-5 text-neutral-300" />
|
||||
<CircleDotIcon className="h-5 w-5 text-muted-foreground/40" />
|
||||
) : (
|
||||
<CircleIcon className="h-5 w-5 text-neutral-300" />
|
||||
<CircleIcon className="h-5 w-5 text-muted-foreground/40" />
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -106,7 +106,7 @@ export const WebhookCreateDialog = ({ trigger, ...props }: WebhookCreateDialogPr
|
||||
<Trans>Create webhook</Trans>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
<Trans>Receive real-time notifications when document events occur.</Trans>
|
||||
<Trans>On this page, you can create a new webhook.</Trans>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ export const AvatarImageForm = ({ className, team, organisation }: AvatarImageFo
|
||||
<div className="relative">
|
||||
<Avatar className="h-16 w-16 border-2 border-solid">
|
||||
{avatarImageId && <AvatarImage src={formatAvatarUrl(avatarImageId)} />}
|
||||
<AvatarFallback className="text-gray-400 text-sm">{initials}</AvatarFallback>
|
||||
<AvatarFallback className="text-muted-foreground text-sm">{initials}</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
{hasAvatarImage && (
|
||||
|
||||
@@ -162,7 +162,7 @@ export const DocumentPreferencesForm = ({
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={handleFormSubmit}>
|
||||
<fieldset className="flex h-full flex-col gap-y-6" disabled={form.formState.isSubmitting}>
|
||||
<fieldset className="flex h-full max-w-2xl 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 flex-col gap-y-6" disabled={form.formState.isSubmitting}>
|
||||
<fieldset className="flex h-full max-w-2xl flex-col gap-y-6" disabled={form.formState.isSubmitting}>
|
||||
{organisation.organisationClaim.flags.emailDomains && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
+1
-1
@@ -155,7 +155,7 @@ export const DocumentSigningFieldContainer = ({
|
||||
{(field.type === FieldType.RADIO || field.type === FieldType.CHECKBOX) && field.fieldMeta?.label && (
|
||||
<div
|
||||
className={cn(
|
||||
'absolute -top-16 right-0 left-0 rounded-md p-2 text-center text-gray-700 text-xs',
|
||||
'absolute -top-16 right-0 left-0 rounded-md p-2 text-center text-foreground text-xs',
|
||||
{
|
||||
'border border-border bg-foreground/5': !field.inserted,
|
||||
},
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ export const DocumentSigningPageViewV2 = () => {
|
||||
}, [recipientFieldsRemaining, selectedAssistantRecipientFields, currentEnvelopeItem]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen w-screen bg-gray-50 dark:bg-background">
|
||||
<div className="min-h-screen w-screen bg-muted dark:bg-background">
|
||||
<SignFieldEmailDialog.Root />
|
||||
<SignFieldTextDialog.Root />
|
||||
<SignFieldNumberDialog.Root />
|
||||
|
||||
@@ -75,7 +75,7 @@ export const DocumentPageViewRecentActivity = ({ documentId, userId }: DocumentP
|
||||
</div>
|
||||
|
||||
<div className="relative flex h-6 w-6 flex-none items-center justify-center bg-widget">
|
||||
<div className="h-1.5 w-1.5 rounded-full bg-widget ring-1 ring-gray-300 dark:ring-neutral-600" />
|
||||
<div className="h-1.5 w-1.5 rounded-full bg-widget ring-1 ring-border" />
|
||||
</div>
|
||||
|
||||
<button
|
||||
@@ -109,27 +109,27 @@ export const DocumentPageViewRecentActivity = ({ documentId, userId }: DocumentP
|
||||
<div className="relative flex h-6 w-6 flex-none items-center justify-center bg-widget text-foreground/40">
|
||||
{match(auditLog.type)
|
||||
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED, () => (
|
||||
<div className="rounded-full border border-gray-300 bg-widget p-1 dark:border-neutral-600">
|
||||
<div className="rounded-full border border-border bg-widget p-1">
|
||||
<CheckCheckIcon className="h-3 w-3" aria-hidden="true" />
|
||||
</div>
|
||||
))
|
||||
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED, () => (
|
||||
<div className="rounded-full border border-gray-300 bg-widget p-1 dark:border-neutral-600">
|
||||
<div className="rounded-full border border-border bg-widget p-1">
|
||||
<CheckIcon className="h-3 w-3" aria-hidden="true" />
|
||||
</div>
|
||||
))
|
||||
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED, () => (
|
||||
<div className="rounded-full border border-gray-300 bg-widget p-1 dark:border-neutral-600">
|
||||
<div className="rounded-full border border-border bg-widget p-1">
|
||||
<AlertTriangle className="h-3 w-3" aria-hidden="true" />
|
||||
</div>
|
||||
))
|
||||
.with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED, () => (
|
||||
<div className="rounded-full border border-gray-300 bg-widget p-1 dark:border-neutral-600">
|
||||
<div className="rounded-full border border-border bg-widget p-1">
|
||||
<MailOpen className="h-3 w-3" aria-hidden="true" />
|
||||
</div>
|
||||
))
|
||||
.otherwise(() => (
|
||||
<div className="h-1.5 w-1.5 rounded-full bg-widget ring-1 ring-gray-300 dark:ring-neutral-600" />
|
||||
<div className="h-1.5 w-1.5 rounded-full bg-widget ring-1 ring-border" />
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -261,7 +261,7 @@ export const EnvelopeEditorPreviewPage = () => {
|
||||
|
||||
{/* Right Section - Form Fields Panel */}
|
||||
{currentEnvelopeItem && false && (
|
||||
<div className="sticky top-0 h-full w-80 flex-shrink-0 overflow-y-auto border-gray-200 border-l bg-white py-4">
|
||||
<div className="sticky top-0 h-full w-80 flex-shrink-0 overflow-y-auto border-border border-l bg-background py-4">
|
||||
{/* Add fields section. */}
|
||||
<section className="px-4">
|
||||
{/* <h3 className="mb-2 text-sm font-semibold text-gray-900">
|
||||
@@ -326,7 +326,7 @@ export const EnvelopeEditorPreviewPage = () => {
|
||||
|
||||
{/* Recipient selector section. */}
|
||||
<section className="px-4">
|
||||
<h3 className="mb-2 font-semibold text-gray-900 text-sm">
|
||||
<h3 className="mb-2 font-semibold text-foreground text-sm">
|
||||
<Trans>Selected Recipient</Trans>
|
||||
</h3>
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ export const EnvelopeItemTitleInput = ({
|
||||
{/* Hidden span to measure text width */}
|
||||
<span
|
||||
ref={measureRef}
|
||||
className="pointer-events-none absolute top-0 left-0 whitespace-nowrap font-medium text-gray-600 text-sm opacity-0"
|
||||
className="pointer-events-none absolute top-0 left-0 whitespace-nowrap font-medium text-muted-foreground text-sm opacity-0"
|
||||
style={{ font: 'inherit' }}
|
||||
>
|
||||
{envelopeItemTitle || placeholder}
|
||||
|
||||
@@ -271,7 +271,7 @@ export const EnvelopeEditor = () => {
|
||||
`cursor-pointer rounded-lg text-left transition-colors ${
|
||||
isActive
|
||||
? 'border border-green-200 bg-green-50 dark:border-green-500/20 dark:bg-green-500/10'
|
||||
: 'border border-gray-200 hover:bg-gray-50 dark:border-gray-400/20 dark:hover:bg-gray-400/10'
|
||||
: 'border border-border hover:bg-muted/50'
|
||||
}`,
|
||||
{
|
||||
'p-3': !minimizeLeftSidebar,
|
||||
@@ -284,10 +284,10 @@ export const EnvelopeEditor = () => {
|
||||
className={`rounded border p-2 ${
|
||||
isActive
|
||||
? 'border-green-200 bg-green-50 dark:border-green-500/20 dark:bg-green-500/10'
|
||||
: 'border-gray-100 bg-gray-100 dark:border-gray-400/20 dark:bg-gray-400/10'
|
||||
: 'border-muted bg-muted'
|
||||
}`}
|
||||
>
|
||||
<Icon className={`h-4 w-4 ${isActive ? 'text-green-600' : 'text-gray-600'}`} />
|
||||
<Icon className={`h-4 w-4 ${isActive ? 'text-green-600' : 'text-muted-foreground'}`} />
|
||||
</div>
|
||||
|
||||
{!minimizeLeftSidebar && (
|
||||
|
||||
@@ -31,14 +31,14 @@ export const EnvelopeItemSelector = ({
|
||||
>
|
||||
<div
|
||||
className={`flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full font-medium text-xs ${
|
||||
isSelected ? 'bg-green-100 text-green-600' : 'bg-gray-200 text-gray-600'
|
||||
isSelected ? 'bg-green-100 text-green-600' : 'bg-muted text-muted-foreground'
|
||||
}`}
|
||||
>
|
||||
{number}
|
||||
</div>
|
||||
<div className="min-w-0 text-left">
|
||||
<div className="truncate font-medium text-sm">{primaryText}</div>
|
||||
<div className="text-gray-500 text-xs">{secondaryText}</div>
|
||||
<div className="text-muted-foreground text-xs">{secondaryText}</div>
|
||||
</div>
|
||||
{actionSlot ?? (
|
||||
<div
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,144 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -31,7 +31,7 @@ export const TemplateDirectLinkBadge = ({ token, enabled, className }: TemplateD
|
||||
<button
|
||||
title="Copy direct link"
|
||||
className={cn(
|
||||
'flex flex-row items-center rounded border border-neutral-300 bg-neutral-200 px-1.5 py-0.5 text-xs dark:border-neutral-500 dark:bg-neutral-600',
|
||||
'flex flex-row items-center rounded border border-border bg-muted px-1.5 py-0.5 text-xs',
|
||||
className,
|
||||
)}
|
||||
onClick={async () => onCopyClick(token)}
|
||||
|
||||
@@ -71,7 +71,7 @@ export const TemplatePageViewRecentActivity = ({
|
||||
</div>
|
||||
|
||||
<div className="relative flex h-6 w-6 flex-none items-center justify-center bg-widget">
|
||||
<div className="h-1.5 w-1.5 rounded-full bg-widget ring-1 ring-gray-300 dark:ring-neutral-600" />
|
||||
<div className="h-1.5 w-1.5 rounded-full bg-widget ring-1 ring-border" />
|
||||
</div>
|
||||
|
||||
<button
|
||||
@@ -108,7 +108,7 @@ export const TemplatePageViewRecentActivity = ({
|
||||
</div>
|
||||
|
||||
<div className="relative flex h-6 w-6 flex-none items-center justify-center bg-widget text-foreground/40">
|
||||
<div className="h-1.5 w-1.5 rounded-full bg-widget ring-1 ring-gray-300 dark:ring-neutral-600" />
|
||||
<div className="h-1.5 w-1.5 rounded-full bg-widget ring-1 ring-border" />
|
||||
</div>
|
||||
|
||||
<Link
|
||||
|
||||
@@ -127,8 +127,8 @@ export const AdminDashboardUsersTable = ({ users, totalPages, perPage, page }: A
|
||||
</DataTable>
|
||||
|
||||
{isPending && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-white/50">
|
||||
<Loader className="h-8 w-8 animate-spin text-gray-500" />
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-background/50">
|
||||
<Loader className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -204,8 +204,8 @@ export const AdminOrganisationOverviewTable = ({
|
||||
</DataTable>
|
||||
|
||||
{isPending && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-white/50">
|
||||
<Loader className="h-8 w-8 animate-spin text-gray-500" />
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-background/50">
|
||||
<Loader className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -110,7 +110,7 @@ export const AdminOrganisationsTable = ({
|
||||
return (
|
||||
<div
|
||||
className={`inline-flex items-center rounded-full px-2 py-1 font-medium text-xs ${
|
||||
isPaid ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'
|
||||
isPaid ? 'bg-green-100 text-green-800' : 'bg-muted text-muted-foreground'
|
||||
}`}
|
||||
>
|
||||
{isPaid ? (
|
||||
|
||||
@@ -102,9 +102,7 @@ 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}`}>
|
||||
<Trans>Manage</Trans>
|
||||
</Link>
|
||||
<Link to={`/o/${organisation.url}/settings/email-domains/${row.original.id}`}>Manage</Link>
|
||||
</Button>
|
||||
|
||||
<OrganisationEmailDomainDeleteDialog
|
||||
|
||||
@@ -253,8 +253,8 @@ export const OrganisationInsightsTable = ({
|
||||
</div>
|
||||
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-white/50">
|
||||
<Loader className="h-8 w-8 animate-spin text-gray-500" />
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-background/50">
|
||||
<Loader className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -72,7 +72,7 @@ export const SettingsPublicProfileTemplatesTable = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mt-6 divide-y divide-neutral-200 overflow-hidden rounded-lg border border-neutral-200 dark:divide-foreground/30 dark:border-foreground/30">
|
||||
<div className="mt-6 divide-y divide-border overflow-hidden rounded-lg border border-border">
|
||||
{/* Loading and error handling states. */}
|
||||
{publicDirectTemplates.length === 0 && (
|
||||
<>
|
||||
@@ -132,7 +132,7 @@ export const SettingsPublicProfileTemplatesTable = () => {
|
||||
|
||||
<div>
|
||||
<p className="break-all text-sm">{template.publicTitle}</p>
|
||||
<p className="break-all text-neutral-400 text-xs">{template.publicDescription}</p>
|
||||
<p className="break-all text-muted-foreground text-xs">{template.publicDescription}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ export const TemplatesTable = ({
|
||||
</p>
|
||||
</li>
|
||||
<li>
|
||||
<div className="mb-2 flex w-fit flex-row items-center rounded border border-neutral-300 bg-neutral-200 px-1.5 py-0.5 text-xs dark:border-neutral-500 dark:bg-neutral-600">
|
||||
<div className="mb-2 flex w-fit flex-row items-center rounded border border-border bg-muted px-1.5 py-0.5 text-xs">
|
||||
<Link2Icon className="mr-1 h-3 w-3" />
|
||||
<Trans>direct link</Trans>
|
||||
</div>
|
||||
@@ -310,8 +310,8 @@ export const TemplatesTable = ({
|
||||
</DataTable>
|
||||
|
||||
{isPending && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-white/50">
|
||||
<Loader className="h-8 w-8 animate-spin text-gray-500" />
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-background/50">
|
||||
<Loader className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -149,8 +149,8 @@ export default function AdminDocumentsPage() {
|
||||
</DataTable>
|
||||
|
||||
{isFindDocumentsLoading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-white/50">
|
||||
<Loader className="h-8 w-8 animate-spin text-gray-500" />
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-background/50">
|
||||
<Loader className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -182,8 +182,8 @@ export default function AdminEmailDomainsPage() {
|
||||
</DataTable>
|
||||
|
||||
{isFindEmailDomainsLoading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-white/50">
|
||||
<Loader className="h-8 w-8 animate-spin text-gray-500" />
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-background/50">
|
||||
<Loader className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -173,8 +173,8 @@ export default function AdminUnsealedDocumentsPage() {
|
||||
</DataTable>
|
||||
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-white/50">
|
||||
<Loader className="h-8 w-8 animate-spin text-gray-500" />
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-background/50">
|
||||
<Loader className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -108,7 +108,7 @@ export default function DashboardPage() {
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar className="h-10 w-10 border border-solid">
|
||||
{org.avatarImageId && <AvatarImage src={formatAvatarUrl(org.avatarImageId)} />}
|
||||
<AvatarFallback className="text-gray-400 text-sm">
|
||||
<AvatarFallback className="text-muted-foreground text-sm">
|
||||
{org.name.slice(0, 1).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
@@ -182,7 +182,7 @@ export default function DashboardPage() {
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar className="h-10 w-10 border border-solid">
|
||||
{team.avatarImageId && <AvatarImage src={formatAvatarUrl(team.avatarImageId)} />}
|
||||
<AvatarFallback className="text-gray-400 text-sm">
|
||||
<AvatarFallback className="text-muted-foreground text-sm">
|
||||
{team.name.slice(0, 1).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
@@ -129,7 +129,7 @@ export default function OrganisationSettingsTeamsPage() {
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar className="h-10 w-10 border-2 border-solid">
|
||||
{team.avatarImageId && <AvatarImage src={formatAvatarUrl(team.avatarImageId)} />}
|
||||
<AvatarFallback className="text-gray-400 text-sm">
|
||||
<AvatarFallback className="text-muted-foreground text-sm">
|
||||
{team.name.slice(0, 1).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
@@ -12,12 +13,11 @@ import {
|
||||
Settings2Icon,
|
||||
ShieldCheckIcon,
|
||||
Users2Icon,
|
||||
UsersIcon,
|
||||
} from 'lucide-react';
|
||||
import { Link, Outlet } from 'react-router';
|
||||
import { FaUsers } from 'react-icons/fa6';
|
||||
import { Link, NavLink, 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: SettingsNavRoute[] = [
|
||||
const organisationSettingRoutes = [
|
||||
{
|
||||
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,
|
||||
isSectionLabel: true,
|
||||
hideHighlight: 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: UsersIcon,
|
||||
icon: FaUsers,
|
||||
},
|
||||
{
|
||||
path: `/o/${organisation.url}/settings/members`,
|
||||
@@ -139,9 +139,32 @@ export default function SettingsLayout() {
|
||||
</h1>
|
||||
|
||||
<div className="mt-4 grid grid-cols-12 gap-x-8 md:mt-8">
|
||||
<SettingsNav routes={organisationSettingRoutes} className="col-span-12 mb-8 md:col-span-3 md:mb-0" />
|
||||
{/* 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>
|
||||
|
||||
<div className="col-span-12 max-w-3xl md:col-span-9">
|
||||
<div className="col-span-12 md:col-span-9">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -89,9 +89,9 @@ export default function TeamsSettingBillingPage() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
<div className="flex flex-row items-end justify-between">
|
||||
<div>
|
||||
<h3 className="font-medium text-lg">
|
||||
<h3 className="font-semibold text-2xl">
|
||||
<Trans>Billing</Trans>
|
||||
</h3>
|
||||
|
||||
|
||||
@@ -123,13 +123,13 @@ export default function OrganisationSettingsBrandingPage() {
|
||||
const settingsHeaderText = t`Branding Preferences`;
|
||||
|
||||
const settingsHeaderSubtitle = isPersonalLayoutMode
|
||||
? t`Set the default branding for documents you send.`
|
||||
? t`Here you can set your general branding preferences.`
|
||||
: team
|
||||
? 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.`;
|
||||
? 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.`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="max-w-2xl">
|
||||
<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`Set your default document settings.`
|
||||
: t`Set the default document settings for your organisation. Teams inherit these settings by default.`;
|
||||
? 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.`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="max-w-2xl">
|
||||
<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 the email addresses for this domain.`}>
|
||||
<SettingsHeader title={t`Email Domain Settings`} subtitle={t`Manage your email domain settings.`}>
|
||||
<OrganisationEmailCreateDialog emailDomain={emailDomain} />
|
||||
</SettingsHeader>
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ export default function OrganisationSettingsEmailDomains() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader title={t`Email Domains`} subtitle={t`Add and verify email domains for your organisation.`}>
|
||||
<SettingsHeader title={t`Email Domains`} subtitle={t`Here you can add email domains to your organisation.`}>
|
||||
{isEmailDomainsEnabled && <OrganisationEmailDomainCreateDialog />}
|
||||
</SettingsHeader>
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
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';
|
||||
@@ -15,16 +13,12 @@ export function meta() {
|
||||
return appMetaTags(msg`Email Preferences`);
|
||||
}
|
||||
|
||||
export default function OrganisationSettingsEmailPage() {
|
||||
export default function OrganisationSettingsGeneral() {
|
||||
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,
|
||||
});
|
||||
@@ -65,15 +59,8 @@ export default function OrganisationSettingsEmailPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<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.`
|
||||
}
|
||||
/>
|
||||
<div className="max-w-2xl">
|
||||
<SettingsHeader title={t`Email Preferences`} subtitle={t`You can manage your email preferences here.`} />
|
||||
|
||||
<section>
|
||||
<EmailPreferencesForm
|
||||
|
||||
@@ -21,8 +21,8 @@ export default function OrganisationSettingsGeneral() {
|
||||
const organisation = useCurrentOrganisation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader title={_(msg`General`)} subtitle={_(msg`Update your organisation's details.`)} />
|
||||
<div className="max-w-2xl">
|
||||
<SettingsHeader title={_(msg`General`)} subtitle={_(msg`Here you can edit your organisation 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`Group Settings`} subtitle={t`Update the group's name, role and members.`}>
|
||||
<SettingsHeader title={t`Organisation Group Settings`} subtitle={t`Manage your organisation group settings.`}>
|
||||
<OrganisationGroupDeleteDialog
|
||||
organisationGroupId={groupId}
|
||||
organisationGroupName={group.name || ''}
|
||||
|
||||
@@ -10,7 +10,7 @@ export default function TeamsSettingsMembersPage() {
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader
|
||||
title={t`Organisation Groups`}
|
||||
title={t`Custom Organisation Groups`}
|
||||
subtitle={t`Manage the custom groups of members for your organisation.`}
|
||||
>
|
||||
<OrganisationGroupCreateDialog />
|
||||
|
||||
@@ -46,10 +46,7 @@ export default function TeamsSettingsMembersPage() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader
|
||||
title={_(msg`Organisation Members`)}
|
||||
subtitle={_(msg`Manage the members and invitations for your organisation.`)}
|
||||
>
|
||||
<SettingsHeader title={_(msg`Organisation Members`)} subtitle={_(msg`Manage the members or invite new members.`)}>
|
||||
<OrganisationMemberInviteDialog />
|
||||
</SettingsHeader>
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ export default function OrganisationSettingSSOLoginPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="max-w-2xl">
|
||||
<SettingsHeader
|
||||
title={t`Organisation SSO Portal`}
|
||||
subtitle={t`Manage a custom SSO login portal for your organisation.`}
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
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, useLingui } from '@lingui/react/macro';
|
||||
import {
|
||||
BracesIcon,
|
||||
CreditCardIcon,
|
||||
Globe2Icon,
|
||||
LockIcon,
|
||||
Settings2Icon,
|
||||
UserIcon,
|
||||
UsersIcon,
|
||||
WebhookIcon,
|
||||
} from 'lucide-react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Outlet } from 'react-router';
|
||||
|
||||
import { SettingsNav, type SettingsNavRoute } from '~/components/general/settings-nav';
|
||||
import { SettingsDesktopNav } from '~/components/general/settings-nav-desktop';
|
||||
import { SettingsMobileNav } from '~/components/general/settings-nav-mobile';
|
||||
import { appMetaTags } from '~/utils/meta';
|
||||
|
||||
export function meta() {
|
||||
@@ -23,83 +11,6 @@ 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">
|
||||
@@ -107,9 +18,10 @@ export default function SettingsLayout() {
|
||||
</h1>
|
||||
|
||||
<div className="mt-4 grid grid-cols-12 gap-x-8 md:mt-8">
|
||||
<SettingsNav routes={settingRoutes} className="col-span-12 mb-8 md:col-span-3 md:mb-0" />
|
||||
<SettingsDesktopNav className="hidden md:col-span-3 md:flex" />
|
||||
<SettingsMobileNav className="col-span-12 mb-8 md:hidden" />
|
||||
|
||||
<div className="col-span-12 max-w-3xl md:col-span-9">
|
||||
<div className="col-span-12 md:col-span-9">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,10 @@ export default function TeamsSettingsPage() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader title={_(msg`Organisations`)} subtitle={_(msg`Manage the organisations you belong to.`)}>
|
||||
<SettingsHeader
|
||||
title={_(msg`Organisations`)}
|
||||
subtitle={_(msg`Manage all organisations you are currently associated with.`)}
|
||||
>
|
||||
<OrganisationCreateDialog />
|
||||
</SettingsHeader>
|
||||
|
||||
|
||||
@@ -27,14 +27,14 @@ export default function SettingsProfile() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader title={_(msg`Profile`)} subtitle={_(msg`Update your personal details.`)} />
|
||||
<SettingsHeader title={_(msg`Profile`)} subtitle={_(msg`Here you can edit your personal details.`)} />
|
||||
|
||||
<AvatarImageForm className="mb-8" />
|
||||
<ProfileForm className="mb-8" />
|
||||
<AvatarImageForm className="mb-8 max-w-xl" />
|
||||
<ProfileForm className="mb-8 max-w-xl" />
|
||||
|
||||
<hr className="my-4" />
|
||||
<hr className="my-4 max-w-xl" />
|
||||
|
||||
<div className="space-y-8">
|
||||
<div className="max-w-xl space-y-8">
|
||||
<AnimatePresence>
|
||||
{(!isPersonalLayoutMode || user.email !== teamEmail?.email) && teamEmail && (
|
||||
<AnimateGenericFadeInOut>
|
||||
|
||||
@@ -62,7 +62,10 @@ export default function SettingsSecurity({ loaderData }: Route.ComponentProps) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader title={_(msg`Security`)} subtitle={_(msg`Manage your password and security settings.`)} />
|
||||
<SettingsHeader
|
||||
title={_(msg`Security`)}
|
||||
subtitle={_(msg`Here you can 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, useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { CheckCircle2, Clock } from 'lucide-react';
|
||||
import { match, P } from 'ts-pattern';
|
||||
|
||||
@@ -33,15 +33,13 @@ 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>
|
||||
<SettingsHeader title={t`General`} subtitle={t`Update your team's details.`} />
|
||||
<div className="max-w-2xl">
|
||||
<SettingsHeader title="General settings" subtitle="Here you can edit 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, Outlet, redirect } from 'react-router';
|
||||
import { Link, NavLink, 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,18 +40,17 @@ export default function TeamsSettingsLayout() {
|
||||
|
||||
const team = useCurrentTeam();
|
||||
|
||||
const teamSettingRoutes: SettingsNavRoute[] = [
|
||||
const teamSettingRoutes = [
|
||||
{
|
||||
path: `/t/${team.url}/settings`,
|
||||
label: t`General`,
|
||||
icon: SettingsIcon,
|
||||
end: true,
|
||||
},
|
||||
{
|
||||
path: `/t/${team.url}/settings/document`,
|
||||
label: t`Preferences`,
|
||||
icon: Settings2Icon,
|
||||
isSectionLabel: true,
|
||||
isSubNavParent: true,
|
||||
},
|
||||
{
|
||||
path: `/t/${team.url}/settings/document`,
|
||||
@@ -125,9 +124,31 @@ export default function TeamsSettingsLayout() {
|
||||
</h1>
|
||||
|
||||
<div className="mt-4 grid grid-cols-12 gap-x-8 md:mt-8">
|
||||
<SettingsNav routes={teamSettingRoutes} className="col-span-12 mb-8 md:col-span-3 md:mb-0" />
|
||||
<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>
|
||||
|
||||
<div className="col-span-12 max-w-3xl md:col-span-9">
|
||||
<div className="col-span-12 md:col-span-9">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -115,10 +115,10 @@ export default function TeamsSettingsPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="max-w-2xl">
|
||||
<SettingsHeader
|
||||
title={t`Branding Preferences`}
|
||||
subtitle={t`Set the default branding for documents sent by your team.`}
|
||||
subtitle={t`Here you can set preferences and defaults for branding.`}
|
||||
/>
|
||||
|
||||
{canConfigureBranding ? (
|
||||
|
||||
@@ -110,8 +110,11 @@ export default function TeamsSettingsPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader title={t`Document Preferences`} subtitle={t`Set the default document settings for your team.`} />
|
||||
<div className="max-w-2xl">
|
||||
<SettingsHeader
|
||||
title={t`Document Preferences`}
|
||||
subtitle={t`Here you can set preferences and defaults 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`Email Preferences`);
|
||||
return appMetaTags(msg`Settings`);
|
||||
}
|
||||
|
||||
export default function TeamEmailSettingsPage() {
|
||||
export default function TeamEmailSettingsGeneral() {
|
||||
const { t } = useLingui();
|
||||
const { toast } = useToast();
|
||||
|
||||
@@ -59,8 +59,8 @@ export default function TeamEmailSettingsPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader title={t`Email Preferences`} subtitle={t`Manage the default email settings for your team.`} />
|
||||
<div className="max-w-2xl">
|
||||
<SettingsHeader title={t`Email Preferences`} subtitle={t`You can manage your email preferences here.`} />
|
||||
|
||||
<section>
|
||||
<EmailPreferencesForm
|
||||
|
||||
@@ -128,8 +128,11 @@ export default function PublicProfilePage({ loaderData }: Route.ComponentProps)
|
||||
}, [profile.enabled]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader title={t`Public Profile`} subtitle={t`Manage your public profile and the templates shown on it.`}>
|
||||
<div className="max-w-2xl">
|
||||
<SettingsHeader
|
||||
title={t`Public Profile`}
|
||||
subtitle={t`You can choose to enable or disable the profile for public view.`}
|
||||
>
|
||||
<Tooltip open={isTooltipOpen} onOpenChange={setIsTooltipOpen}>
|
||||
<TooltipTrigger asChild>
|
||||
<div
|
||||
@@ -187,7 +190,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>
|
||||
Create and manage API tokens. See our{' '}
|
||||
On this page, you can 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 tokens={tokens} />
|
||||
<ApiTokenForm className="max-w-xl" 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 flex-col gap-y-4">
|
||||
<div className="mt-4 flex max-w-xl 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,12 +88,15 @@ export default function WebhookPage() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SettingsHeader title={t`Webhooks`} subtitle={t`Create and manage webhooks for document events.`}>
|
||||
<SettingsHeader
|
||||
title={t`Webhooks`}
|
||||
subtitle={t`On this page, you can create new Webhooks and manage the existing ones.`}
|
||||
>
|
||||
<WebhookCreateDialog />
|
||||
</SettingsHeader>
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-white/50">
|
||||
<Loader className="h-8 w-8 animate-spin text-gray-500" />
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-background/50">
|
||||
<Loader className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -59,7 +59,9 @@ export default function PublicProfilePage({ loaderData }: Route.ComponentProps)
|
||||
<Avatar className="h-24 w-24 border-2 border-solid dark:border-border">
|
||||
{publicProfile.avatarImageId && <AvatarImage src={formatAvatarUrl(publicProfile.avatarImageId)} />}
|
||||
|
||||
<AvatarFallback className="text-gray-400 text-sm">{extractInitials(publicProfile.name)}</AvatarFallback>
|
||||
<AvatarFallback className="text-muted-foreground text-sm">
|
||||
{extractInitials(publicProfile.name)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
<div className="mt-4 flex flex-row items-center justify-center">
|
||||
@@ -138,7 +140,7 @@ export default function PublicProfilePage({ loaderData }: Route.ComponentProps)
|
||||
<Table className="w-full" overflowHidden>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-full rounded-tl-md bg-neutral-50 dark:bg-neutral-700">
|
||||
<TableHead className="w-full rounded-tl-md bg-muted">
|
||||
<Trans>Documents</Trans>
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
|
||||
@@ -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: 'Add Groups' }).click();
|
||||
await expect(page.getByText('Team groups have been added').first()).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Create Groups' }).click();
|
||||
await expect(page.getByText('Team members 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: 'Add Groups' }).click();
|
||||
await expect(page.getByText('Team groups have been added').first()).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Create Groups' }).click();
|
||||
await expect(page.getByText('Team members have been added').first()).toBeVisible();
|
||||
|
||||
// Update CUSTOM_GROUP_B
|
||||
const updateBtn = page.getByRole('row', { name: 'CUSTOM_GROUP_B' }).getByRole('button');
|
||||
|
||||
@@ -10,7 +10,7 @@ const alertVariants = cva(
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-green-50 text-green-700 [&_.alert-title]:text-green-800 [&>svg]:text-green-400',
|
||||
neutral: 'bg-gray-50 dark:bg-neutral-900/20 text-muted-foreground [&_.alert-title]:text-foreground',
|
||||
neutral: 'bg-muted/50 text-muted-foreground [&_.alert-title]:text-foreground',
|
||||
secondary: 'bg-blue-50 text-blue-700 [&_.alert-title]:text-blue-800 [&>svg]:text-blue-400',
|
||||
destructive: 'bg-red-50 text-red-700 [&_.alert-title]:text-red-800 [&>svg]:text-red-400',
|
||||
warning: 'bg-yellow-50 text-yellow-700 [&_.alert-title]:text-yellow-800 [&>svg]:text-yellow-400',
|
||||
|
||||
@@ -63,7 +63,7 @@ const AvatarWithText = ({
|
||||
<div className={cn('flex w-full max-w-xs items-center gap-2', className)}>
|
||||
<Avatar className={cn('h-10 w-10 border-2 border-white border-solid dark:border-border', avatarClass)}>
|
||||
{avatarSrc && <AvatarImage src={avatarSrc} />}
|
||||
<AvatarFallback className="text-gray-400 text-xs">{avatarFallback}</AvatarFallback>
|
||||
<AvatarFallback className="text-muted-foreground text-xs">{avatarFallback}</AvatarFallback>
|
||||
</Avatar>
|
||||
|
||||
<div className={cn('flex flex-col truncate text-left font-normal text-sm', textSectionClassName)}>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { cn } from '../lib/utils';
|
||||
const badgeVariants = cva('inline-flex items-center rounded-md text-xs font-medium ring-1 ring-inset w-fit', {
|
||||
variants: {
|
||||
variant: {
|
||||
neutral: 'bg-gray-50 text-gray-600 ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20',
|
||||
neutral: 'bg-muted text-muted-foreground ring-border',
|
||||
destructive: 'bg-red-50 text-red-700 ring-red-600/10 dark:bg-red-400/10 dark:text-red-400 dark:ring-red-400/20',
|
||||
warning:
|
||||
'bg-yellow-50 text-yellow-800 ring-yellow-600/20 dark:bg-yellow-400/10 dark:text-yellow-500 dark:ring-yellow-400/20',
|
||||
|
||||
@@ -62,7 +62,7 @@ const CommandInput = React.forwardRef<
|
||||
<CommandPrimitive.Input
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-foreground-muted disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -327,7 +327,7 @@ export const AddSettingsFormPartial = ({
|
||||
|
||||
<Accordion type="multiple" className="mt-6">
|
||||
<AccordionItem value="advanced-options" className="border-none">
|
||||
<AccordionTrigger className="mb-2 rounded border px-3 py-2 text-left text-foreground hover:bg-neutral-200/30 hover:no-underline">
|
||||
<AccordionTrigger className="mb-2 rounded border px-3 py-2 text-left text-foreground hover:bg-muted/50 hover:no-underline">
|
||||
<Trans>Advanced Options</Trans>
|
||||
</AccordionTrigger>
|
||||
|
||||
|
||||
@@ -273,7 +273,7 @@ const FieldItemInner = ({
|
||||
>
|
||||
{(field.type === FieldType.RADIO || field.type === FieldType.CHECKBOX) && field.fieldMeta?.label && (
|
||||
<div
|
||||
className={cn('absolute -top-16 right-0 left-0 rounded-md p-2 text-center text-gray-700 text-xs', {
|
||||
className={cn('absolute -top-16 right-0 left-0 rounded-md p-2 text-center text-foreground text-xs', {
|
||||
'border border-primary bg-foreground/5': !fieldHasCheckedValues,
|
||||
'border border-primary bg-documenso-200': fieldHasCheckedValues,
|
||||
})}
|
||||
|
||||
@@ -454,7 +454,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
{distributionMethod === DocumentDistributionMethod.EMAIL && (
|
||||
<Accordion type="multiple">
|
||||
<AccordionItem value="email-options" className="border-none">
|
||||
<AccordionTrigger className="rounded border px-3 py-2 text-left text-foreground hover:bg-neutral-200/30 hover:no-underline">
|
||||
<AccordionTrigger className="rounded border px-3 py-2 text-left text-foreground hover:bg-muted/50 hover:no-underline">
|
||||
<Trans>Email Options</Trans>
|
||||
</AccordionTrigger>
|
||||
|
||||
@@ -588,7 +588,7 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
|
||||
<Accordion type="multiple">
|
||||
<AccordionItem value="advanced-options" className="border-none">
|
||||
<AccordionTrigger className="rounded border px-3 py-2 text-left text-foreground hover:bg-neutral-200/30 hover:no-underline">
|
||||
<AccordionTrigger className="rounded border px-3 py-2 text-left text-foreground hover:bg-muted/50 hover:no-underline">
|
||||
<Trans>Advanced Options</Trans>
|
||||
</AccordionTrigger>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user