- {step === 'BASIC_DETAILS' && (
-
-
- Create a new account
-
+
+
+ Create a new account
+
-
-
- Create your account and start using state-of-the-art document signing. Open and
- beautiful signing is within your grasp.
-
-
-
- )}
-
- {step === 'CLAIM_USERNAME' && (
-
-
- Claim your username now
-
-
-
-
- You will get notified & be able to set up your documenso public profile when we
- launch the feature.
-
-
-
- )}
+
+
+ Create your account and start using state-of-the-art document signing. Open and
+ beautiful signing is within your grasp.
+
+
+
@@ -292,242 +224,146 @@ export const SignUpForm = ({
className="flex w-full flex-1 flex-col gap-y-4"
onSubmit={form.handleSubmit(onFormSubmit)}
>
- {step === 'BASIC_DETAILS' && (
-
+ (
+
+
+ Full Name
+
+
+
+
+
+
)}
- disabled={isSubmitting}
- >
- (
-
-
- Full Name
-
-
-
-
-
-
- )}
- />
+ />
- (
-
-
- Email Address
-
-
-
-
-
-
- )}
- />
-
- (
-
-
- Password
-
-
-
-
-
-
-
-
- )}
- />
-
- (
-
-
- Sign Here
-
-
- onChange(v ?? '')}
- />
-
-
-
-
- )}
- />
-
- {(isGoogleSSOEnabled || isOIDCSSOEnabled) && (
- <>
-
- >
+ (
+
+
+ Email Address
+
+
+
+
+
+
)}
+ />
- {isGoogleSSOEnabled && (
- <>
-
-
- Sign Up with Google
-
- >
+ (
+
+
+ Password
+
+
+
+
+
+
+
+
)}
+ />
- {isOIDCSSOEnabled && (
- <>
-
-
- Sign Up with OIDC
-
- >
+ (
+
+
+ Sign Here
+
+
+ onChange(v ?? '')}
+ />
+
+
+
+
)}
+ />
-
-
- Already have an account?{' '}
-
- Sign in instead
-
-
-
-
- )}
-
- {step === 'CLAIM_USERNAME' && (
-
- (
-
-
- Public profile username
-
-
-
-
-
-
-
-
-
- {baseUrl.host}/u/{field.value || ''}
-
-
- )}
- />
-
- )}
-
-
- {step === 'BASIC_DETAILS' && (
-
-
- Basic details
- {' '}
- 1/2
-
+ {(isGoogleSSOEnabled || isOIDCSSOEnabled) && (
+ <>
+
+ >
)}
- {step === 'CLAIM_USERNAME' && (
-
-
- Claim username
- {' '}
- 2/2
-
+ {isGoogleSSOEnabled && (
+ <>
+
+
+ Sign Up with Google
+
+ >
)}
-
-
-
-
-
-
- {/* Go back button, disabled if step is basic details */}
- setStep('BASIC_DETAILS')}
- >
- Back
-
-
- {/* Continue button */}
- {step === 'BASIC_DETAILS' && (
-
- Next
-
+ {isOIDCSSOEnabled && (
+ <>
+
+
+ Sign Up with OIDC
+
+ >
)}
- {/* Sign up button */}
- {step === 'CLAIM_USERNAME' && (
-
- Complete
-
- )}
-
+
+
+ Already have an account?{' '}
+
+ Sign in instead
+
+
+
+
+
+
+ Complete
+
diff --git a/apps/remix/app/components/forms/subscription-claim-form.tsx b/apps/remix/app/components/forms/subscription-claim-form.tsx
new file mode 100644
index 000000000..0a26b2ab6
--- /dev/null
+++ b/apps/remix/app/components/forms/subscription-claim-form.tsx
@@ -0,0 +1,155 @@
+import { zodResolver } from '@hookform/resolvers/zod';
+import { Trans, useLingui } from '@lingui/react/macro';
+import type { SubscriptionClaim } from '@prisma/client';
+import { useForm } from 'react-hook-form';
+import type { z } from 'zod';
+
+import { SUBSCRIPTION_CLAIM_FEATURE_FLAGS } from '@documenso/lib/types/subscription';
+import { ZCreateSubscriptionClaimRequestSchema } from '@documenso/trpc/server/admin-router/create-subscription-claim.types';
+import { Checkbox } from '@documenso/ui/primitives/checkbox';
+import {
+ Form,
+ FormControl,
+ FormDescription,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@documenso/ui/primitives/form/form';
+import { Input } from '@documenso/ui/primitives/input';
+
+export type SubscriptionClaimFormValues = z.infer;
+
+type SubscriptionClaimFormProps = {
+ subscriptionClaim: Omit;
+ onFormSubmit: (data: SubscriptionClaimFormValues) => Promise;
+ formSubmitTrigger?: React.ReactNode;
+};
+
+export const SubscriptionClaimForm = ({
+ subscriptionClaim,
+ onFormSubmit,
+ formSubmitTrigger,
+}: SubscriptionClaimFormProps) => {
+ const { t } = useLingui();
+
+ const form = useForm({
+ resolver: zodResolver(ZCreateSubscriptionClaimRequestSchema),
+ defaultValues: {
+ name: subscriptionClaim.name,
+ teamCount: subscriptionClaim.teamCount,
+ memberCount: subscriptionClaim.memberCount,
+ flags: subscriptionClaim.flags,
+ },
+ });
+
+ return (
+
+
+ );
+};
diff --git a/apps/remix/app/components/forms/team-document-preferences-form.tsx b/apps/remix/app/components/forms/team-document-preferences-form.tsx
deleted file mode 100644
index 2b4846116..000000000
--- a/apps/remix/app/components/forms/team-document-preferences-form.tsx
+++ /dev/null
@@ -1,328 +0,0 @@
-import { zodResolver } from '@hookform/resolvers/zod';
-import { msg } from '@lingui/core/macro';
-import { useLingui } from '@lingui/react';
-import { Trans } from '@lingui/react/macro';
-import type { Team, TeamGlobalSettings } from '@prisma/client';
-import { DocumentVisibility } from '@prisma/client';
-import { useForm } from 'react-hook-form';
-import { z } from 'zod';
-
-import { useSession } from '@documenso/lib/client-only/providers/session';
-import { DOCUMENT_SIGNATURE_TYPES, DocumentSignatureType } from '@documenso/lib/constants/document';
-import {
- SUPPORTED_LANGUAGES,
- SUPPORTED_LANGUAGE_CODES,
- isValidLanguageCode,
-} from '@documenso/lib/constants/i18n';
-import { extractTeamSignatureSettings } from '@documenso/lib/utils/teams';
-import { trpc } from '@documenso/trpc/react';
-import { DocumentSignatureSettingsTooltip } from '@documenso/ui/components/document/document-signature-settings-tooltip';
-import { Alert } from '@documenso/ui/primitives/alert';
-import { Button } from '@documenso/ui/primitives/button';
-import {
- Form,
- FormControl,
- FormDescription,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
-} from '@documenso/ui/primitives/form/form';
-import { MultiSelectCombobox } from '@documenso/ui/primitives/multi-select-combobox';
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from '@documenso/ui/primitives/select';
-import { Switch } from '@documenso/ui/primitives/switch';
-import { useToast } from '@documenso/ui/primitives/use-toast';
-
-const ZTeamDocumentPreferencesFormSchema = z.object({
- documentVisibility: z.nativeEnum(DocumentVisibility),
- documentLanguage: z.enum(SUPPORTED_LANGUAGE_CODES),
- includeSenderDetails: z.boolean(),
- includeSigningCertificate: z.boolean(),
- signatureTypes: z.array(z.nativeEnum(DocumentSignatureType)).min(1, {
- message: msg`At least one signature type must be enabled`.id,
- }),
-});
-
-type TTeamDocumentPreferencesFormSchema = z.infer;
-
-export type TeamDocumentPreferencesFormProps = {
- team: Team;
- settings?: TeamGlobalSettings | null;
-};
-
-export const TeamDocumentPreferencesForm = ({
- team,
- settings,
-}: TeamDocumentPreferencesFormProps) => {
- const { _ } = useLingui();
- const { toast } = useToast();
- const { user } = useSession();
-
- const placeholderEmail = user.email ?? 'user@example.com';
-
- const { mutateAsync: updateTeamDocumentPreferences } =
- trpc.team.updateTeamDocumentSettings.useMutation();
-
- const form = useForm({
- defaultValues: {
- documentVisibility: settings?.documentVisibility ?? 'EVERYONE',
- documentLanguage: isValidLanguageCode(settings?.documentLanguage)
- ? settings?.documentLanguage
- : 'en',
- includeSenderDetails: settings?.includeSenderDetails ?? false,
- includeSigningCertificate: settings?.includeSigningCertificate ?? true,
- signatureTypes: extractTeamSignatureSettings(settings),
- },
- resolver: zodResolver(ZTeamDocumentPreferencesFormSchema),
- });
-
- const includeSenderDetails = form.watch('includeSenderDetails');
-
- const onSubmit = async (data: TTeamDocumentPreferencesFormSchema) => {
- try {
- const {
- documentVisibility,
- documentLanguage,
- includeSenderDetails,
- includeSigningCertificate,
- signatureTypes,
- } = data;
-
- await updateTeamDocumentPreferences({
- teamId: team.id,
- settings: {
- documentVisibility,
- documentLanguage,
- includeSenderDetails,
- includeSigningCertificate,
- typedSignatureEnabled: signatureTypes.includes(DocumentSignatureType.TYPE),
- uploadSignatureEnabled: signatureTypes.includes(DocumentSignatureType.UPLOAD),
- drawSignatureEnabled: signatureTypes.includes(DocumentSignatureType.DRAW),
- },
- });
-
- toast({
- title: _(msg`Document preferences updated`),
- description: _(msg`Your document preferences have been updated`),
- });
- } catch (err) {
- toast({
- title: _(msg`Something went wrong!`),
- description: _(
- msg`We were unable to update your document preferences at this time, please try again later`,
- ),
- });
- }
- };
-
- return (
-
-
- );
-};
diff --git a/apps/remix/app/components/forms/team-update-form.tsx b/apps/remix/app/components/forms/team-update-form.tsx
index a5165e30f..bf6159443 100644
--- a/apps/remix/app/components/forms/team-update-form.tsx
+++ b/apps/remix/app/components/forms/team-update-form.tsx
@@ -10,7 +10,7 @@ import type { z } from 'zod';
import { NEXT_PUBLIC_WEBAPP_URL } from '@documenso/lib/constants/app';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
-import { ZUpdateTeamMutationSchema } from '@documenso/trpc/server/team-router/schema';
+import { ZUpdateTeamRequestSchema } from '@documenso/trpc/server/team-router/update-team.types';
import { Button } from '@documenso/ui/primitives/button';
import {
Form,
@@ -29,7 +29,7 @@ export type UpdateTeamDialogProps = {
teamUrl: string;
};
-const ZTeamUpdateFormSchema = ZUpdateTeamMutationSchema.shape.data.pick({
+const ZTeamUpdateFormSchema = ZUpdateTeamRequestSchema.shape.data.pick({
name: true,
url: true,
});
@@ -49,7 +49,7 @@ export const TeamUpdateForm = ({ teamId, teamName, teamUrl }: UpdateTeamDialogPr
},
});
- const { mutateAsync: updateTeam } = trpc.team.updateTeam.useMutation();
+ const { mutateAsync: updateTeam } = trpc.team.update.useMutation();
const onFormSubmit = async ({ name, url }: TTeamUpdateFormSchema) => {
try {
diff --git a/apps/remix/app/components/forms/token.tsx b/apps/remix/app/components/forms/token.tsx
index d7bec0cba..13b7da0b1 100644
--- a/apps/remix/app/components/forms/token.tsx
+++ b/apps/remix/app/components/forms/token.tsx
@@ -8,12 +8,11 @@ import type { ApiToken } from '@prisma/client';
import { AnimatePresence, motion } from 'framer-motion';
import { useForm } from 'react-hook-form';
import { match } from 'ts-pattern';
-import { z } from 'zod';
+import type { z } from 'zod';
import { useCopyToClipboard } from '@documenso/lib/client-only/hooks/use-copy-to-clipboard';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { trpc } from '@documenso/trpc/react';
-import type { TCreateTokenMutationSchema } from '@documenso/trpc/server/api-token-router/schema';
import { ZCreateTokenMutationSchema } from '@documenso/trpc/server/api-token-router/schema';
import { cn } from '@documenso/ui/lib/utils';
import { Button } from '@documenso/ui/primitives/button';
@@ -38,7 +37,7 @@ import {
import { Switch } from '@documenso/ui/primitives/switch';
import { useToast } from '@documenso/ui/primitives/use-toast';
-import { useOptionalCurrentTeam } from '~/providers/team';
+import { useCurrentTeam } from '~/providers/team';
export const EXPIRATION_DATES = {
ONE_WEEK: msg`7 days`,
@@ -48,8 +47,9 @@ export const EXPIRATION_DATES = {
ONE_YEAR: msg`12 months`,
} as const;
-const ZCreateTokenFormSchema = ZCreateTokenMutationSchema.extend({
- enabled: z.boolean(),
+const ZCreateTokenFormSchema = ZCreateTokenMutationSchema.pick({
+ tokenName: true,
+ expirationDate: true,
});
type TCreateTokenFormSchema = z.infer;
@@ -67,7 +67,7 @@ export type ApiTokenFormProps = {
export const ApiTokenForm = ({ className, tokens }: ApiTokenFormProps) => {
const [, copy] = useCopyToClipboard();
- const team = useOptionalCurrentTeam();
+ const team = useCurrentTeam();
const { _ } = useLingui();
const { toast } = useToast();
@@ -86,7 +86,6 @@ export const ApiTokenForm = ({ className, tokens }: ApiTokenFormProps) => {
defaultValues: {
tokenName: '',
expirationDate: '',
- enabled: false,
},
});
@@ -111,10 +110,10 @@ export const ApiTokenForm = ({ className, tokens }: ApiTokenFormProps) => {
}
};
- const onSubmit = async ({ tokenName, expirationDate }: TCreateTokenMutationSchema) => {
+ const onSubmit = async ({ tokenName, expirationDate }: TCreateTokenFormSchema) => {
try {
await createTokenMutation({
- teamId: team?.id,
+ teamId: team.id,
tokenName,
expirationDate: noExpirationDate ? null : expirationDate,
});
@@ -149,7 +148,10 @@ export const ApiTokenForm = ({ className, tokens }: ApiTokenFormProps) => {
Create token
-
+
Create token
diff --git a/apps/remix/app/components/general/app-command-menu.tsx b/apps/remix/app/components/general/app-command-menu.tsx
index d6dcfe395..9e9724d2e 100644
--- a/apps/remix/app/components/general/app-command-menu.tsx
+++ b/apps/remix/app/components/general/app-command-menu.tsx
@@ -9,6 +9,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { useNavigate } from 'react-router';
import { Theme, useTheme } from 'remix-themes';
+import { useSession } from '@documenso/lib/client-only/providers/session';
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
import {
DOCUMENTS_PAGE_SHORTCUT,
@@ -20,6 +21,7 @@ import {
SKIP_QUERY_BATCH_META,
} from '@documenso/lib/constants/trpc';
import { dynamicActivate } from '@documenso/lib/utils/i18n';
+import { isPersonalLayout } from '@documenso/lib/utils/organisations';
import { trpc as trpcReact } from '@documenso/trpc/react';
import { cn } from '@documenso/ui/lib/utils';
import {
@@ -33,28 +35,7 @@ import {
} from '@documenso/ui/primitives/command';
import { useToast } from '@documenso/ui/primitives/use-toast';
-const DOCUMENTS_PAGES = [
- {
- label: msg`All documents`,
- path: '/documents?status=ALL',
- shortcut: DOCUMENTS_PAGE_SHORTCUT.replace('+', ''),
- },
- { label: msg`Draft documents`, path: '/documents?status=DRAFT' },
- {
- label: msg`Completed documents`,
- path: '/documents?status=COMPLETED',
- },
- { label: msg`Pending documents`, path: '/documents?status=PENDING' },
- { label: msg`Inbox documents`, path: '/documents?status=INBOX' },
-];
-
-const TEMPLATES_PAGES = [
- {
- label: msg`All templates`,
- path: '/templates',
- shortcut: TEMPLATES_PAGE_SHORTCUT.replace('+', ''),
- },
-];
+import { useOptionalCurrentTeam } from '~/providers/team';
const SETTINGS_PAGES = [
{
@@ -73,8 +54,10 @@ export type AppCommandMenuProps = {
export function AppCommandMenu({ open, onOpenChange }: AppCommandMenuProps) {
const { _ } = useLingui();
+ const { organisations } = useSession();
const navigate = useNavigate();
+ const currentTeam = useOptionalCurrentTeam();
const [isOpen, setIsOpen] = useState(() => open ?? false);
const [search, setSearch] = useState('');
@@ -94,6 +77,60 @@ export function AppCommandMenu({ open, onOpenChange }: AppCommandMenuProps) {
},
);
+ const teamUrl = useMemo(() => {
+ let teamUrl = currentTeam?.url || null;
+
+ if (!teamUrl && isPersonalLayout(organisations)) {
+ teamUrl = organisations[0].teams[0]?.url || null;
+ }
+
+ return teamUrl;
+ }, [currentTeam, organisations]);
+
+ const documentPageLinks = useMemo(() => {
+ if (!teamUrl) {
+ return [];
+ }
+
+ return [
+ {
+ label: msg`All documents`,
+ path: `/t/${teamUrl}/documents?status=ALL`,
+ shortcut: DOCUMENTS_PAGE_SHORTCUT.replace('+', ''),
+ },
+ {
+ label: msg`Draft documents`,
+ path: `/t/${teamUrl}/documents?status=DRAFT`,
+ },
+ {
+ label: msg`Completed documents`,
+ path: `/t/${teamUrl}/documents?status=COMPLETED`,
+ },
+ {
+ label: msg`Pending documents`,
+ path: `/t/${teamUrl}/documents?status=PENDING`,
+ },
+ {
+ label: msg`Inbox documents`,
+ path: `/t/${teamUrl}/documents?status=INBOX`,
+ },
+ ];
+ }, [currentTeam, organisations]);
+
+ const templatePageLinks = useMemo(() => {
+ if (!teamUrl) {
+ return [];
+ }
+
+ return [
+ {
+ label: msg`All templates`,
+ path: `/t/${teamUrl}/templates`,
+ shortcut: TEMPLATES_PAGE_SHORTCUT.replace('+', ''),
+ },
+ ];
+ }, [currentTeam, organisations]);
+
const searchResults = useMemo(() => {
if (!searchDocumentsData) {
return [];
@@ -145,8 +182,8 @@ export function AppCommandMenu({ open, onOpenChange }: AppCommandMenuProps) {
};
const goToSettings = useCallback(() => push(SETTINGS_PAGES[0].path), [push]);
- const goToDocuments = useCallback(() => push(DOCUMENTS_PAGES[0].path), [push]);
- const goToTemplates = useCallback(() => push(TEMPLATES_PAGES[0].path), [push]);
+ const goToDocuments = useCallback(() => push(documentPageLinks[0].path), [push]);
+ const goToTemplates = useCallback(() => push(templatePageLinks[0].path), [push]);
useHotkeys(['ctrl+k', 'meta+k'], toggleOpen, { preventDefault: true });
useHotkeys(SETTINGS_PAGE_SHORTCUT, goToSettings);
@@ -197,12 +234,16 @@ export function AppCommandMenu({ open, onOpenChange }: AppCommandMenuProps) {
)}
{!currentPage && (
<>
-
-
-
-
-
-
+ {documentPageLinks.length > 0 && (
+
+
+
+ )}
+ {templatePageLinks.length > 0 && (
+
+
+
+ )}
diff --git a/apps/remix/app/components/general/app-header.tsx b/apps/remix/app/components/general/app-header.tsx
index 4d8361e69..f907e7efd 100644
--- a/apps/remix/app/components/general/app-header.tsx
+++ b/apps/remix/app/components/general/app-header.tsx
@@ -1,12 +1,13 @@
import { type HTMLAttributes, useEffect, useState } from 'react';
-import { MenuIcon, SearchIcon } from 'lucide-react';
-import { Link, useLocation, useParams } from 'react-router';
+import { InboxIcon, MenuIcon, SearchIcon } from 'lucide-react';
+import { Link, useParams } from 'react-router';
-import type { SessionUser } from '@documenso/auth/server/lib/session/session';
-import type { TGetTeamsResponse } from '@documenso/lib/server-only/team/get-teams';
+import { useSession } from '@documenso/lib/client-only/providers/session';
+import { isPersonalLayout } from '@documenso/lib/utils/organisations';
import { getRootHref } from '@documenso/lib/utils/params';
import { cn } from '@documenso/ui/lib/utils';
+import { Button } from '@documenso/ui/primitives/button';
import { BrandingLogo } from '~/components/general/branding-logo';
@@ -14,15 +15,14 @@ import { AppCommandMenu } from './app-command-menu';
import { AppNavDesktop } from './app-nav-desktop';
import { AppNavMobile } from './app-nav-mobile';
import { MenuSwitcher } from './menu-switcher';
+import { OrgMenuSwitcher } from './org-menu-switcher';
-export type HeaderProps = HTMLAttributes & {
- user: SessionUser;
- teams: TGetTeamsResponse;
-};
+export type HeaderProps = HTMLAttributes;
-export const Header = ({ className, user, teams, ...props }: HeaderProps) => {
+export const Header = ({ className, ...props }: HeaderProps) => {
const params = useParams();
- const { pathname } = useLocation();
+
+ const { organisations } = useSession();
const [isCommandMenuOpen, setIsCommandMenuOpen] = useState(false);
const [isHamburgerMenuOpen, setIsHamburgerMenuOpen] = useState(false);
@@ -38,16 +38,6 @@ export const Header = ({ className, user, teams, ...props }: HeaderProps) => {
return () => window.removeEventListener('scroll', onScroll);
}, []);
- const isPathTeamUrl = (teamUrl: string) => {
- if (!pathname || !pathname.startsWith(`/t/`)) {
- return false;
- }
-
- return pathname.split('/')[2] === teamUrl;
- };
-
- const selectedTeam = teams?.find((team) => isPathTeamUrl(team.url));
-
return (