feat: darker dark theme
This commit is contained in:
@@ -39,21 +39,21 @@ export default function BlogPostPage({ params }: { params: { post: string } }) {
|
|||||||
const MDXContent = useMDXComponent(post.body.code);
|
const MDXContent = useMDXComponent(post.body.code);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article className="prose prose-slate mx-auto py-8">
|
<article className="prose dark:prose-invert mx-auto py-8">
|
||||||
<div className="mb-6 text-center">
|
<div className="mb-6 text-center">
|
||||||
<time dateTime={post.date} className="mb-1 text-xs text-gray-600">
|
<time dateTime={post.date} className="text-muted-foreground mb-1 text-xs">
|
||||||
{new Date(post.date).toLocaleDateString()}
|
{new Date(post.date).toLocaleDateString()}
|
||||||
</time>
|
</time>
|
||||||
|
|
||||||
<h1 className="text-3xl font-bold">{post.title}</h1>
|
<h1 className="text-3xl font-bold">{post.title}</h1>
|
||||||
|
|
||||||
<div className="not-prose relative -mt-2 flex items-center gap-x-4 border-b border-t py-4">
|
<div className="not-prose relative -mt-2 flex items-center gap-x-4 border-b border-t py-4">
|
||||||
<div className="h-10 w-10 rounded-full bg-gray-50">
|
<div className="bg-foreground h-10 w-10 rounded-full">
|
||||||
{post.authorImage && (
|
{post.authorImage && (
|
||||||
<img
|
<img
|
||||||
src={post.authorImage}
|
src={post.authorImage}
|
||||||
alt={`Image of ${post.authorName}`}
|
alt={`Image of ${post.authorName}`}
|
||||||
className="h-10 w-10 rounded-full bg-gray-50"
|
className="bg-foreground/10 h-10 w-10 rounded-full"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ export default function BlogPage() {
|
|||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h1 className="text-3xl font-bold lg:text-5xl">From the blog</h1>
|
<h1 className="text-3xl font-bold lg:text-5xl">From the blog</h1>
|
||||||
|
|
||||||
<p className="mx-auto mt-4 max-w-xl text-center text-lg leading-normal text-[#31373D]">
|
<p className="text-muted-foreground mx-auto mt-4 max-w-xl text-center text-lg leading-normal">
|
||||||
Get the latest news from Documenso, including product updates, team announcements and
|
Get the latest news from Documenso, including product updates, team announcements and
|
||||||
more!
|
more!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-10 divide-y divide-slate-100 border-t border-slate-200 ">
|
<div className="divide-muted-foreground/20 border-muted-foreground/20 mt-10 divide-y border-t">
|
||||||
{blogPosts.map((post, i) => (
|
{blogPosts.map((post, i) => (
|
||||||
<article
|
<article
|
||||||
key={`blog-${i}`}
|
key={`blog-${i}`}
|
||||||
@@ -57,12 +57,12 @@ export default function BlogPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative mt-4 flex items-center gap-x-4">
|
<div className="relative mt-4 flex items-center gap-x-4">
|
||||||
<div className="h-10 w-10 rounded-full bg-slate-50">
|
<div className="bg-foreground/5 h-10 w-10 rounded-full">
|
||||||
{post.authorImage && (
|
{post.authorImage && (
|
||||||
<img
|
<img
|
||||||
src={post.authorImage}
|
src={post.authorImage}
|
||||||
alt={`Image of ${post.authorName}`}
|
alt={`Image of ${post.authorName}`}
|
||||||
className="h-10 w-10 rounded-full bg-slate-50"
|
className="bg-foreground/5 h-10 w-10 rounded-full"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -58,40 +58,40 @@ export default async function ClaimedPlanPage({ searchParams = {} }: ClaimedPlan
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-12">
|
<div className="mt-12">
|
||||||
<h1 className="text-3xl font-bold text-slate-900 md:text-4xl">
|
<h1 className="text-foreground text-3xl font-bold md:text-4xl">
|
||||||
Welcome to the <span className="text-primary">open signing</span> revolution{' '}
|
Welcome to the <span className="text-primary">open signing</span> revolution{' '}
|
||||||
<u>{user.name}</u>
|
<u>{user.name}</u>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="mt-4 max-w-prose text-base text-slate-500 md:text-lg">
|
<p className="text-muted-foreground mt-4 max-w-prose text-base md:text-lg">
|
||||||
It's not every day you get to be part of a revolution.
|
It's not every day you get to be part of a revolution.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p className="mt-4 max-w-prose text-base text-slate-500 md:text-lg">
|
<p className="text-muted-foreground mt-4 max-w-prose text-base md:text-lg">
|
||||||
But today is that day, by signing up to Documenso, you're joining a movement of people who
|
But today is that day, by signing up to Documenso, you're joining a movement of people who
|
||||||
want to make the world a better place.
|
want to make the world a better place.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p className="mt-4 max-w-prose text-base text-slate-500 md:text-lg">
|
<p className="text-muted-foreground mt-4 max-w-prose text-base md:text-lg">
|
||||||
We're going to change the way people sign documents. We're going to make it easier, faster,
|
We're going to change the way people sign documents. We're going to make it easier, faster,
|
||||||
and more secure. And we're going to do it together.
|
and more secure. And we're going to do it together.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="mt-12">
|
<div className="mt-12">
|
||||||
<h2 className="text-2xl font-bold text-slate-900">Let's do it together</h2>
|
<h2 className="text-foreground text-2xl font-bold">Let's do it together</h2>
|
||||||
|
|
||||||
<div className="-mx-4 mt-8 flex md:-mx-8">
|
<div className="-mx-4 mt-8 flex md:-mx-8">
|
||||||
<div className="flex flex-1 flex-col justify-end gap-y-4 border-r px-4 last:border-r-0 md:px-8 lg:flex-none">
|
<div className="flex flex-1 flex-col justify-end gap-y-4 border-r px-4 last:border-r-0 md:px-8 lg:flex-none">
|
||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-4xl font-semibold text-slate-900 md:text-5xl',
|
'text-foreground text-4xl font-semibold md:text-5xl',
|
||||||
fontCaveat.className,
|
fontCaveat.className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Timur
|
Timur
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p className="text-sm text-slate-500 md:text-lg">
|
<p className="text-muted-foreground text-sm md:text-lg">
|
||||||
Timur Ercan
|
Timur Ercan
|
||||||
<span className="block lg:hidden" />
|
<span className="block lg:hidden" />
|
||||||
<span className="hidden lg:inline"> - </span>
|
<span className="hidden lg:inline"> - </span>
|
||||||
@@ -102,14 +102,14 @@ export default async function ClaimedPlanPage({ searchParams = {} }: ClaimedPlan
|
|||||||
<div className="flex flex-1 flex-col justify-end gap-y-4 border-r px-4 last:border-r-0 md:px-8 lg:flex-none">
|
<div className="flex flex-1 flex-col justify-end gap-y-4 border-r px-4 last:border-r-0 md:px-8 lg:flex-none">
|
||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-4xl font-semibold text-slate-900 md:text-5xl',
|
'text-foreground text-4xl font-semibold md:text-5xl',
|
||||||
fontCaveat.className,
|
fontCaveat.className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Lucas
|
Lucas
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p className="text-sm text-slate-500 md:text-lg">
|
<p className="text-muted-foreground text-sm md:text-lg">
|
||||||
Lucas Smith
|
Lucas Smith
|
||||||
<span className="block lg:hidden" />
|
<span className="block lg:hidden" />
|
||||||
<span className="hidden lg:inline"> - </span>
|
<span className="hidden lg:inline"> - </span>
|
||||||
@@ -119,12 +119,16 @@ export default async function ClaimedPlanPage({ searchParams = {} }: ClaimedPlan
|
|||||||
|
|
||||||
<div className="flex flex-1 flex-col justify-end gap-y-4 border-r px-4 last:border-r-0 md:px-8 lg:flex-none">
|
<div className="flex flex-1 flex-col justify-end gap-y-4 border-r px-4 last:border-r-0 md:px-8 lg:flex-none">
|
||||||
{signatureDataUrl && (
|
{signatureDataUrl && (
|
||||||
<img src={signatureDataUrl} alt="your-signature" className="max-w-[172px]" />
|
<img
|
||||||
|
src={signatureDataUrl}
|
||||||
|
alt="your-signature"
|
||||||
|
className="max-w-[172px] dark:invert"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{!signatureDataUrl && (
|
{!signatureDataUrl && (
|
||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-4xl font-semibold text-slate-900 md:text-5xl',
|
'text-foreground text-4xl font-semibold md:text-5xl',
|
||||||
fontCaveat.className,
|
fontCaveat.className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -132,7 +136,7 @@ export default async function ClaimedPlanPage({ searchParams = {} }: ClaimedPlan
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<p className="text-sm text-slate-500 md:text-lg">
|
<p className="text-muted-foreground text-sm md:text-lg">
|
||||||
{user.name}
|
{user.name}
|
||||||
<span className="block lg:hidden" />
|
<span className="block lg:hidden" />
|
||||||
<span className="hidden lg:inline"> - </span>
|
<span className="hidden lg:inline"> - </span>
|
||||||
@@ -143,20 +147,20 @@ export default async function ClaimedPlanPage({ searchParams = {} }: ClaimedPlan
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-12">
|
<div className="mt-12">
|
||||||
<h2 className="text-2xl font-bold text-slate-900">Your sign in details</h2>
|
<h2 className="text-foreground text-2xl font-bold">Your sign in details</h2>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<p className="text-lg text-slate-500">
|
<p className="text-muted-foreground text-lg">
|
||||||
<span className="font-bold">Email:</span> {user.email}
|
<span className="font-bold">Email:</span> {user.email}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p className="mt-2 text-lg text-slate-500">
|
<p className="text-muted-foreground mt-2 text-lg">
|
||||||
<span className="font-bold">Password:</span>{' '}
|
<span className="font-bold">Password:</span>{' '}
|
||||||
<PasswordReveal password={password ?? 'password'} />
|
<PasswordReveal password={password ?? 'password'} />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="mt-4 text-sm italic text-slate-500">
|
<p className="text-muted-foreground mt-4 text-sm italic">
|
||||||
This is a temporary password. Please change it as soon as possible.
|
This is a temporary password. Please change it as soon as possible.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export type MarketingLayoutProps = {
|
|||||||
export default function MarketingLayout({ children }: MarketingLayoutProps) {
|
export default function MarketingLayout({ children }: MarketingLayoutProps) {
|
||||||
return (
|
return (
|
||||||
<div className="relative max-w-[100vw] overflow-y-auto overflow-x-hidden pt-20 md:pt-28">
|
<div className="relative max-w-[100vw] overflow-y-auto overflow-x-hidden pt-20 md:pt-28">
|
||||||
<div className="fixed left-0 top-0 z-50 w-full bg-white/50 backdrop-blur-md">
|
<div className="bg-background/50 fixed left-0 top-0 z-50 w-full backdrop-blur-md">
|
||||||
<Header className="mx-auto h-16 max-w-screen-xl px-4 md:h-20 lg:px-8" />
|
<Header className="mx-auto h-16 max-w-screen-xl px-4 md:h-20 lg:px-8" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
84
apps/marketing/src/app/(marketing)/oss-friends/container.tsx
Normal file
84
apps/marketing/src/app/(marketing)/oss-friends/container.tsx
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
import { Variants, motion } from 'framer-motion';
|
||||||
|
|
||||||
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
|
import { Button } from '@documenso/ui/primitives/button';
|
||||||
|
import { Card, CardContent, CardTitle } from '@documenso/ui/primitives/card';
|
||||||
|
|
||||||
|
import { TOSSFriendsSchema } from './schema';
|
||||||
|
|
||||||
|
const ContainerVariants: Variants = {
|
||||||
|
initial: {
|
||||||
|
opacity: 0,
|
||||||
|
},
|
||||||
|
animate: {
|
||||||
|
opacity: 1,
|
||||||
|
transition: {
|
||||||
|
staggerChildren: 0.075,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const CardVariants: Variants = {
|
||||||
|
initial: {
|
||||||
|
opacity: 0,
|
||||||
|
y: 50,
|
||||||
|
},
|
||||||
|
animate: {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
transition: {
|
||||||
|
duration: 0.5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const randomDegrees = () => {
|
||||||
|
const degrees = [45, 120, -140, -45];
|
||||||
|
|
||||||
|
return degrees[Math.floor(Math.random() * degrees.length)];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OSSFriendsContainerProps = {
|
||||||
|
className?: string;
|
||||||
|
ossFriends: TOSSFriendsSchema;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OSSFriendsContainer = ({ className, ossFriends }: OSSFriendsContainerProps) => {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className={cn('grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3', className)}
|
||||||
|
variants={ContainerVariants}
|
||||||
|
initial="initial"
|
||||||
|
animate="animate"
|
||||||
|
>
|
||||||
|
{ossFriends.map((friend, index) => (
|
||||||
|
<motion.div key={index} className="h-full w-full" variants={CardVariants}>
|
||||||
|
<Card
|
||||||
|
className="h-full"
|
||||||
|
degrees={randomDegrees()}
|
||||||
|
gradient={index % 2 === 0}
|
||||||
|
spotlight={index % 2 !== 0}
|
||||||
|
>
|
||||||
|
<CardContent className="flex h-full flex-col p-6">
|
||||||
|
<CardTitle>
|
||||||
|
<Link href={friend.href}>{friend.name}</Link>
|
||||||
|
</CardTitle>
|
||||||
|
|
||||||
|
<p className="text-foreground mt-4 flex-1 text-sm">{friend.description}</p>
|
||||||
|
|
||||||
|
<div className="mt-8">
|
||||||
|
<Link target="_blank" href={friend.href}>
|
||||||
|
<Button>Learn more</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,152 +1,23 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
|
||||||
|
|
||||||
import { Variants, motion } from 'framer-motion';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { Button } from '@documenso/ui/primitives/button';
|
|
||||||
import { Card, CardContent, CardTitle } from '@documenso/ui/primitives/card';
|
|
||||||
|
|
||||||
import backgroundPattern from '~/assets/background-pattern.png';
|
import backgroundPattern from '~/assets/background-pattern.png';
|
||||||
|
|
||||||
const OSSFriends = [
|
import { OSSFriendsContainer } from './container';
|
||||||
{
|
import { TOSSFriendsSchema, ZOSSFriendsSchema } from './schema';
|
||||||
name: 'BoxyHQ',
|
|
||||||
description:
|
|
||||||
'BoxyHQ’s suite of APIs for security and privacy helps engineering teams build and ship compliant cloud applications faster.',
|
|
||||||
href: 'https://boxyhq.com',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Cal.com',
|
|
||||||
description:
|
|
||||||
'Cal.com is a scheduling tool that helps you schedule meetings without the back-and-forth emails.',
|
|
||||||
href: 'https://cal.com',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Crowd.dev',
|
|
||||||
description:
|
|
||||||
'Centralize community, product, and customer data to understand which companies are engaging with your open source project.',
|
|
||||||
href: 'https://www.crowd.dev',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Documenso',
|
|
||||||
description:
|
|
||||||
'The Open-Source DocuSign Alternative. We aim to earn your trust by enabling you to self-host the platform and examine its inner workings.',
|
|
||||||
href: 'https://documenso.com',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Erxes',
|
|
||||||
description:
|
|
||||||
'The Open-Source HubSpot Alternative. A single XOS enables to create unique and life-changing experiences that work for all types of business.',
|
|
||||||
href: 'https://erxes.io',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Formbricks',
|
|
||||||
description:
|
|
||||||
'Survey granular user segments at any point in the user journey. Gather up to 6x more insights with targeted micro-surveys. All open-source.',
|
|
||||||
href: 'https://formbricks.com',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Forward Email',
|
|
||||||
description:
|
|
||||||
'Free email forwarding for custom domains. For 6 years and counting, we are the go-to email service for thousands of creators, developers, and businesses.',
|
|
||||||
href: 'https://forwardemail.net',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'GitWonk',
|
|
||||||
description:
|
|
||||||
'GitWonk is an open-source technical documentation tool, designed and built focusing on the developer experience.',
|
|
||||||
href: 'https://gitwonk.com',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Hanko',
|
|
||||||
description:
|
|
||||||
'Open-source authentication and user management for the passkey era. Integrated in minutes, for web and mobile apps.',
|
|
||||||
href: 'https://www.hanko.io',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'HTMX',
|
|
||||||
description:
|
|
||||||
'HTMX is a dependency-free JavaScript library that allows you to access AJAX, CSS Transitions, WebSockets, and Server Sent Events directly in HTML.',
|
|
||||||
href: 'https://htmx.org',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Infisical',
|
|
||||||
description:
|
|
||||||
'Open source, end-to-end encrypted platform that lets you securely manage secrets and configs across your team, devices, and infrastructure.',
|
|
||||||
href: 'https://infisical.com',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Novu',
|
|
||||||
description:
|
|
||||||
'The open-source notification infrastructure for developers. Simple components and APIs for managing all communication channels in one place.',
|
|
||||||
href: 'https://novu.co',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'OpenBB',
|
|
||||||
description:
|
|
||||||
'Democratizing investment research through an open source financial ecosystem. The OpenBB Terminal allows everyone to perform investment research, from everywhere.',
|
|
||||||
href: 'https://openbb.co',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Sniffnet',
|
|
||||||
description:
|
|
||||||
'Sniffnet is a network monitoring tool to help you easily keep track of your Internet traffic.',
|
|
||||||
href: 'https://www.sniffnet.net',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Typebot',
|
|
||||||
description:
|
|
||||||
'Typebot gives you powerful blocks to create unique chat experiences. Embed them anywhere on your apps and start collecting results like magic.',
|
|
||||||
href: 'https://typebot.io',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Webiny',
|
|
||||||
description:
|
|
||||||
'Open-source enterprise-grade serverless CMS. Own your data. Scale effortlessly. Customize everything.',
|
|
||||||
href: 'https://www.webiny.com',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Webstudio',
|
|
||||||
description: 'Webstudio is an open source alternative to Webflow',
|
|
||||||
href: 'https://webstudio.is',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const ContainerVariants: Variants = {
|
export default async function OSSFriendsPage() {
|
||||||
initial: {
|
const ossFriends: TOSSFriendsSchema = await fetch('https://formbricks.com/api/oss-friends', {
|
||||||
opacity: 0,
|
next: {
|
||||||
},
|
revalidate: 3600,
|
||||||
animate: {
|
|
||||||
opacity: 1,
|
|
||||||
transition: {
|
|
||||||
staggerChildren: 0.075,
|
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
};
|
.then(async (res) => res.json())
|
||||||
|
.then(async (data) => z.object({ data: ZOSSFriendsSchema }).parseAsync(data))
|
||||||
|
.then(({ data }) => data)
|
||||||
|
.catch(() => []);
|
||||||
|
|
||||||
const CardVariants: Variants = {
|
|
||||||
initial: {
|
|
||||||
opacity: 0,
|
|
||||||
y: 50,
|
|
||||||
},
|
|
||||||
animate: {
|
|
||||||
opacity: 1,
|
|
||||||
y: 0,
|
|
||||||
transition: {
|
|
||||||
duration: 0.5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const randomDegrees = () => {
|
|
||||||
const degrees = [45, 120, -140, -45];
|
|
||||||
|
|
||||||
return degrees[Math.floor(Math.random() * degrees.length)];
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function OSSFriendsPage() {
|
|
||||||
return (
|
return (
|
||||||
<div className="relative mt-12">
|
<div className="relative mt-12">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
@@ -154,49 +25,19 @@ export default function OSSFriendsPage() {
|
|||||||
Our <span title="Open Source Software">OSS</span> Friends
|
Our <span title="Open Source Software">OSS</span> Friends
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="mx-auto mt-4 max-w-[55ch] text-lg leading-normal text-[#31373D]">
|
<p className="text-foreground mx-auto mt-4 max-w-[55ch] text-lg leading-normal">
|
||||||
We love open source and so should you, below you can find a list of our friends who are
|
We love open source and so should you, below you can find a list of our friends who are
|
||||||
just as passionate about open source as we are.
|
just as passionate about open source as we are.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<motion.div
|
<OSSFriendsContainer className="mt-12" ossFriends={ossFriends} />
|
||||||
className="mt-12 grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3"
|
|
||||||
variants={ContainerVariants}
|
|
||||||
initial="initial"
|
|
||||||
animate="animate"
|
|
||||||
>
|
|
||||||
{OSSFriends.map((friend, index) => (
|
|
||||||
<motion.div key={index} className="h-full w-full" variants={CardVariants}>
|
|
||||||
<Card
|
|
||||||
className="h-full"
|
|
||||||
degrees={randomDegrees()}
|
|
||||||
gradient={index % 2 === 0}
|
|
||||||
spotlight={index % 2 !== 0}
|
|
||||||
>
|
|
||||||
<CardContent className="flex h-full flex-col p-6">
|
|
||||||
<CardTitle>
|
|
||||||
<Link href={friend.href}>{friend.name}</Link>
|
|
||||||
</CardTitle>
|
|
||||||
|
|
||||||
<p className="mt-4 flex-1 text-sm text-slate-700">{friend.description}</p>
|
|
||||||
|
|
||||||
<div className="mt-8">
|
|
||||||
<Link target="_blank" href={friend.href}>
|
|
||||||
<Button>Learn more</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</motion.div>
|
|
||||||
))}
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<div className="absolute inset-0 -z-10 flex items-start justify-center">
|
<div className="absolute inset-0 -z-10 flex items-start justify-center">
|
||||||
<Image
|
<Image
|
||||||
src={backgroundPattern}
|
src={backgroundPattern}
|
||||||
alt="background pattern"
|
alt="background pattern"
|
||||||
className="-mr-[15vw] -mt-[15vh] h-full max-h-[150vh] scale-125 object-cover md:-mr-[50vw] md:scale-150 lg:scale-[175%]"
|
className="-mr-[15vw] -mt-[15vh] h-full max-h-[150vh] scale-125 object-cover dark:invert dark:sepia md:-mr-[50vw] md:scale-150 lg:scale-[175%]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
11
apps/marketing/src/app/(marketing)/oss-friends/schema.ts
Normal file
11
apps/marketing/src/app/(marketing)/oss-friends/schema.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const ZOSSFriendsSchema = z.array(
|
||||||
|
z.object({
|
||||||
|
name: z.string(),
|
||||||
|
href: z.string().url(),
|
||||||
|
description: z.string(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export type TOSSFriendsSchema = z.infer<typeof ZOSSFriendsSchema>;
|
||||||
@@ -24,10 +24,10 @@ export default function PricingPage() {
|
|||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h1 className="text-3xl font-bold lg:text-5xl">Pricing</h1>
|
<h1 className="text-3xl font-bold lg:text-5xl">Pricing</h1>
|
||||||
|
|
||||||
<p className="mt-4 text-lg leading-normal text-[#31373D]">
|
<p className="text-foreground mt-4 text-lg leading-normal">
|
||||||
Designed for every stage of your journey.
|
Designed for every stage of your journey.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-lg leading-normal text-[#31373D]">Get started today.</p>
|
<p className="text-foreground text-lg leading-normal">Get started today.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-12">
|
<div className="mt-12">
|
||||||
@@ -45,7 +45,7 @@ export default function PricingPage() {
|
|||||||
What is the difference between the plans?
|
What is the difference between the plans?
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
|
|
||||||
<AccordionContent className="max-w-prose text-sm leading-relaxed text-slate-500">
|
<AccordionContent className="text-muted-foreground max-w-prose text-sm leading-relaxed">
|
||||||
You can self-host Documenso for free or use our ready-to-use hosted version. The
|
You can self-host Documenso for free or use our ready-to-use hosted version. The
|
||||||
hosted version comes with additional support, painless scalability and more. Early
|
hosted version comes with additional support, painless scalability and more. Early
|
||||||
adopters will get access to all features we build this year, for no additional cost!
|
adopters will get access to all features we build this year, for no additional cost!
|
||||||
@@ -59,7 +59,7 @@ export default function PricingPage() {
|
|||||||
How do you handle my data?
|
How do you handle my data?
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
|
|
||||||
<AccordionContent className="max-w-prose text-sm leading-relaxed text-slate-500">
|
<AccordionContent className="text-muted-foreground max-w-prose text-sm leading-relaxed">
|
||||||
Securely. Our data centers are located in Frankfurt (Germany), giving us the best
|
Securely. Our data centers are located in Frankfurt (Germany), giving us the best
|
||||||
local privacy laws. We are very aware of the sensitive nature of our data and follow
|
local privacy laws. We are very aware of the sensitive nature of our data and follow
|
||||||
best practices to ensure the security and integrity of the data entrusted to us.
|
best practices to ensure the security and integrity of the data entrusted to us.
|
||||||
@@ -71,7 +71,7 @@ export default function PricingPage() {
|
|||||||
Why should I use your hosting service?
|
Why should I use your hosting service?
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
|
|
||||||
<AccordionContent className="max-w-prose text-sm leading-relaxed text-slate-500">
|
<AccordionContent className="text-muted-foreground max-w-prose text-sm leading-relaxed">
|
||||||
Using our hosted version is the easiest way to get started, you can simply subscribe
|
Using our hosted version is the easiest way to get started, you can simply subscribe
|
||||||
and start signing your documents. We take care of the infrastructure, so you can focus
|
and start signing your documents. We take care of the infrastructure, so you can focus
|
||||||
on your business. Additionally, when using our hosted version you benefit from our
|
on your business. Additionally, when using our hosted version you benefit from our
|
||||||
@@ -84,7 +84,7 @@ export default function PricingPage() {
|
|||||||
How can I contribute?
|
How can I contribute?
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
|
|
||||||
<AccordionContent className="max-w-prose text-sm leading-relaxed text-slate-500">
|
<AccordionContent className="text-muted-foreground max-w-prose text-sm leading-relaxed">
|
||||||
That's awesome. You can take a look at the current{' '}
|
That's awesome. You can take a look at the current{' '}
|
||||||
<Link
|
<Link
|
||||||
className="text-documenso-700 font-bold"
|
className="text-documenso-700 font-bold"
|
||||||
@@ -111,7 +111,7 @@ export default function PricingPage() {
|
|||||||
Can I use Documenso commercially?
|
Can I use Documenso commercially?
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
|
|
||||||
<AccordionContent className="max-w-prose text-sm leading-relaxed text-slate-500">
|
<AccordionContent className="text-muted-foreground max-w-prose text-sm leading-relaxed">
|
||||||
Yes! Documenso is offered under the GNU AGPL V3 open source license. This means you
|
Yes! Documenso is offered under the GNU AGPL V3 open source license. This means you
|
||||||
can use it for free and even modify it to fit your needs, as long as you publish your
|
can use it for free and even modify it to fit your needs, as long as you publish your
|
||||||
changes under the same license.
|
changes under the same license.
|
||||||
@@ -123,7 +123,7 @@ export default function PricingPage() {
|
|||||||
Why should I prefer Documenso over DocuSign or some other signing tool?
|
Why should I prefer Documenso over DocuSign or some other signing tool?
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
|
|
||||||
<AccordionContent className="max-w-prose text-sm leading-relaxed text-slate-500">
|
<AccordionContent className="text-muted-foreground max-w-prose text-sm leading-relaxed">
|
||||||
Documenso is a community effort to create an open and vibrant ecosystem around a tool,
|
Documenso is a community effort to create an open and vibrant ecosystem around a tool,
|
||||||
everybody is free to use and adapt. By being truly open we want to create trusted
|
everybody is free to use and adapt. By being truly open we want to create trusted
|
||||||
infrastructure for the future of the internet.
|
infrastructure for the future of the internet.
|
||||||
@@ -135,7 +135,7 @@ export default function PricingPage() {
|
|||||||
Where can I get support?
|
Where can I get support?
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
|
|
||||||
<AccordionContent className="max-w-prose text-sm leading-relaxed text-slate-500">
|
<AccordionContent className="text-muted-foreground max-w-prose text-sm leading-relaxed">
|
||||||
We are happy to assist you at{' '}
|
We are happy to assist you at{' '}
|
||||||
<Link
|
<Link
|
||||||
className="text-documenso-700 font-bold"
|
className="text-documenso-700 font-bold"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Inter } from 'next/font/google';
|
|||||||
|
|
||||||
import { Toaster } from '@documenso/ui/primitives/toaster';
|
import { Toaster } from '@documenso/ui/primitives/toaster';
|
||||||
|
|
||||||
|
import { ThemeProvider } from '~/providers/next-theme';
|
||||||
import { PlausibleProvider } from '~/providers/plausible';
|
import { PlausibleProvider } from '~/providers/plausible';
|
||||||
|
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
@@ -43,7 +44,10 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<PlausibleProvider>{children}</PlausibleProvider>
|
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||||
|
<PlausibleProvider>{children}</PlausibleProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export default function NotFound() {
|
|||||||
<Image
|
<Image
|
||||||
src={backgroundPattern}
|
src={backgroundPattern}
|
||||||
alt="background pattern"
|
alt="background pattern"
|
||||||
className="-mr-[50vw] -mt-[15vh] h-full scale-100 object-cover md:scale-100 lg:scale-[100%]"
|
className="-mr-[50vw] -mt-[15vh] h-full scale-100 object-cover dark:invert dark:sepia md:scale-100 lg:scale-[100%]"
|
||||||
priority
|
priority
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ export const Callout = ({ starCount }: CalloutProps) => {
|
|||||||
className="rounded-full bg-transparent backdrop-blur-sm"
|
className="rounded-full bg-transparent backdrop-blur-sm"
|
||||||
onClick={onSignUpClick}
|
onClick={onSignUpClick}
|
||||||
>
|
>
|
||||||
Get the Early Adopters Plan
|
Get the Community Plan
|
||||||
<span className="bg-primary -mr-2.5 ml-2.5 rounded-full px-2 py-1.5 text-xs">
|
<span className="bg-primary dark:text-background -mr-2.5 ml-2.5 rounded-full px-2 py-1.5 text-xs">
|
||||||
$30/mo. forever!
|
$30/mo. forever!
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -55,7 +55,7 @@ export const Callout = ({ starCount }: CalloutProps) => {
|
|||||||
<Github className="mr-2 h-5 w-5" />
|
<Github className="mr-2 h-5 w-5" />
|
||||||
Star on Github
|
Star on Github
|
||||||
{starCount && starCount > 0 && (
|
{starCount && starCount > 0 && (
|
||||||
<span className="bg-primary -mr-2.5 ml-2.5 rounded-full px-2 py-1.5 text-xs">
|
<span className="bg-primary dark:text-background -mr-2.5 ml-2.5 rounded-full px-2 py-1.5 text-xs">
|
||||||
{starCount.toLocaleString('en-US')}
|
{starCount.toLocaleString('en-US')}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ export const ClaimPlanDialog = ({ className, planId, children }: ClaimPlanDialog
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-slate-500">Name</Label>
|
<Label className="text-muted-foreground">Name</Label>
|
||||||
|
|
||||||
<Input type="text" className="mt-2" {...register('name')} autoFocus />
|
<Input type="text" className="mt-2" {...register('name')} autoFocus />
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ export const ClaimPlanDialog = ({ className, planId, children }: ClaimPlanDialog
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label className="text-slate-500">Email</Label>
|
<Label className="text-muted-foreground">Email</Label>
|
||||||
|
|
||||||
<Input type="email" className="mt-2" {...register('email')} />
|
<Input type="email" className="mt-2" {...register('email')} />
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const FasterSmarterBeautifulBento = ({
|
|||||||
<Image
|
<Image
|
||||||
src={backgroundPattern}
|
src={backgroundPattern}
|
||||||
alt="background pattern"
|
alt="background pattern"
|
||||||
className="h-full scale-125 object-cover md:scale-150 lg:scale-[175%]"
|
className="h-full scale-125 object-cover dark:invert dark:sepia md:scale-150 lg:scale-[175%]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="px-0 text-[22px] font-semibold md:px-12 md:text-4xl lg:px-24">
|
<h2 className="px-0 text-[22px] font-semibold md:px-12 md:text-4xl lg:px-24">
|
||||||
@@ -33,41 +33,53 @@ export const FasterSmarterBeautifulBento = ({
|
|||||||
<div className="mt-6 grid grid-cols-2 gap-8 md:mt-8">
|
<div className="mt-6 grid grid-cols-2 gap-8 md:mt-8">
|
||||||
<Card className="col-span-2" degrees={45} gradient>
|
<Card className="col-span-2" degrees={45} gradient>
|
||||||
<CardContent className="grid grid-cols-12 gap-8 overflow-hidden p-6 lg:aspect-[2.5/1]">
|
<CardContent className="grid grid-cols-12 gap-8 overflow-hidden p-6 lg:aspect-[2.5/1]">
|
||||||
<p className="col-span-12 leading-relaxed text-[#555E67] lg:col-span-6">
|
<p className="text-foreground/80 col-span-12 leading-relaxed lg:col-span-6">
|
||||||
<strong className="block">Fast.</strong>
|
<strong className="block">Fast.</strong>
|
||||||
When it comes to sending or receiving a contract, you can count on lightning-fast
|
When it comes to sending or receiving a contract, you can count on lightning-fast
|
||||||
speeds.
|
speeds.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="col-span-12 -my-6 -mr-6 flex items-end justify-end pt-12 lg:col-span-6">
|
<div className="col-span-12 -my-6 -mr-6 flex items-end justify-end pt-12 lg:col-span-6">
|
||||||
<Image src={cardFastFigure} alt="its fast" className="max-w-[80%] lg:max-w-none" />
|
<Image
|
||||||
|
src={cardFastFigure}
|
||||||
|
alt="its fast"
|
||||||
|
className="max-w-[80%] dark:contrast-[70%] dark:hue-rotate-180 dark:invert lg:max-w-none"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="col-span-2 lg:col-span-1" spotlight>
|
<Card className="col-span-2 lg:col-span-1" spotlight>
|
||||||
<CardContent className="grid grid-cols-1 gap-8 p-6">
|
<CardContent className="grid grid-cols-1 gap-8 p-6">
|
||||||
<p className="leading-relaxed text-[#555E67]">
|
<p className="text-foreground/80 leading-relaxed">
|
||||||
<strong className="block">Beautiful.</strong>
|
<strong className="block">Beautiful.</strong>
|
||||||
Because signing should be celebrated. That’s why we care about the smallest detail in
|
Because signing should be celebrated. That’s why we care about the smallest detail in
|
||||||
our product.
|
our product.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex items-center justify-center p-8">
|
<div className="flex items-center justify-center p-8">
|
||||||
<Image src={cardBeautifulFigure} alt="its fast" className="w-full max-w-xs" />
|
<Image
|
||||||
|
src={cardBeautifulFigure}
|
||||||
|
alt="its fast"
|
||||||
|
className="w-full max-w-xs dark:contrast-[70%] dark:hue-rotate-180 dark:invert"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="col-span-2 lg:col-span-1" spotlight>
|
<Card className="col-span-2 lg:col-span-1" spotlight>
|
||||||
<CardContent className="grid grid-cols-1 gap-8 p-6">
|
<CardContent className="grid grid-cols-1 gap-8 p-6">
|
||||||
<p className="leading-relaxed text-[#555E67]">
|
<p className="text-foreground/80 leading-relaxed">
|
||||||
<strong className="block">Smart.</strong>
|
<strong className="block">Smart.</strong>
|
||||||
Our custom templates come with smart rules that can help you save time and energy.
|
Our custom templates come with smart rules that can help you save time and energy.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex items-center justify-center p-8">
|
<div className="flex items-center justify-center p-8">
|
||||||
<Image src={cardSmartFigure} alt="its fast" className="w-full max-w-[16rem]" />
|
<Image
|
||||||
|
src={cardSmartFigure}
|
||||||
|
alt="its fast"
|
||||||
|
className="w-full max-w-[16rem] dark:contrast-[70%] dark:hue-rotate-180 dark:invert"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
import { HTMLAttributes } from 'react';
|
import { HTMLAttributes } from 'react';
|
||||||
|
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
|
|
||||||
import { Github, MessagesSquare, Twitter } from 'lucide-react';
|
import { Github, MessagesSquare, Moon, Sun, Twitter } from 'lucide-react';
|
||||||
|
import { useTheme } from 'next-themes';
|
||||||
|
|
||||||
import { cn } from '@documenso/ui/lib/utils';
|
import { cn } from '@documenso/ui/lib/utils';
|
||||||
|
|
||||||
@@ -26,17 +29,30 @@ const FOOTER_LINKS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const Footer = ({ className, ...props }: FooterProps) => {
|
export const Footer = ({ className, ...props }: FooterProps) => {
|
||||||
|
const { setTheme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('border-t py-12', className)} {...props}>
|
<div className={cn('border-t py-12', className)} {...props}>
|
||||||
<div className="mx-auto flex w-full max-w-screen-xl flex-wrap items-start justify-between gap-8 px-8">
|
<div className="mx-auto flex w-full max-w-screen-xl flex-wrap items-start justify-between gap-8 px-8">
|
||||||
<div>
|
<div>
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
<Image src="/logo.png" alt="Documenso Logo" width={170} height={0}></Image>
|
<Image
|
||||||
|
src="/logo.png"
|
||||||
|
alt="Documenso Logo"
|
||||||
|
className="dark:invert"
|
||||||
|
width={170}
|
||||||
|
height={0}
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="mt-4 flex flex-wrap items-center gap-x-4 gap-y-4 text-[#8D8D8D]">
|
<div className="mt-4 flex flex-wrap items-center gap-x-4 gap-y-4">
|
||||||
{SOCIAL_LINKS.map((link, index) => (
|
{SOCIAL_LINKS.map((link, index) => (
|
||||||
<Link key={index} href={link.href} target="_blank" className="hover:text-[#6D6D6D]">
|
<Link
|
||||||
|
key={index}
|
||||||
|
href={link.href}
|
||||||
|
target="_blank"
|
||||||
|
className="text-muted-foreground hover:text-muted-foreground/80"
|
||||||
|
>
|
||||||
{link.icon}
|
{link.icon}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
@@ -49,17 +65,29 @@ export const Footer = ({ className, ...props }: FooterProps) => {
|
|||||||
key={index}
|
key={index}
|
||||||
href={link.href}
|
href={link.href}
|
||||||
target={link.target}
|
target={link.target}
|
||||||
className="flex-shrink-0 text-sm text-[#8D8D8D] hover:text-[#6D6D6D]"
|
className="text-muted-foreground hover:text-muted-foreground/80 flex-shrink-0 text-sm"
|
||||||
>
|
>
|
||||||
{link.text}
|
{link.text}
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mx-auto mt-4 w-full max-w-screen-xl px-8 md:mt-12 lg:mt-24">
|
<div className="mx-auto mt-4 flex w-full max-w-screen-xl flex-wrap justify-between gap-4 px-8 md:mt-12 lg:mt-24">
|
||||||
<p className="text-sm text-[#8D8D8D]">
|
<p className="text-muted-foreground text-sm">
|
||||||
© {new Date().getFullYear()} Documenso, Inc. All rights reserved.
|
© {new Date().getFullYear()} Documenso, Inc. All rights reserved.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div className="flex flex-wrap items-center gap-x-4 gap-y-2.5">
|
||||||
|
<button type="button" className="text-muted-foreground" onClick={() => setTheme('light')}>
|
||||||
|
<Sun className="h-5 w-5" />
|
||||||
|
<span className="sr-only">Light</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="button" className="text-muted-foreground" onClick={() => setTheme('dark')}>
|
||||||
|
<Moon className="h-5 w-5" />
|
||||||
|
<span className="sr-only">Dark</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,26 +18,41 @@ export const Header = ({ className, ...props }: HeaderProps) => {
|
|||||||
return (
|
return (
|
||||||
<header className={cn('flex items-center justify-between', className)} {...props}>
|
<header className={cn('flex items-center justify-between', className)} {...props}>
|
||||||
<Link href="/" className="z-10" onClick={() => setIsHamburgerMenuOpen(false)}>
|
<Link href="/" className="z-10" onClick={() => setIsHamburgerMenuOpen(false)}>
|
||||||
<Image src="/logo.png" alt="Documenso Logo" width={170} height={25} />
|
<Image
|
||||||
|
src="/logo.png"
|
||||||
|
alt="Documenso Logo"
|
||||||
|
className="dark:invert"
|
||||||
|
width={170}
|
||||||
|
height={25}
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="hidden items-center gap-x-6 md:flex">
|
<div className="hidden items-center gap-x-6 md:flex">
|
||||||
<Link href="/pricing" className="text-sm font-semibold text-[#8D8D8D] hover:text-[#6D6D6D]">
|
<Link
|
||||||
|
href="/pricing"
|
||||||
|
className="text-muted-foreground hover:text-muted-foreground/80 text-sm font-semibold"
|
||||||
|
>
|
||||||
Pricing
|
Pricing
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link href="/blog" className="text-sm font-semibold text-[#8D8D8D] hover:text-[#6D6D6D]">
|
<Link
|
||||||
|
href="/blog"
|
||||||
|
className="text-muted-foreground hover:text-muted-foreground/80 text-sm font-semibold"
|
||||||
|
>
|
||||||
Blog
|
Blog
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link href="/open" className="text-sm font-semibold text-[#8D8D8D] hover:text-[#6D6D6D]">
|
<Link
|
||||||
|
href="/open"
|
||||||
|
className="text-muted-foreground hover:text-muted-foreground/80 text-sm font-semibold"
|
||||||
|
>
|
||||||
Open
|
Open
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
href="https://app.documenso.com/login"
|
href="https://app.documenso.com/login"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-sm font-semibold text-[#8D8D8D] hover:text-[#6D6D6D]"
|
className="text-muted-foreground hover:text-muted-foreground/80 text-sm font-semibold"
|
||||||
>
|
>
|
||||||
Sign in
|
Sign in
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export const Hero = ({ className, ...props }: HeroProps) => {
|
|||||||
<Image
|
<Image
|
||||||
src={backgroundPattern}
|
src={backgroundPattern}
|
||||||
alt="background pattern"
|
alt="background pattern"
|
||||||
className="-mr-[50vw] -mt-[15vh] h-full scale-125 object-cover md:scale-150 lg:scale-[175%]"
|
className="-mr-[50vw] -mt-[15vh] h-full scale-125 object-cover dark:invert dark:sepia md:scale-150 lg:scale-[175%]"
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
@@ -108,8 +108,8 @@ export const Hero = ({ className, ...props }: HeroProps) => {
|
|||||||
className="rounded-full bg-transparent backdrop-blur-sm"
|
className="rounded-full bg-transparent backdrop-blur-sm"
|
||||||
onClick={onSignUpClick}
|
onClick={onSignUpClick}
|
||||||
>
|
>
|
||||||
Get the Early Adopters Plan
|
Get the Community Plan
|
||||||
<span className="bg-primary -mr-2 ml-2.5 rounded-full px-2 py-1.5 text-xs">
|
<span className="bg-primary dark:text-background -mr-2 ml-2.5 rounded-full px-2 py-1.5 text-xs">
|
||||||
$30/mo. forever!
|
$30/mo. forever!
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -55,7 +55,13 @@ export const MobileNavigation = ({ isMenuOpen, onMenuOpenChange }: MobileNavigat
|
|||||||
<Sheet open={isMenuOpen} onOpenChange={onMenuOpenChange}>
|
<Sheet open={isMenuOpen} onOpenChange={onMenuOpenChange}>
|
||||||
<SheetContent className="w-full max-w-[400px]">
|
<SheetContent className="w-full max-w-[400px]">
|
||||||
<Link href="/" className="z-10" onClick={handleMenuItemClick}>
|
<Link href="/" className="z-10" onClick={handleMenuItemClick}>
|
||||||
<Image src="/logo.png" alt="Documenso Logo" width={170} height={25} />
|
<Image
|
||||||
|
src="/logo.png"
|
||||||
|
alt="Documenso Logo"
|
||||||
|
className="dark:invert"
|
||||||
|
width={170}
|
||||||
|
height={25}
|
||||||
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -85,7 +91,7 @@ export const MobileNavigation = ({ isMenuOpen, onMenuOpenChange }: MobileNavigat
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
className="text-2xl font-semibold text-[#8D8D8D] hover:text-[#6D6D6D]"
|
className="text-foreground hover:text-foreground/80 text-2xl font-semibold"
|
||||||
href={href}
|
href={href}
|
||||||
onClick={() => handleMenuItemClick()}
|
onClick={() => handleMenuItemClick()}
|
||||||
>
|
>
|
||||||
@@ -99,7 +105,7 @@ export const MobileNavigation = ({ isMenuOpen, onMenuOpenChange }: MobileNavigat
|
|||||||
<Link
|
<Link
|
||||||
href="https://twitter.com/documenso"
|
href="https://twitter.com/documenso"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-[#8D8D8D] hover:text-[#6D6D6D]"
|
className="text-foreground hover:text-foreground/80"
|
||||||
>
|
>
|
||||||
<Twitter className="h-6 w-6" />
|
<Twitter className="h-6 w-6" />
|
||||||
</Link>
|
</Link>
|
||||||
@@ -107,7 +113,7 @@ export const MobileNavigation = ({ isMenuOpen, onMenuOpenChange }: MobileNavigat
|
|||||||
<Link
|
<Link
|
||||||
href="https://github.com/documenso/documenso"
|
href="https://github.com/documenso/documenso"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-[#8D8D8D] hover:text-[#6D6D6D]"
|
className="text-foreground hover:text-foreground/80"
|
||||||
>
|
>
|
||||||
<Github className="h-6 w-6" />
|
<Github className="h-6 w-6" />
|
||||||
</Link>
|
</Link>
|
||||||
@@ -115,7 +121,7 @@ export const MobileNavigation = ({ isMenuOpen, onMenuOpenChange }: MobileNavigat
|
|||||||
<Link
|
<Link
|
||||||
href="https://documen.so/discord"
|
href="https://documen.so/discord"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="text-[#8D8D8D] hover:text-[#6D6D6D]"
|
className="text-foreground hover:text-foreground/80"
|
||||||
>
|
>
|
||||||
<MessagesSquare className="h-6 w-6" />
|
<MessagesSquare className="h-6 w-6" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const OpenBuildTemplateBento = ({ className, ...props }: OpenBuildTemplat
|
|||||||
<Image
|
<Image
|
||||||
src={backgroundPattern}
|
src={backgroundPattern}
|
||||||
alt="background pattern"
|
alt="background pattern"
|
||||||
className="h-full scale-125 object-cover md:scale-150 lg:scale-[175%]"
|
className="h-full scale-125 object-cover dark:invert dark:sepia md:scale-150 lg:scale-[175%]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="px-0 text-[22px] font-semibold md:px-12 md:text-4xl lg:px-24">
|
<h2 className="px-0 text-[22px] font-semibold md:px-12 md:text-4xl lg:px-24">
|
||||||
@@ -30,41 +30,53 @@ export const OpenBuildTemplateBento = ({ className, ...props }: OpenBuildTemplat
|
|||||||
<div className="mt-6 grid grid-cols-2 gap-8 md:mt-8">
|
<div className="mt-6 grid grid-cols-2 gap-8 md:mt-8">
|
||||||
<Card className="col-span-2" degrees={45} gradient>
|
<Card className="col-span-2" degrees={45} gradient>
|
||||||
<CardContent className="grid grid-cols-12 gap-8 overflow-hidden p-6 lg:aspect-[2.5/1]">
|
<CardContent className="grid grid-cols-12 gap-8 overflow-hidden p-6 lg:aspect-[2.5/1]">
|
||||||
<p className="col-span-12 leading-relaxed text-[#555E67] lg:col-span-6">
|
<p className="text-foreground/80 col-span-12 leading-relaxed lg:col-span-6">
|
||||||
<strong className="block">Open Source or Hosted.</strong>
|
<strong className="block">Open Source or Hosted.</strong>
|
||||||
It’s up to you. Either clone our repository or rely on our easy to use hosting
|
It’s up to you. Either clone our repository or rely on our easy to use hosting
|
||||||
solution.
|
solution.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="col-span-12 -my-6 -mr-6 flex items-end justify-end pt-12 lg:col-span-6">
|
<div className="col-span-12 -my-6 -mr-6 flex items-end justify-end pt-12 lg:col-span-6">
|
||||||
<Image src={cardOpenFigure} alt="its fast" className="max-w-[80%] lg:max-w-full" />
|
<Image
|
||||||
|
src={cardOpenFigure}
|
||||||
|
alt="its fast"
|
||||||
|
className="max-w-[80%] dark:contrast-[70%] dark:hue-rotate-180 dark:invert lg:max-w-full"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="col-span-2 lg:col-span-1" spotlight>
|
<Card className="col-span-2 lg:col-span-1" spotlight>
|
||||||
<CardContent className="grid grid-cols-1 gap-8 p-6">
|
<CardContent className="grid grid-cols-1 gap-8 p-6">
|
||||||
<p className="leading-relaxed text-[#555E67]">
|
<p className="text-foreground/80 leading-relaxed">
|
||||||
<strong className="block">Build on top.</strong>
|
<strong className="block">Build on top.</strong>
|
||||||
Make it your own through advanced customization and adjustability.
|
Make it your own through advanced customization and adjustability.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex items-center justify-center p-8">
|
<div className="flex items-center justify-center p-8">
|
||||||
<Image src={cardBuildFigure} alt="its fast" className="w-full max-w-xs" />
|
<Image
|
||||||
|
src={cardBuildFigure}
|
||||||
|
alt="its fast"
|
||||||
|
className="w-full max-w-xs dark:contrast-[70%] dark:hue-rotate-180 dark:invert"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="col-span-2 lg:col-span-1" spotlight>
|
<Card className="col-span-2 lg:col-span-1" spotlight>
|
||||||
<CardContent className="grid grid-cols-1 gap-8 p-6">
|
<CardContent className="grid grid-cols-1 gap-8 p-6">
|
||||||
<p className="leading-relaxed text-[#555E67]">
|
<p className="text-foreground/80 leading-relaxed">
|
||||||
<strong className="block">Template Store (Soon).</strong>
|
<strong className="block">Template Store (Soon).</strong>
|
||||||
Choose a template from the community app store. Or submit your own template for others
|
Choose a template from the community app store. Or submit your own template for others
|
||||||
to use.
|
to use.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex items-center justify-center p-8">
|
<div className="flex items-center justify-center p-8">
|
||||||
<Image src={cardTemplateFigure} alt="its fast" className="w-full max-w-sm" />
|
<Image
|
||||||
|
src={cardTemplateFigure}
|
||||||
|
alt="its fast"
|
||||||
|
className="w-full max-w-sm dark:contrast-[70%] dark:hue-rotate-180 dark:invert"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -41,10 +41,13 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
|
|||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
<motion.button
|
<motion.button
|
||||||
key="MONTHLY"
|
key="MONTHLY"
|
||||||
className={cn('relative flex items-center gap-x-2.5 px-1 py-2.5 text-[#727272]', {
|
className={cn(
|
||||||
'text-slate-900': period === 'MONTHLY',
|
'text-muted-foreground relative flex items-center gap-x-2.5 px-1 py-2.5',
|
||||||
'hover:text-slate-900/80': period !== 'MONTHLY',
|
{
|
||||||
})}
|
'text-foreground': period === 'MONTHLY',
|
||||||
|
'hover:text-foreground/80': period !== 'MONTHLY',
|
||||||
|
},
|
||||||
|
)}
|
||||||
onClick={() => setPeriod('MONTHLY')}
|
onClick={() => setPeriod('MONTHLY')}
|
||||||
>
|
>
|
||||||
Monthly
|
Monthly
|
||||||
@@ -58,14 +61,17 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
|
|||||||
|
|
||||||
<motion.button
|
<motion.button
|
||||||
key="YEARLY"
|
key="YEARLY"
|
||||||
className={cn('relative flex items-center gap-x-2.5 px-1 py-2.5 text-[#727272]', {
|
className={cn(
|
||||||
'text-slate-900': period === 'YEARLY',
|
'text-muted-foreground relative flex items-center gap-x-2.5 px-1 py-2.5',
|
||||||
'hover:text-slate-900/80': period !== 'YEARLY',
|
{
|
||||||
})}
|
'text-foreground': period === 'YEARLY',
|
||||||
|
'hover:text-foreground/80': period !== 'YEARLY',
|
||||||
|
},
|
||||||
|
)}
|
||||||
onClick={() => setPeriod('YEARLY')}
|
onClick={() => setPeriod('YEARLY')}
|
||||||
>
|
>
|
||||||
Yearly
|
Yearly
|
||||||
<div className="block rounded-full bg-slate-200 px-2 py-0.5 text-xs text-slate-700">
|
<div className="bg-muted text-foreground block rounded-full px-2 py-0.5 text-xs">
|
||||||
Save $60
|
Save $60
|
||||||
</div>
|
</div>
|
||||||
{period === 'YEARLY' && (
|
{period === 'YEARLY' && (
|
||||||
@@ -81,12 +87,12 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
|
|||||||
<div className="mt-12 grid grid-cols-1 gap-x-6 gap-y-12 md:grid-cols-2 lg:grid-cols-3">
|
<div className="mt-12 grid grid-cols-1 gap-x-6 gap-y-12 md:grid-cols-2 lg:grid-cols-3">
|
||||||
<div
|
<div
|
||||||
data-plan="self-hosted"
|
data-plan="self-hosted"
|
||||||
className="flex flex-col items-center justify-center rounded-lg border bg-white px-8 py-12 shadow-lg shadow-slate-900/5"
|
className="bg-background shadow-foreground/5 flex flex-col items-center justify-center rounded-lg border px-8 py-12 shadow-lg"
|
||||||
>
|
>
|
||||||
<p className="text-4xl font-medium text-slate-900">Self Hosted</p>
|
<p className="text-foreground text-4xl font-medium">Self Hosted</p>
|
||||||
<p className="text-primary mt-2.5 text-xl font-medium">Free</p>
|
<p className="text-primary mt-2.5 text-xl font-medium">Free</p>
|
||||||
|
|
||||||
<p className="mt-4 max-w-[30ch] text-center text-slate-900">
|
<p className="text-foreground mt-4 max-w-[30ch] text-center">
|
||||||
For small teams and individuals who need a simple solution
|
For small teams and individuals who need a simple solution
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -100,20 +106,20 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
|
|||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="mt-8 flex w-full flex-col divide-y">
|
<div className="mt-8 flex w-full flex-col divide-y">
|
||||||
<p className="py-4 font-medium text-slate-900">Host your own instance</p>
|
<p className="text-foreground py-4 font-medium">Host your own instance</p>
|
||||||
<p className="py-4 text-slate-900">Full Control</p>
|
<p className="text-foreground py-4">Full Control</p>
|
||||||
<p className="py-4 text-slate-900">Customizability</p>
|
<p className="text-foreground py-4">Customizability</p>
|
||||||
<p className="py-4 text-slate-900">Docker Ready</p>
|
<p className="text-foreground py-4">Docker Ready</p>
|
||||||
<p className="py-4 text-slate-900">Community Support</p>
|
<p className="text-foreground py-4">Community Support</p>
|
||||||
<p className="py-4 text-slate-900">Free, Forever</p>
|
<p className="text-foreground py-4">Free, Forever</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
data-plan="community"
|
data-plan="community"
|
||||||
className="border-primary flex flex-col items-center justify-center rounded-lg border-2 bg-white px-8 py-12 shadow-[0px_0px_0px_4px_#E3E3E380] shadow-slate-900/5"
|
className="border-primary bg-background shadow-foreground/5 flex flex-col items-center justify-center rounded-lg border-2 px-8 py-12 shadow-[0px_0px_0px_4px_#E3E3E380]"
|
||||||
>
|
>
|
||||||
<p className="text-4xl font-medium text-slate-900">Early Adopters</p>
|
<p className="text-foreground text-4xl font-medium">Community</p>
|
||||||
<div className="text-primary mt-2.5 text-xl font-medium">
|
<div className="text-primary mt-2.5 text-xl font-medium">
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
{period === 'MONTHLY' && <motion.div layoutId="pricing">$30</motion.div>}
|
{period === 'MONTHLY' && <motion.div layoutId="pricing">$30</motion.div>}
|
||||||
@@ -121,7 +127,7 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
|
|||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="mt-4 max-w-[30ch] text-center text-slate-900">
|
<p className="text-foreground mt-4 max-w-[30ch] text-center">
|
||||||
For fast-growing companies that aim to scale across multiple teams.
|
For fast-growing companies that aim to scale across multiple teams.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -130,33 +136,25 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
|
|||||||
</ClaimPlanDialog>
|
</ClaimPlanDialog>
|
||||||
|
|
||||||
<div className="mt-8 flex w-full flex-col divide-y">
|
<div className="mt-8 flex w-full flex-col divide-y">
|
||||||
<p className="py-4 font-medium text-slate-900">
|
<p className="text-foreground py-4 font-medium">Documenso Early Adopter Deal:</p>
|
||||||
<a href="https://documens.so/early" target="_blank">
|
<p className="text-foreground py-4">Join the movement</p>
|
||||||
Documenso Early Adopter Deal
|
<p className="text-foreground py-4">Simple signing solution</p>
|
||||||
</a>
|
<p className="text-foreground py-4">Email and Slack assistance</p>
|
||||||
|
<p className="text-foreground py-4">
|
||||||
|
<strong>Includes all upcoming features</strong>
|
||||||
</p>
|
</p>
|
||||||
<p className="py-4 text-slate-900">Join the movement</p>
|
<p className="text-foreground py-4">Fixed, straightforward pricing</p>
|
||||||
<p className="py-4 text-slate-900">Simple signing solution</p>
|
|
||||||
<p className="py-4 text-slate-900">Email and Slack assistance</p>
|
|
||||||
<p className="py-4 text-slate-900">
|
|
||||||
<strong>
|
|
||||||
<a href="https://documens.so/early" target="_blank">
|
|
||||||
Includes all upcoming features
|
|
||||||
</a>
|
|
||||||
</strong>
|
|
||||||
</p>
|
|
||||||
<p className="py-4 text-slate-900">Fixed, straightforward pricing</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
data-plan="enterprise"
|
data-plan="enterprise"
|
||||||
className="flex flex-col items-center justify-center rounded-lg border bg-white px-8 py-12 shadow-lg shadow-slate-900/5"
|
className="bg-background shadow-foreground/5 flex flex-col items-center justify-center rounded-lg border px-8 py-12 shadow-lg"
|
||||||
>
|
>
|
||||||
<p className="text-4xl font-medium text-slate-900">Enterprise</p>
|
<p className="text-foreground text-4xl font-medium">Enterprise</p>
|
||||||
<p className="text-primary mt-2.5 text-xl font-medium">Pricing on request</p>
|
<p className="text-primary mt-2.5 text-xl font-medium">Pricing on request</p>
|
||||||
|
|
||||||
<p className="mt-4 max-w-[30ch] text-center text-slate-900">
|
<p className="text-foreground mt-4 max-w-[30ch] text-center">
|
||||||
For large organizations that need extra flexibility and control.
|
For large organizations that need extra flexibility and control.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -170,12 +168,12 @@ export const PricingTable = ({ className, ...props }: PricingTableProps) => {
|
|||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="mt-8 flex w-full flex-col divide-y">
|
<div className="mt-8 flex w-full flex-col divide-y">
|
||||||
<p className="py-4 font-medium text-slate-900">Everything in Early Adopters, plus:</p>
|
<p className="text-foreground py-4 font-medium">Everything in Community, plus:</p>
|
||||||
<p className="py-4 text-slate-900">Custom Subdomain</p>
|
<p className="text-foreground py-4">Custom Subdomain</p>
|
||||||
<p className="py-4 text-slate-900">Compliance Check</p>
|
<p className="text-foreground py-4">Compliance Check</p>
|
||||||
<p className="py-4 text-slate-900">Guaranteed Uptime</p>
|
<p className="text-foreground py-4">Guaranteed Uptime</p>
|
||||||
<p className="py-4 text-slate-900">Reporting & Analysis</p>
|
<p className="text-foreground py-4">Reporting & Analysis</p>
|
||||||
<p className="py-4 text-slate-900">24/7 Support</p>
|
<p className="text-foreground py-4">24/7 Support</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const ShareConnectPaidWidgetBento = ({
|
|||||||
<Image
|
<Image
|
||||||
src={backgroundPattern}
|
src={backgroundPattern}
|
||||||
alt="background pattern"
|
alt="background pattern"
|
||||||
className="h-full scale-125 object-cover md:scale-150 lg:scale-[175%]"
|
className="h-full scale-125 object-cover dark:invert dark:sepia md:scale-150 lg:scale-[175%]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="px-0 text-[22px] font-semibold md:px-12 md:text-4xl lg:px-24">
|
<h2 className="px-0 text-[22px] font-semibold md:px-12 md:text-4xl lg:px-24">
|
||||||
@@ -34,54 +34,70 @@ export const ShareConnectPaidWidgetBento = ({
|
|||||||
<div className="mt-6 grid grid-cols-2 gap-8 md:mt-8">
|
<div className="mt-6 grid grid-cols-2 gap-8 md:mt-8">
|
||||||
<Card className="col-span-2 lg:col-span-1" degrees={120} gradient>
|
<Card className="col-span-2 lg:col-span-1" degrees={120} gradient>
|
||||||
<CardContent className="grid grid-cols-1 gap-8 p-6">
|
<CardContent className="grid grid-cols-1 gap-8 p-6">
|
||||||
<p className="leading-relaxed text-[#555E67]">
|
<p className="text-foreground/80 leading-relaxed">
|
||||||
<strong className="block">Easy Sharing (Soon).</strong>
|
<strong className="block">Easy Sharing (Soon).</strong>
|
||||||
Receive your personal link to share with everyone you care about.
|
Receive your personal link to share with everyone you care about.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex items-center justify-center p-8">
|
<div className="flex items-center justify-center p-8">
|
||||||
<Image src={cardSharingFigure} alt="its fast" className="w-full max-w-xs" />
|
<Image
|
||||||
|
src={cardSharingFigure}
|
||||||
|
alt="its fast"
|
||||||
|
className="w-full max-w-xs dark:contrast-[70%] dark:hue-rotate-180 dark:invert"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="col-span-2 lg:col-span-1" spotlight>
|
<Card className="col-span-2 lg:col-span-1" spotlight>
|
||||||
<CardContent className="grid grid-cols-1 gap-8 p-6">
|
<CardContent className="grid grid-cols-1 gap-8 p-6">
|
||||||
<p className="leading-relaxed text-[#555E67]">
|
<p className="text-foreground/80 leading-relaxed">
|
||||||
<strong className="block">Connections (Soon).</strong>
|
<strong className="block">Connections (Soon).</strong>
|
||||||
Create connections and automations with Zapier and more to integrate with your
|
Create connections and automations with Zapier and more to integrate with your
|
||||||
favorite tools.
|
favorite tools.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex items-center justify-center p-8">
|
<div className="flex items-center justify-center p-8">
|
||||||
<Image src={cardConnectionsFigure} alt="its fast" className="w-full max-w-sm" />
|
<Image
|
||||||
|
src={cardConnectionsFigure}
|
||||||
|
alt="its fast"
|
||||||
|
className="w-full max-w-sm dark:contrast-[70%] dark:hue-rotate-180 dark:invert"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="col-span-2 lg:col-span-1" spotlight>
|
<Card className="col-span-2 lg:col-span-1" spotlight>
|
||||||
<CardContent className="grid grid-cols-1 gap-8 p-6">
|
<CardContent className="grid grid-cols-1 gap-8 p-6">
|
||||||
<p className="leading-relaxed text-[#555E67]">
|
<p className="text-foreground/80 leading-relaxed">
|
||||||
<strong className="block">Get paid (Soon).</strong>
|
<strong className="block">Get paid (Soon).</strong>
|
||||||
Integrated payments with stripe so you don’t have to worry about getting paid.
|
Integrated payments with stripe so you don’t have to worry about getting paid.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex items-center justify-center p-8">
|
<div className="flex items-center justify-center p-8">
|
||||||
<Image src={cardPaidFigure} alt="its fast" className="w-full max-w-[14rem]" />
|
<Image
|
||||||
|
src={cardPaidFigure}
|
||||||
|
alt="its fast"
|
||||||
|
className="w-full max-w-[14rem] dark:contrast-[70%] dark:hue-rotate-180 dark:invert"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card className="col-span-2 lg:col-span-1" spotlight>
|
<Card className="col-span-2 lg:col-span-1" spotlight>
|
||||||
<CardContent className="grid grid-cols-1 gap-8 p-6">
|
<CardContent className="grid grid-cols-1 gap-8 p-6">
|
||||||
<p className="leading-relaxed text-[#555E67]">
|
<p className="text-foreground/80 leading-relaxed">
|
||||||
<strong className="block">React Widget (Soon).</strong>
|
<strong className="block">React Widget (Soon).</strong>
|
||||||
Easily embed Documenso into your product. Simply copy and paste our react widget into
|
Easily embed Documenso into your product. Simply copy and paste our react widget into
|
||||||
your application.
|
your application.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="flex items-center justify-center p-8">
|
<div className="flex items-center justify-center p-8">
|
||||||
<Image src={cardWidgetFigure} alt="its fast" className="w-full max-w-xs" />
|
<Image
|
||||||
|
src={cardWidgetFigure}
|
||||||
|
alt="its fast"
|
||||||
|
className="w-full max-w-xs dark:contrast-[70%] dark:hue-rotate-180 dark:invert"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -181,16 +181,16 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div className="grid grid-cols-12 gap-y-8 overflow-hidden p-2 lg:gap-x-8">
|
<div className="grid grid-cols-12 gap-y-8 overflow-hidden p-2 lg:gap-x-8">
|
||||||
<div className="col-span-12 flex flex-col gap-y-4 p-4 text-xs leading-relaxed text-[#727272] lg:col-span-7">
|
<div className="text-muted-foreground col-span-12 flex flex-col gap-y-4 p-4 text-xs leading-relaxed lg:col-span-7">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
className="col-span-12 flex flex-col rounded-2xl bg-[#F7F7F7] p-6 lg:col-span-5"
|
className="bg-foreground/5 col-span-12 flex flex-col rounded-2xl p-6 lg:col-span-5"
|
||||||
onSubmit={handleSubmit(onFormSubmit)}
|
onSubmit={handleSubmit(onFormSubmit)}
|
||||||
>
|
>
|
||||||
<h3 className="text-2xl font-semibold">Sign up for the early adopters plan</h3>
|
<h3 className="text-2xl font-semibold">Sign up for the community plan</h3>
|
||||||
<p className="mt-2 text-xs text-[#AFAFAF]">
|
<p className="text-muted-foreground mt-2 text-xs">
|
||||||
with Timur Ercan & Lucas Smith from Documenso
|
with Timur Ercan & Lucas Smith from Documenso
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -198,7 +198,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
|
|||||||
|
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
<motion.div key="email">
|
<motion.div key="email">
|
||||||
<label htmlFor="email" className="text-lg font-semibold text-slate-900 lg:text-xl">
|
<label htmlFor="email" className="text-foreground text-lg font-semibold lg:text-xl">
|
||||||
What’s your email?
|
What’s your email?
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@@ -211,7 +211,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
|
|||||||
id="email"
|
id="email"
|
||||||
type="email"
|
type="email"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
className="w-full bg-white pr-16"
|
className="bg-background w-full pr-16"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
onKeyDown={(e) =>
|
onKeyDown={(e) =>
|
||||||
field.value !== '' &&
|
field.value !== '' &&
|
||||||
@@ -255,7 +255,10 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
|
|||||||
transform: 'translateX(25%)',
|
transform: 'translateX(25%)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<label htmlFor="name" className="text-lg font-semibold text-slate-900 lg:text-xl">
|
<label
|
||||||
|
htmlFor="name"
|
||||||
|
className="text-foreground text-lg font-semibold lg:text-xl"
|
||||||
|
>
|
||||||
and your name?
|
and your name?
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@@ -268,7 +271,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
|
|||||||
id="name"
|
id="name"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
className="w-full bg-white pr-16"
|
className="bg-background w-full pr-16"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
onKeyDown={(e) =>
|
onKeyDown={(e) =>
|
||||||
field.value !== '' &&
|
field.value !== '' &&
|
||||||
@@ -300,11 +303,11 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
|
|||||||
<div className="mt-12 flex-1" />
|
<div className="mt-12 flex-1" />
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-xs text-[#AFAFAF]">{stepsRemaining} step(s) until signed</p>
|
<p className="text-muted-foreground text-xs">{stepsRemaining} step(s) until signed</p>
|
||||||
<p className="block text-xs text-[#AFAFAF] md:hidden">Minimise contract</p>
|
<p className="text-muted-foreground block text-xs md:hidden">Minimise contract</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative mt-2.5 h-[2px] w-full bg-[#E9E9E9]">
|
<div className="bg-background relative mt-2.5 h-[2px] w-full">
|
||||||
<div
|
<div
|
||||||
className={cn('bg-primary/60 absolute inset-y-0 left-0 duration-200', {
|
className={cn('bg-primary/60 absolute inset-y-0 left-0 duration-200', {
|
||||||
'w-1/3': stepsRemaining === 3,
|
'w-1/3': stepsRemaining === 3,
|
||||||
@@ -322,13 +325,17 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
|
|||||||
>
|
>
|
||||||
<div className="flex h-28 items-center justify-center pb-6">
|
<div className="flex h-28 items-center justify-center pb-6">
|
||||||
{!signatureText && signatureDataUrl && (
|
{!signatureText && signatureDataUrl && (
|
||||||
<img src={signatureDataUrl} alt="user signature" className="h-full" />
|
<img
|
||||||
|
src={signatureDataUrl}
|
||||||
|
alt="user signature"
|
||||||
|
className="h-full dark:invert"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{signatureText && (
|
{signatureText && (
|
||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-4xl font-semibold text-slate-900 [font-family:var(--font-caveat)]',
|
'text-foreground text-4xl font-semibold [font-family:var(--font-caveat)]',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{signatureText}
|
{signatureText}
|
||||||
@@ -342,7 +349,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
|
|||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
id="signatureText"
|
id="signatureText"
|
||||||
className="border-none p-0 text-sm text-slate-700 placeholder:text-[#D6D6D6] focus-visible:ring-0"
|
className="text-foreground placeholder:text-muted-foreground border-none p-0 text-sm focus-visible:ring-0"
|
||||||
placeholder="Draw or type name here"
|
placeholder="Draw or type name here"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
{...register('signatureText', {
|
{...register('signatureText', {
|
||||||
@@ -356,7 +363,7 @@ export const Widget = ({ className, children, ...props }: WidgetProps) => {
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="h-8 disabled:bg-[#ECEEED] disabled:text-[#C6C6C6] disabled:hover:bg-[#ECEEED]"
|
className="disabled:bg-muted disabled:text-muted-foreground disabled:hover:bg-muted h-8"
|
||||||
disabled={!isValid || isSubmitting}
|
disabled={!isValid || isSubmitting}
|
||||||
>
|
>
|
||||||
{isSubmitting && <Loader className="mr-2 h-4 w-4 animate-spin" />}
|
{isSubmitting && <Loader className="mr-2 h-4 w-4 animate-spin" />}
|
||||||
|
|||||||
10
apps/marketing/src/providers/next-theme.tsx
Normal file
10
apps/marketing/src/providers/next-theme.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { ThemeProvider as NextThemesProvider } from 'next-themes';
|
||||||
|
import { ThemeProviderProps } from 'next-themes/dist/types';
|
||||||
|
|
||||||
|
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||||
|
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
||||||
|
}
|
||||||
@@ -169,6 +169,7 @@ export const EditDocumentForm = ({
|
|||||||
|
|
||||||
{step === 'signers' && (
|
{step === 'signers' && (
|
||||||
<AddSignersFormPartial
|
<AddSignersFormPartial
|
||||||
|
key={recipients.length}
|
||||||
documentFlow={documentFlow.signers}
|
documentFlow={documentFlow.signers}
|
||||||
recipients={recipients}
|
recipients={recipients}
|
||||||
fields={fields}
|
fields={fields}
|
||||||
@@ -179,6 +180,7 @@ export const EditDocumentForm = ({
|
|||||||
|
|
||||||
{step === 'fields' && (
|
{step === 'fields' && (
|
||||||
<AddFieldsFormPartial
|
<AddFieldsFormPartial
|
||||||
|
key={fields.length}
|
||||||
documentFlow={documentFlow.fields}
|
documentFlow={documentFlow.fields}
|
||||||
recipients={recipients}
|
recipients={recipients}
|
||||||
fields={fields}
|
fields={fields}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) =
|
|||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
<MoreHorizontal className="h-5 w-5 text-gray-500" />
|
<MoreHorizontal className="text-muted-foreground h-5 w-5" />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
|
||||||
<DropdownMenuContent className="w-52" align="start" forceMount>
|
<DropdownMenuContent className="w-52" align="start" forceMount>
|
||||||
|
|||||||
@@ -92,8 +92,8 @@ export const DocumentsDataTable = ({ results }: DocumentsDataTableProps) => {
|
|||||||
</DataTable>
|
</DataTable>
|
||||||
|
|
||||||
{isPending && (
|
{isPending && (
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-white/50">
|
<div className="bg-background/50 absolute inset-0 flex items-center justify-center">
|
||||||
<Loader className="h-8 w-8 animate-spin text-gray-500" />
|
<Loader className="text-muted-foreground h-8 w-8 animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -63,29 +63,38 @@ export default async function DocumentsPage({ searchParams = {} }: DocumentsPage
|
|||||||
<div className="mx-auto w-full max-w-screen-xl px-4 md:px-8">
|
<div className="mx-auto w-full max-w-screen-xl px-4 md:px-8">
|
||||||
<UploadDocument />
|
<UploadDocument />
|
||||||
|
|
||||||
<h1 className="mt-12 text-4xl font-semibold">Documents</h1>
|
<div className="mt-12 flex flex-wrap items-center justify-between gap-x-4 gap-y-8">
|
||||||
|
<h1 className="text-4xl font-semibold">Documents</h1>
|
||||||
|
|
||||||
<div className="mt-8 flex flex-wrap gap-x-4 gap-y-6">
|
<div className="flex flex-wrap gap-x-4 gap-y-6 overflow-hidden">
|
||||||
<Tabs defaultValue={status} className="overflow-x-auto">
|
<Tabs defaultValue={status} className="overflow-x-auto">
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger className="min-w-[60px]" value={InternalDocumentStatus.PENDING} asChild>
|
{[
|
||||||
<Link href={getTabHref(InternalDocumentStatus.PENDING)}>
|
ExtendedDocumentStatus.INBOX,
|
||||||
<DocumentStatus status={InternalDocumentStatus.PENDING} />
|
ExtendedDocumentStatus.PENDING,
|
||||||
|
ExtendedDocumentStatus.COMPLETED,
|
||||||
|
ExtendedDocumentStatus.DRAFT,
|
||||||
|
ExtendedDocumentStatus.ALL,
|
||||||
|
].map((value) => (
|
||||||
|
<TabsTrigger key={value} className="min-w-[60px]" value={value} asChild>
|
||||||
|
<Link href={getTabHref(value)} scroll={false}>
|
||||||
|
<DocumentStatus status={value} />
|
||||||
|
|
||||||
{value !== ExtendedDocumentStatus.ALL && (
|
{value !== ExtendedDocumentStatus.ALL && (
|
||||||
<span className="ml-1 hidden opacity-50 md:inline-block">
|
<span className="ml-1 hidden opacity-50 md:inline-block">
|
||||||
{Math.min(stats[value], 99)}
|
{Math.min(stats[value], 99)}
|
||||||
{stats[value] > 99 && '+'}
|
{stats[value] > 99 && '+'}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Link>
|
</Link>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
))}
|
))}
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
<div className="flex flex-1 flex-wrap items-center justify-between gap-x-2 gap-y-4">
|
<div className="flex w-48 flex-wrap items-center justify-between gap-x-2 gap-y-4">
|
||||||
<PeriodSelector />
|
<PeriodSelector />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export const UploadDocument = ({ className }: UploadDocumentProps) => {
|
|||||||
<DocumentDropzone className="min-h-[40vh]" onDrop={onFileDrop} />
|
<DocumentDropzone className="min-h-[40vh]" onDrop={onFileDrop} />
|
||||||
|
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<div className="absolute inset-0 flex items-center justify-center bg-white/50">
|
<div className="bg-background/50 absolute inset-0 flex items-center justify-center">
|
||||||
<Loader className="text-muted-foreground h-12 w-12 animate-spin" />
|
<Loader className="text-muted-foreground h-12 w-12 animate-spin" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => {
|
|||||||
<img
|
<img
|
||||||
src={signature.signatureImageAsBase64}
|
src={signature.signatureImageAsBase64}
|
||||||
alt={`Signature for ${recipient.name}`}
|
alt={`Signature for ${recipient.name}`}
|
||||||
className="h-full w-full object-contain"
|
className="h-full w-full object-contain dark:invert"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
|||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</PlausibleProvider>
|
</PlausibleProvider>
|
||||||
|
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</FeatureFlagProvider>
|
</FeatureFlagProvider>
|
||||||
</LocaleProvider>
|
</LocaleProvider>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const StackAvatarsWithTooltip = ({
|
|||||||
type={getRecipientType(recipient)}
|
type={getRecipientType(recipient)}
|
||||||
fallbackText={recipientAbbreviation(recipient)}
|
fallbackText={recipientAbbreviation(recipient)}
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-gray-500">{recipient.email}</span>
|
<span className="text-muted-foreground text-sm">{recipient.email}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -75,7 +75,7 @@ export const StackAvatarsWithTooltip = ({
|
|||||||
type={getRecipientType(recipient)}
|
type={getRecipientType(recipient)}
|
||||||
fallbackText={recipientAbbreviation(recipient)}
|
fallbackText={recipientAbbreviation(recipient)}
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-gray-500">{recipient.email}</span>
|
<span className="text-muted-foreground text-sm">{recipient.email}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -92,7 +92,7 @@ export const StackAvatarsWithTooltip = ({
|
|||||||
type={getRecipientType(recipient)}
|
type={getRecipientType(recipient)}
|
||||||
fallbackText={recipientAbbreviation(recipient)}
|
fallbackText={recipientAbbreviation(recipient)}
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-gray-500">{recipient.email}</span>
|
<span className="text-muted-foreground text-sm">{recipient.email}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -109,7 +109,7 @@ export const StackAvatarsWithTooltip = ({
|
|||||||
type={getRecipientType(recipient)}
|
type={getRecipientType(recipient)}
|
||||||
fallbackText={recipientAbbreviation(recipient)}
|
fallbackText={recipientAbbreviation(recipient)}
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-gray-500">{recipient.email}</span>
|
<span className="text-muted-foreground text-sm">{recipient.email}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,17 +17,17 @@ const FRIENDLY_STATUS_MAP: Record<ExtendedDocumentStatus, FriendlyStatus> = {
|
|||||||
PENDING: {
|
PENDING: {
|
||||||
label: 'Pending',
|
label: 'Pending',
|
||||||
icon: Clock,
|
icon: Clock,
|
||||||
color: 'text-blue-600',
|
color: 'text-blue-600 dark:text-blue-300',
|
||||||
},
|
},
|
||||||
COMPLETED: {
|
COMPLETED: {
|
||||||
label: 'Completed',
|
label: 'Completed',
|
||||||
icon: CheckCircle2,
|
icon: CheckCircle2,
|
||||||
color: 'text-green-500',
|
color: 'text-green-500 dark:text-green-300',
|
||||||
},
|
},
|
||||||
DRAFT: {
|
DRAFT: {
|
||||||
label: 'Draft',
|
label: 'Draft',
|
||||||
icon: File,
|
icon: File,
|
||||||
color: 'text-yellow-500',
|
color: 'text-yellow-500 dark:text-yellow-200',
|
||||||
},
|
},
|
||||||
INBOX: {
|
INBOX: {
|
||||||
label: 'Inbox',
|
label: 'Inbox',
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ export const getFieldsForDocument = async ({ documentId, userId }: GetFieldsForD
|
|||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
orderBy: {
|
||||||
|
id: 'asc',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return fields;
|
return fields;
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { prisma } from '@documenso/prisma';
|
import { prisma } from '@documenso/prisma';
|
||||||
import { SendStatus, SigningStatus } from '@documenso/prisma/client';
|
import { FieldType, SendStatus, SigningStatus } from '@documenso/prisma/client';
|
||||||
|
|
||||||
export interface SetFieldsForDocumentOptions {
|
export interface SetFieldsForDocumentOptions {
|
||||||
userId: number;
|
userId: number;
|
||||||
documentId: number;
|
documentId: number;
|
||||||
fields: {
|
fields: {
|
||||||
id?: number | null;
|
id?: number | null;
|
||||||
|
type: FieldType;
|
||||||
signerEmail: string;
|
signerEmail: string;
|
||||||
pageNumber: number;
|
pageNumber: number;
|
||||||
pageX: number;
|
pageX: number;
|
||||||
@@ -54,62 +55,56 @@ export const setFieldsForDocument = async ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...field,
|
...field,
|
||||||
...existing,
|
_persisted: existing,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((field) => {
|
.filter((field) => {
|
||||||
return (
|
return (
|
||||||
field.Recipient?.sendStatus !== SendStatus.SENT &&
|
field._persisted?.Recipient?.sendStatus !== SendStatus.SENT &&
|
||||||
field.Recipient?.signingStatus !== SigningStatus.SIGNED
|
field._persisted?.Recipient?.signingStatus !== SigningStatus.SIGNED
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const persistedFields = await prisma.$transaction(
|
const persistedFields = await prisma.$transaction(
|
||||||
|
// Disabling as wrapping promises here causes type issues
|
||||||
|
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||||
linkedFields.map((field) =>
|
linkedFields.map((field) =>
|
||||||
field.id
|
prisma.field.upsert({
|
||||||
? prisma.field.update({
|
where: {
|
||||||
where: {
|
id: field._persisted?.id ?? -1,
|
||||||
id: field.id,
|
documentId,
|
||||||
recipientId: field.recipientId,
|
},
|
||||||
documentId,
|
update: {
|
||||||
|
page: field.pageNumber,
|
||||||
|
positionX: field.pageX,
|
||||||
|
positionY: field.pageY,
|
||||||
|
width: field.pageWidth,
|
||||||
|
height: field.pageHeight,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
type: field.type,
|
||||||
|
page: field.pageNumber,
|
||||||
|
positionX: field.pageX,
|
||||||
|
positionY: field.pageY,
|
||||||
|
width: field.pageWidth,
|
||||||
|
height: field.pageHeight,
|
||||||
|
customText: '',
|
||||||
|
inserted: false,
|
||||||
|
Document: {
|
||||||
|
connect: {
|
||||||
|
id: documentId,
|
||||||
},
|
},
|
||||||
data: {
|
},
|
||||||
type: field.type,
|
Recipient: {
|
||||||
page: field.pageNumber,
|
connect: {
|
||||||
positionX: field.pageX,
|
documentId_email: {
|
||||||
positionY: field.pageY,
|
documentId,
|
||||||
width: field.pageWidth,
|
email: field.signerEmail.toLowerCase(),
|
||||||
height: field.pageHeight,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
: prisma.field.create({
|
|
||||||
data: {
|
|
||||||
// TODO: Rewrite this entire transaction because this is a mess
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
type: field.type!,
|
|
||||||
page: field.pageNumber,
|
|
||||||
positionX: field.pageX,
|
|
||||||
positionY: field.pageY,
|
|
||||||
width: field.pageWidth,
|
|
||||||
height: field.pageHeight,
|
|
||||||
customText: '',
|
|
||||||
inserted: false,
|
|
||||||
|
|
||||||
Document: {
|
|
||||||
connect: {
|
|
||||||
id: document.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Recipient: {
|
|
||||||
connect: {
|
|
||||||
documentId_email: {
|
|
||||||
documentId: document.id,
|
|
||||||
email: field.signerEmail,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ export const getRecipientsForDocument = async ({
|
|||||||
userId,
|
userId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
orderBy: {
|
||||||
|
id: 'asc',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return recipients;
|
return recipients;
|
||||||
|
|||||||
@@ -67,27 +67,26 @@ export const setRecipientsForDocument = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const persistedRecipients = await prisma.$transaction(
|
const persistedRecipients = await prisma.$transaction(
|
||||||
|
// Disabling as wrapping promises here causes type issues
|
||||||
|
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||||
linkedRecipients.map((recipient) =>
|
linkedRecipients.map((recipient) =>
|
||||||
recipient.id
|
prisma.recipient.upsert({
|
||||||
? prisma.recipient.update({
|
where: {
|
||||||
where: {
|
id: recipient.id ?? -1,
|
||||||
id: recipient.id,
|
documentId,
|
||||||
documentId,
|
},
|
||||||
},
|
update: {
|
||||||
data: {
|
name: recipient.name,
|
||||||
name: recipient.name,
|
email: recipient.email,
|
||||||
email: recipient.email,
|
documentId,
|
||||||
documentId,
|
},
|
||||||
},
|
create: {
|
||||||
})
|
name: recipient.name,
|
||||||
: prisma.recipient.create({
|
email: recipient.email,
|
||||||
data: {
|
token: nanoid(),
|
||||||
name: recipient.name,
|
documentId,
|
||||||
email: recipient.email,
|
},
|
||||||
token: nanoid(),
|
}),
|
||||||
documentId,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export type GetSubscriptionByUserIdOptions = {
|
|||||||
userId: number;
|
userId: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSubscriptionByUserId = ({ userId }: GetSubscriptionByUserIdOptions) => {
|
export const getSubscriptionByUserId = async ({ userId }: GetSubscriptionByUserIdOptions) => {
|
||||||
return prisma.subscription.findFirst({
|
return prisma.subscription.findFirst({
|
||||||
where: {
|
where: {
|
||||||
userId,
|
userId,
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ export const AddFieldsFormPartial = ({
|
|||||||
{selectedField && (
|
{selectedField && (
|
||||||
<Card
|
<Card
|
||||||
className={cn(
|
className={cn(
|
||||||
'pointer-events-none fixed z-50 cursor-pointer bg-white transition-opacity',
|
'bg-background pointer-events-none fixed z-50 cursor-pointer transition-opacity',
|
||||||
{
|
{
|
||||||
'border-primary': isFieldWithinBounds,
|
'border-primary': isFieldWithinBounds,
|
||||||
'opacity-50': !isFieldWithinBounds,
|
'opacity-50': !isFieldWithinBounds,
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ export const FieldItem = ({
|
|||||||
>
|
>
|
||||||
{!disabled && (
|
{!disabled && (
|
||||||
<button
|
<button
|
||||||
className="text-muted-foreground/50 hover:text-muted-foreground/80 absolute -right-2 -top-2 z-20 flex h-8 w-8 items-center justify-center rounded-full border bg-white shadow-[0_0_0_2px_theme(colors.gray.100/70%)]"
|
className="text-muted-foreground/50 hover:text-muted-foreground/80 bg-background absolute -right-2 -top-2 z-20 flex h-8 w-8 items-center justify-center rounded-full border"
|
||||||
onClick={() => onRemove?.()}
|
onClick={() => onRemove?.()}
|
||||||
>
|
>
|
||||||
<Trash className="h-4 w-4" />
|
<Trash className="h-4 w-4" />
|
||||||
@@ -126,7 +126,7 @@ export const FieldItem = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
className={cn('h-full w-full bg-white', {
|
className={cn('bg-background h-full w-full', {
|
||||||
'border-primary': !disabled,
|
'border-primary': !disabled,
|
||||||
'border-primary/80': active,
|
'border-primary/80': active,
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -41,34 +41,34 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: 224 71% 4%;
|
--background: 0 0% 14.9%;
|
||||||
--foreground: 213 31% 91%;
|
--foreground: 0 0% 97%;
|
||||||
|
|
||||||
--muted: 223 47% 11%;
|
--muted: 0 0% 23.4%;
|
||||||
--muted-foreground: 215.4 16.3% 56.9%;
|
--muted-foreground: 0 0% 85%;
|
||||||
|
|
||||||
--popover: 224 71% 4%;
|
--popover: 0 0% 14.9%;
|
||||||
--popover-foreground: 215 20.2% 65.1%;
|
--popover-foreground: 0 0% 90%;
|
||||||
|
|
||||||
--card: 224 71% 4%;
|
--card: 0 0% 14.9%;
|
||||||
--card-border: 216 34% 17%;
|
--card-border: 0 0% 27.9%;
|
||||||
--card-border-tint: 112 205 159;
|
--card-border-tint: 112 205 159;
|
||||||
--card-foreground: 213 31% 91%;
|
--card-foreground: 0 0% 95%;
|
||||||
|
|
||||||
--border: 216 34% 17%;
|
--border: 0 0% 27.9%;
|
||||||
--input: 216 34% 17%;
|
--input: 0 0% 27.9%;
|
||||||
|
|
||||||
--primary: 210 40% 98%;
|
--primary: 95.08 71.08% 67.45%;
|
||||||
--primary-foreground: 222.2 47.4% 1.2%;
|
--primary-foreground: 95.08 71.08% 10%;
|
||||||
|
|
||||||
--secondary: 222.2 47.4% 11.2%;
|
--secondary: 0 0% 23.4%;
|
||||||
--secondary-foreground: 210 40% 98%;
|
--secondary-foreground: 95.08 71.08% 67.45%;
|
||||||
|
|
||||||
--accent: 216 34% 17%;
|
--accent: 0 0% 27.9%;
|
||||||
--accent-foreground: 210 40% 98%;
|
--accent-foreground: 95.08 71.08% 67.45%;
|
||||||
|
|
||||||
--destructive: 0 63% 31%;
|
--destructive: 0 87% 62%;
|
||||||
--destructive-foreground: 210 40% 98%;
|
--destructive-foreground: 0 87% 19%;
|
||||||
|
|
||||||
--ring: 95.08 71.08% 67.45%;
|
--ring: 95.08 71.08% 67.45%;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user