-
-
+ <>
+
+
+
-
- {children}
-
-
+
+ >
);
}
diff --git a/apps/web/src/app/(unauthenticated)/signin/page.tsx b/apps/web/src/app/(unauthenticated)/signin/page.tsx
index 50356a5bb..31baa502f 100644
--- a/apps/web/src/app/(unauthenticated)/signin/page.tsx
+++ b/apps/web/src/app/(unauthenticated)/signin/page.tsx
@@ -30,36 +30,31 @@ export default function SignInPage({ searchParams }: SignInPageProps) {
}
return (
-
-
Sign in to your account
+ <>
+
+
Sign in to your account
-
- Welcome back, we are lucky to have you.
-
-
-
-
- {NEXT_PUBLIC_DISABLE_SIGNUP !== 'true' && (
-
- Don't have an account?{' '}
-
- Sign up
-
+
+ Welcome back, we are lucky to have you.
- )}
-
-
- Forgot your password?
-
-
-
+
+
+
+
+ {process.env.NEXT_PUBLIC_DISABLE_SIGNUP !== 'true' && (
+
+ Don't have an account?{' '}
+
+ Sign up
+
+
+ )}
+
+ >
);
}
diff --git a/apps/web/src/app/(unauthenticated)/signup-layout.tsx b/apps/web/src/app/(unauthenticated)/signup-layout.tsx
new file mode 100644
index 000000000..61c390d13
--- /dev/null
+++ b/apps/web/src/app/(unauthenticated)/signup-layout.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+
+import Image from 'next/image';
+
+import backgroundPattern from '@documenso/assets/images/background-pattern.png';
+import { Card } from '@documenso/ui/primitives/card';
+
+import { NewHeader } from '../../components/(dashboard)/layout/new/new-header';
+
+type SignUpLayoutProps = {
+ children: React.ReactNode;
+};
+
+export default function SignUpLayout({ children }: SignUpLayoutProps) {
+ return (
+ <>
+
+
+
+
+ >
+ );
+}
diff --git a/apps/web/src/app/(unauthenticated)/signup/page.tsx b/apps/web/src/app/(unauthenticated)/signup/page.tsx
index b9365e1d5..ed7477041 100644
--- a/apps/web/src/app/(unauthenticated)/signup/page.tsx
+++ b/apps/web/src/app/(unauthenticated)/signup/page.tsx
@@ -9,6 +9,8 @@ import { decryptSecondaryData } from '@documenso/lib/server-only/crypto/decrypt'
import { SignUpForm } from '~/components/forms/signup';
+import SignUpLayout from '../signup-layout';
+
export const metadata: Metadata = {
title: 'Sign Up',
};
@@ -34,26 +36,28 @@ export default function SignUpPage({ searchParams }: SignUpPageProps) {
}
return (
-
-
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.
-
+
+ Create your account and start using state-of-the-art document signing. Open and beautiful
+ signing is within your grasp.
+
-
+
-
- Already have an account?{' '}
-
- Sign in instead
-
-
-
+
+ Already have an account?{' '}
+
+ Sign in instead
+
+
+ >
+
);
}
diff --git a/apps/web/src/components/(dashboard)/layout/new/new-header.tsx b/apps/web/src/components/(dashboard)/layout/new/new-header.tsx
new file mode 100644
index 000000000..66036359c
--- /dev/null
+++ b/apps/web/src/components/(dashboard)/layout/new/new-header.tsx
@@ -0,0 +1,87 @@
+'use client';
+
+import type { HTMLAttributes } from 'react';
+import { useState } from 'react';
+
+import Image from 'next/image';
+import Link from 'next/link';
+
+import LogoImage from '@documenso/assets/logo.png';
+import { cn } from '@documenso/ui/lib/utils';
+
+import { NewHamburgerMenu } from './new-mobile-hamburger';
+import { NewMobileNavigation } from './new-mobile-navigation';
+
+export type HeaderProps = HTMLAttributes
;
+
+export const NewHeader = ({ className, ...props }: HeaderProps) => {
+ const [isHamburgerMenuOpen, setIsHamburgerMenuOpen] = useState(false);
+
+ return (
+
+
+ setIsHamburgerMenuOpen(false)}>
+
+
+
+
+
+
+ Pricing
+
+
+
+ Blog
+
+
+
+ Open Startup
+
+
+
+ Sign in
+
+
+
+ Sign up
+
+
+
+
+ setIsHamburgerMenuOpen((v) => !v)}
+ isMenuOpen={isHamburgerMenuOpen}
+ />
+
+
+ );
+};
diff --git a/apps/web/src/components/(dashboard)/layout/new/new-mobile-hamburger.tsx b/apps/web/src/components/(dashboard)/layout/new/new-mobile-hamburger.tsx
new file mode 100644
index 000000000..8b7666df4
--- /dev/null
+++ b/apps/web/src/components/(dashboard)/layout/new/new-mobile-hamburger.tsx
@@ -0,0 +1,20 @@
+'use client';
+
+import { Menu, X } from 'lucide-react';
+
+import { Button } from '@documenso/ui/primitives/button';
+
+export interface HamburgerMenuProps {
+ isMenuOpen: boolean;
+ onToggleMenuOpen?: () => void;
+}
+
+export const NewHamburgerMenu = ({ isMenuOpen, onToggleMenuOpen }: HamburgerMenuProps) => {
+ return (
+
+
+
+ );
+};
diff --git a/apps/web/src/components/(dashboard)/layout/new/new-mobile-navigation.tsx b/apps/web/src/components/(dashboard)/layout/new/new-mobile-navigation.tsx
new file mode 100644
index 000000000..0d104eeb6
--- /dev/null
+++ b/apps/web/src/components/(dashboard)/layout/new/new-mobile-navigation.tsx
@@ -0,0 +1,151 @@
+'use client';
+
+import Image from 'next/image';
+import Link from 'next/link';
+
+import { motion, useReducedMotion } from 'framer-motion';
+import { FaXTwitter } from 'react-icons/fa6';
+import { LiaDiscord } from 'react-icons/lia';
+import { LuGithub } from 'react-icons/lu';
+
+import LogoImage from '@documenso/assets/logo.png';
+import { Sheet, SheetContent } from '@documenso/ui/primitives/sheet';
+
+export type MobileNavigationProps = {
+ isMenuOpen: boolean;
+ onMenuOpenChange?: (_value: boolean) => void;
+};
+
+export const MENU_NAVIGATION_LINKS = [
+ {
+ href: 'https://documenso.com/singleplayer',
+ text: 'Singleplayer',
+ },
+ {
+ href: 'https://documenso.com/blog',
+ text: 'Blog',
+ },
+ {
+ href: 'https://documenso.com/pricing',
+ text: 'Pricing',
+ },
+ {
+ href: 'https://documenso.com/open',
+ text: 'Open Startup',
+ },
+ {
+ href: 'https://status.documenso.com',
+ text: 'Status',
+ },
+ {
+ href: 'mailto:support@documenso.com',
+ text: 'Support',
+ target: '_blank',
+ },
+ {
+ href: 'https://documenso.com/privacy',
+ text: 'Privacy',
+ },
+ {
+ href: '/signin',
+ text: 'Sign in',
+ },
+ {
+ href: '/signup',
+ text: 'Sign up',
+ },
+];
+
+export const NewMobileNavigation = ({ isMenuOpen, onMenuOpenChange }: MobileNavigationProps) => {
+ const shouldReduceMotion = useReducedMotion();
+
+ const handleMenuItemClick = () => {
+ onMenuOpenChange?.(false);
+ };
+
+ return (
+
+
+
+
+
+
+
+ {MENU_NAVIGATION_LINKS.map(({ href, text, target }) => (
+
+ handleMenuItemClick()}
+ target={target}
+ >
+ {href === 'https://app.documenso.com/signup' ? (
+
+ {text}
+
+ ) : (
+ text
+ )}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/apps/web/src/components/(dashboard)/settings/layout/activity-back.tsx b/apps/web/src/components/(dashboard)/settings/layout/activity-back.tsx
new file mode 100644
index 000000000..d6ab1d080
--- /dev/null
+++ b/apps/web/src/components/(dashboard)/settings/layout/activity-back.tsx
@@ -0,0 +1,22 @@
+'use client';
+
+import { useRouter } from 'next/navigation';
+
+import { Button } from '@documenso/ui/primitives/button';
+
+export default function ActivityPageBackButton() {
+ const router = useRouter();
+ return (
+
+
+
+ );
+}
diff --git a/apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx b/apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx
index e87c47b67..17147305f 100644
--- a/apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx
+++ b/apps/web/src/components/(dashboard)/settings/layout/desktop-nav.tsx
@@ -5,7 +5,7 @@ import type { HTMLAttributes } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
-import { Braces, CreditCard, Lock, User, Users } from 'lucide-react';
+import { Braces, CreditCard, Globe2, Lock, User, Users } from 'lucide-react';
import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag';
import { cn } from '@documenso/ui/lib/utils';
@@ -91,6 +91,19 @@ export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
)}
+
+
+
+
);
};
diff --git a/apps/web/src/components/(dashboard)/settings/layout/header.tsx b/apps/web/src/components/(dashboard)/settings/layout/header.tsx
index 3fe567b81..bc8b33507 100644
--- a/apps/web/src/components/(dashboard)/settings/layout/header.tsx
+++ b/apps/web/src/components/(dashboard)/settings/layout/header.tsx
@@ -1,21 +1,33 @@
import React from 'react';
+import { cn } from '@documenso/ui/lib/utils';
+
export type SettingsHeaderProps = {
title: string;
subtitle: string;
children?: React.ReactNode;
+ titleChildren?: React.ReactNode;
+ className?: string;
};
-export const SettingsHeader = ({ children, title, subtitle }: SettingsHeaderProps) => {
+export const SettingsHeader = ({
+ children,
+ title,
+ subtitle,
+ titleChildren,
+ className,
+}: SettingsHeaderProps) => {
return (
<>
-
+
+
{titleChildren}
+
{children}
diff --git a/apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx b/apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx
index ad5ca96f6..152eb59a6 100644
--- a/apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx
+++ b/apps/web/src/components/(dashboard)/settings/layout/mobile-nav.tsx
@@ -5,7 +5,7 @@ import type { HTMLAttributes } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
-import { Braces, CreditCard, Lock, User, Users } from 'lucide-react';
+import { Braces, CreditCard, Globe2, Lock, User, Users } from 'lucide-react';
import { useFeatureFlags } from '@documenso/lib/client-only/providers/feature-flag';
import { cn } from '@documenso/ui/lib/utils';
@@ -94,6 +94,19 @@ export const MobileNav = ({ className, ...props }: MobileNavProps) => {
)}
+
+
+
+
);
};
diff --git a/apps/web/src/components/forms/public-profile.tsx b/apps/web/src/components/forms/public-profile.tsx
new file mode 100644
index 000000000..5ceb2c6a5
--- /dev/null
+++ b/apps/web/src/components/forms/public-profile.tsx
@@ -0,0 +1,165 @@
+'use client';
+
+import { useRef } from 'react';
+
+import { useRouter } from 'next/navigation';
+
+import { zodResolver } from '@hookform/resolvers/zod';
+import { Copy } from 'lucide-react';
+import { useForm } from 'react-hook-form';
+import { z } from 'zod';
+
+import type { User } from '@documenso/prisma/client';
+import { TRPCClientError } from '@documenso/trpc/client';
+import { trpc } from '@documenso/trpc/react';
+import { cn } from '@documenso/ui/lib/utils';
+import { Button } from '@documenso/ui/primitives/button';
+import {
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from '@documenso/ui/primitives/form/form';
+import { Input } from '@documenso/ui/primitives/input';
+import { Textarea } from '@documenso/ui/primitives/textarea';
+import { useToast } from '@documenso/ui/primitives/use-toast';
+
+export const ZPublicProfileFormSchema = z.object({
+ profileURL: z.string().trim().min(1, { message: 'Please enter a valid URL slug.' }),
+ profileBio: z
+ .string()
+ .max(256, { message: 'Profile bio must not exceed 256 characters' })
+ .optional(),
+});
+
+export type TPublicProfileFormSchema = z.infer