+
-
-
-
+ {[
+ ExtendedDocumentStatus.INBOX,
+ ExtendedDocumentStatus.PENDING,
+ ExtendedDocumentStatus.COMPLETED,
+ ExtendedDocumentStatus.DRAFT,
+ ExtendedDocumentStatus.ALL,
+ ].map((value) => (
+
+
+
-
- {Math.min(stats.PENDING, 99)}
-
-
-
-
-
-
-
-
-
- {Math.min(stats.COMPLETED, 99)}
-
-
-
-
-
-
-
-
-
- {Math.min(stats.DRAFT, 99)}
-
-
-
-
-
-
- All
-
-
+ {value !== ExtendedDocumentStatus.ALL && (
+
+ {Math.min(stats[value], 99)}
+
+ )}
+
+
+ ))}
diff --git a/apps/web/src/app/(dashboard)/settings/billing/page.tsx b/apps/web/src/app/(dashboard)/settings/billing/page.tsx
index 256f682fb..e9966b9ac 100644
--- a/apps/web/src/app/(dashboard)/settings/billing/page.tsx
+++ b/apps/web/src/app/(dashboard)/settings/billing/page.tsx
@@ -1,8 +1,14 @@
+import Link from 'next/link';
import { redirect } from 'next/navigation';
+import { createCustomer } from '@documenso/ee/server-only/stripe/create-customer';
+import { getPortalSession } from '@documenso/ee/server-only/stripe/get-portal-session';
import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session';
+import { getSubscriptionByUserId } from '@documenso/lib/server-only/subscription/get-subscription-by-user-id';
+import { SubscriptionStatus } from '@documenso/prisma/client';
+import { Button } from '@documenso/ui/primitives/button';
-import { PasswordForm } from '~/components/forms/password';
+import { LocaleDate } from '~/components/formatter/locale-date';
import { getServerComponentFlag } from '~/helpers/get-server-component-feature-flag';
export default async function BillingSettingsPage() {
@@ -15,17 +21,55 @@ export default async function BillingSettingsPage() {
redirect('/settings/profile');
}
+ let subscription = await getSubscriptionByUserId({ userId: user.id });
+
+ // If we don't have a customer record, create one as well as an empty subscription.
+ if (!subscription?.customerId) {
+ subscription = await createCustomer({ user });
+ }
+
+ let billingPortalUrl = '';
+
+ if (subscription?.customerId) {
+ billingPortalUrl = await getPortalSession({
+ customerId: subscription.customerId,
+ returnUrl: `${process.env.NEXT_PUBLIC_SITE_URL}/settings/billing`,
+ });
+ }
+
return (
Billing
- Here you can update and manage your subscription.
+ Your subscription is{' '}
+ {subscription.status !== SubscriptionStatus.INACTIVE ? 'active' : 'inactive'}.
+ {subscription?.periodEnd && (
+ <>
+ {' '}
+ Your next payment is due on{' '}
+
+
+
+ .
+ >
+ )}
-
+ {billingPortalUrl && (
+
+ Manage Subscription
+
+ )}
+
+ {!billingPortalUrl && (
+
+ You do not currently have a customer record, this should not happen. Please contact
+ support for assistance.
+
+ )}
);
}
diff --git a/apps/web/src/app/(signing)/sign/[token]/form.tsx b/apps/web/src/app/(signing)/sign/[token]/form.tsx
index 3666941dc..e18571e33 100644
--- a/apps/web/src/app/(signing)/sign/[token]/form.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/form.tsx
@@ -87,9 +87,6 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) =
className="h-44 w-full"
defaultValue={signature ?? undefined}
onChange={(value) => {
- console.log({
- signpadValue: value,
- });
setSignature(value);
}}
/>
diff --git a/apps/web/src/app/(signing)/sign/[token]/name-field.tsx b/apps/web/src/app/(signing)/sign/[token]/name-field.tsx
index f200d94cd..9688619fa 100644
--- a/apps/web/src/app/(signing)/sign/[token]/name-field.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/name-field.tsx
@@ -149,7 +149,7 @@ export const NameField = ({ field, recipient }: NameFieldProps) => {
disabled={!localFullName}
onClick={() => {
setShowFullNameModal(false);
- onSign('local');
+ void onSign('local');
}}
>
Sign
diff --git a/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx b/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx
index bbc58b5e8..cb70ea4db 100644
--- a/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx
+++ b/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx
@@ -63,11 +63,6 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => {
const onSign = async (source: 'local' | 'provider' = 'provider') => {
try {
- console.log({
- providedSignature,
- localSignature,
- });
-
if (!providedSignature && !localSignature) {
setShowSignatureModal(true);
return;
@@ -141,6 +136,7 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => {
{state === 'signed-text' && (
+ {/* This optional chaining is intentional, we don't want to move the check into the condition above */}
{signature?.typedSignature}
)}
@@ -182,7 +178,7 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => {
disabled={!localSignature}
onClick={() => {
setShowSignatureModal(false);
- onSign('local');
+ void onSign('local');
}}
>
Sign
diff --git a/apps/web/src/components/(dashboard)/layout/desktop-nav.tsx b/apps/web/src/components/(dashboard)/layout/desktop-nav.tsx
index 9b64baf58..2c6165a05 100644
--- a/apps/web/src/components/(dashboard)/layout/desktop-nav.tsx
+++ b/apps/web/src/components/(dashboard)/layout/desktop-nav.tsx
@@ -7,13 +7,15 @@ import { cn } from '@documenso/ui/lib/utils';
export type DesktopNavProps = HTMLAttributes
;
export const DesktopNav = ({ className, ...props }: DesktopNavProps) => {
+ // const pathname = usePathname();
+
return (
- {/* No Nav tabs while there is only one main page */}
+ {/* We have no other subpaths rn */}
{/*
{
return (
diff --git a/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx b/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx
index f7d14c39d..02af86d70 100644
--- a/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx
+++ b/apps/web/src/components/(dashboard)/layout/profile-dropdown.tsx
@@ -118,7 +118,7 @@ export const ProfileDropdown = ({ user }: ProfileDropdownProps) => {
- signOut({
+ void signOut({
callbackUrl: '/',
})
}
diff --git a/apps/web/src/components/(dashboard)/period-selector/types.ts b/apps/web/src/components/(dashboard)/period-selector/types.ts
index 4ebfe47f1..2b50f5d6c 100644
--- a/apps/web/src/components/(dashboard)/period-selector/types.ts
+++ b/apps/web/src/components/(dashboard)/period-selector/types.ts
@@ -1,5 +1,6 @@
export type PeriodSelectorValue = '' | '7d' | '14d' | '30d';
export const isPeriodSelectorValue = (value: unknown): value is PeriodSelectorValue => {
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return ['', '7d', '14d', '30d'].includes(value as string);
};
diff --git a/apps/web/src/components/(marketing)/callout.tsx b/apps/web/src/components/(marketing)/callout.tsx
deleted file mode 100644
index d83983141..000000000
--- a/apps/web/src/components/(marketing)/callout.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-'use client';
-
-import Link from 'next/link';
-
-import { Github } from 'lucide-react';
-import { usePlausible } from 'next-plausible';
-
-import { Button } from '@documenso/ui/primitives/button';
-
-export type CalloutProps = {
- starCount?: number;
- [key: string]: unknown;
-};
-
-export const Callout = ({ starCount }: CalloutProps) => {
- const event = usePlausible();
-
- const onSignUpClick = () => {
- const el = document.getElementById('email');
-
- if (el) {
- const { top } = el.getBoundingClientRect();
-
- window.scrollTo({
- top: top - 120,
- behavior: 'smooth',
- });
-
- setTimeout(() => {
- el.focus();
- }, 500);
- }
- };
-
- return (
-
-
- Get the Community Plan
-
- $30/mo. forever!
-
-
-
- event('view-github')}
- >
-
-
- Star on Github
- {starCount && starCount > 0 && (
-
- {starCount.toLocaleString('en-US')}
-
- )}
-
-
-
- );
-};
diff --git a/apps/web/src/components/(marketing)/claim-plan-dialog.tsx b/apps/web/src/components/(marketing)/claim-plan-dialog.tsx
deleted file mode 100644
index 06bfd5ced..000000000
--- a/apps/web/src/components/(marketing)/claim-plan-dialog.tsx
+++ /dev/null
@@ -1,148 +0,0 @@
-'use client';
-
-import React, { useState } from 'react';
-
-import { useSearchParams } from 'next/navigation';
-
-import { zodResolver } from '@hookform/resolvers/zod';
-import { Info, Loader } from 'lucide-react';
-import { usePlausible } from 'next-plausible';
-import { useForm } from 'react-hook-form';
-import { z } from 'zod';
-
-import { cn } from '@documenso/ui/lib/utils';
-import { Button } from '@documenso/ui/primitives/button';
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from '@documenso/ui/primitives/dialog';
-import { Input } from '@documenso/ui/primitives/input';
-import { Label } from '@documenso/ui/primitives/label';
-import { useToast } from '@documenso/ui/primitives/use-toast';
-
-import { claimPlan } from '~/api/claim-plan/fetcher';
-
-import { FormErrorMessage } from '../form/form-error-message';
-
-export const ZClaimPlanDialogFormSchema = z.object({
- name: z.string().min(3),
- email: z.string().email(),
-});
-
-export type TClaimPlanDialogFormSchema = z.infer;
-
-export type ClaimPlanDialogProps = {
- className?: string;
- planId: string;
- children: React.ReactNode;
-};
-
-export const ClaimPlanDialog = ({ className, planId, children }: ClaimPlanDialogProps) => {
- const params = useSearchParams();
- const { toast } = useToast();
- const event = usePlausible();
-
- const [open, setOpen] = useState(() => params?.get('cancelled') === 'true');
-
- const {
- register,
- handleSubmit,
- formState: { errors, isSubmitting },
- } = useForm({
- mode: 'onBlur',
- defaultValues: {
- name: params?.get('name') ?? '',
- email: params?.get('email') ?? '',
- },
- resolver: zodResolver(ZClaimPlanDialogFormSchema),
- });
-
- const onFormSubmit = async ({ name, email }: TClaimPlanDialogFormSchema) => {
- try {
- const delay = new Promise((resolve) => setTimeout(resolve, 1000));
-
- const [redirectUrl] = await Promise.all([
- claimPlan({ name, email, planId, signatureText: name, signatureDataUrl: null }),
- delay,
- ]);
-
- event('claim-plan-pricing');
-
- window.location.href = redirectUrl;
- } catch (error) {
- event('claim-plan-failed');
-
- toast({
- title: 'Something went wrong',
- description: error instanceof Error ? error.message : 'Please try again later.',
- variant: 'destructive',
- });
- }
- };
-
- return (
-
- {children}
-
-
-
- Claim your plan
-
-
- We're almost there! Please enter your email address and name to claim your plan.
-
-
-
-
-
-
- );
-};
diff --git a/apps/web/src/components/(marketing)/faster-smarter-beautiful-bento.tsx b/apps/web/src/components/(marketing)/faster-smarter-beautiful-bento.tsx
deleted file mode 100644
index 2cbaaef53..000000000
--- a/apps/web/src/components/(marketing)/faster-smarter-beautiful-bento.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import { HTMLAttributes } from 'react';
-
-import Image from 'next/image';
-
-import { cn } from '@documenso/ui/lib/utils';
-import { Card, CardContent } from '@documenso/ui/primitives/card';
-
-import backgroundPattern from '~/assets/background-pattern.png';
-import cardBeautifulFigure from '~/assets/card-beautiful-figure.png';
-import cardFastFigure from '~/assets/card-fast-figure.png';
-import cardSmartFigure from '~/assets/card-smart-figure.png';
-
-export type FasterSmarterBeautifulBentoProps = HTMLAttributes;
-
-export const FasterSmarterBeautifulBento = ({
- className,
- ...props
-}: FasterSmarterBeautifulBentoProps) => {
- return (
-
-
-
-
-
- A 10x better signing experience.
- Faster, smarter and more beautiful.
-
-
-
-
-
-
- Fast.
- When it comes to sending or receiving a contract, you can count on lightning-fast
- speeds.
-
-
-
-
-
-
-
-
-
-
-
- Beautiful.
- Because signing should be celebrated. That’s why we care about the smallest detail in
- our product.
-
-
-
-
-
-
-
-
-
-
-
- Smart.
- Our custom templates come with smart rules that can help you save time and energy.
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/apps/web/src/components/(marketing)/footer.tsx b/apps/web/src/components/(marketing)/footer.tsx
deleted file mode 100644
index 823ece92e..000000000
--- a/apps/web/src/components/(marketing)/footer.tsx
+++ /dev/null
@@ -1,86 +0,0 @@
-import { HTMLAttributes } from 'react';
-
-import Image from 'next/image';
-import Link from 'next/link';
-
-import { Github, Slack, Twitter } from 'lucide-react';
-
-import { cn } from '@documenso/ui/lib/utils';
-
-export type FooterProps = HTMLAttributes;
-
-export const Footer = ({ className, ...props }: FooterProps) => {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Pricing
-
-
-
- Status
-
-
-
- Support
-
-
- {/*
- Privacy
- */}
-
-
-
-
- © {new Date().getFullYear()} Documenso, Inc. All rights reserved.
-
-
-
- );
-};
diff --git a/apps/web/src/components/(marketing)/header.tsx b/apps/web/src/components/(marketing)/header.tsx
deleted file mode 100644
index 5a1fa3b89..000000000
--- a/apps/web/src/components/(marketing)/header.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { HTMLAttributes } from 'react';
-
-import Image from 'next/image';
-import Link from 'next/link';
-
-import { cn } from '@documenso/ui/lib/utils';
-
-export type HeaderProps = HTMLAttributes;
-
-export const Header = ({ className, ...props }: HeaderProps) => {
- return (
-
- );
-};
diff --git a/apps/web/src/components/(marketing)/hero.tsx b/apps/web/src/components/(marketing)/hero.tsx
deleted file mode 100644
index 7896a010e..000000000
--- a/apps/web/src/components/(marketing)/hero.tsx
+++ /dev/null
@@ -1,225 +0,0 @@
-'use client';
-
-import Image from 'next/image';
-import Link from 'next/link';
-
-import { Variants, motion } from 'framer-motion';
-import { Github } from 'lucide-react';
-import { usePlausible } from 'next-plausible';
-
-import { cn } from '@documenso/ui/lib/utils';
-import { Button } from '@documenso/ui/primitives/button';
-
-import backgroundPattern from '~/assets/background-pattern.png';
-
-import { Widget } from './widget';
-
-export type HeroProps = {
- className?: string;
- starCount?: number;
- [key: string]: unknown;
-};
-
-const BackgroundPatternVariants: Variants = {
- initial: {
- opacity: 0,
- },
-
- animate: {
- opacity: 1,
-
- transition: {
- delay: 1,
- duration: 1.2,
- },
- },
-};
-
-const HeroTitleVariants: Variants = {
- initial: {
- opacity: 0,
- y: 60,
- },
- animate: {
- opacity: 1,
- y: 0,
- transition: {
- duration: 0.5,
- },
- },
-};
-
-export const Hero = ({ className, starCount, ...props }: HeroProps) => {
- const event = usePlausible();
-
- const onSignUpClick = () => {
- const el = document.getElementById('email');
-
- if (el) {
- const { top } = el.getBoundingClientRect();
-
- window.scrollTo({
- top: top - 120,
- behavior: 'smooth',
- });
-
- requestAnimationFrame(() => {
- el.focus();
- });
- }
- };
-
- return (
-
-
-
-
-
-
-
-
-
- Document signing,
- finally open source.
-
-
-
-
- Get the Community Plan
-
- $30/mo. forever!
-
-
-
- event('view-github')}>
-
-
- Star on Github
- {starCount && starCount > 0 && (
-
- {starCount.toLocaleString('en-US')}
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Documenso Supporter Pledge
-
- Our mission is to create an open signing infrastructure that empowers the world,
- enabling businesses to embrace openness, cooperation, and transparency. We believe
- that signing, as a fundamental act, should embody these values. By offering an
- open-source signing solution, we aim to make document signing accessible, transparent,
- and trustworthy.
-
-
-
- Through our platform, called Documenso, we strive to earn your trust by allowing
- self-hosting and providing complete visibility into its inner workings. We value
- inclusivity and foster an environment where diverse perspectives and contributions are
- welcomed, even though we may not implement them all.
-
-
-
- At Documenso, we envision a web-enabled future for business and contracts, and we are
- committed to being the leading provider of open signing infrastructure. By combining
- exceptional product design with open-source principles, we aim to deliver a robust and
- well-designed application that exceeds your expectations.
-
-
-
- We understand that exceptional products are born from exceptional communities, and we
- invite you to join our open-source community. Your contributions, whether technical or
- non-technical, will help shape the future of signing. Together, we can create a better
- future for everyone.
-
-
-
- Today we invite you to join us on this journey: By signing this mission statement you
- signal your support of Documenso's mission{' '}
-
- (in a non-legally binding, but heartfelt way)
- {' '}
- and lock in the early supporter plan for forever, including everything we build this
- year.
-
-
-
-
-
-
Timur Ercan & Lucas Smith
-
Co-Founders, Documenso
-
-
-
-
-
- );
-};
diff --git a/apps/web/src/components/(marketing)/open-build-template-bento.tsx b/apps/web/src/components/(marketing)/open-build-template-bento.tsx
deleted file mode 100644
index e7920500b..000000000
--- a/apps/web/src/components/(marketing)/open-build-template-bento.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import { HTMLAttributes } from 'react';
-
-import Image from 'next/image';
-
-import { cn } from '@documenso/ui/lib/utils';
-import { Card, CardContent } from '@documenso/ui/primitives/card';
-
-import backgroundPattern from '~/assets/background-pattern.png';
-import cardBuildFigure from '~/assets/card-build-figure.png';
-import cardOpenFigure from '~/assets/card-open-figure.png';
-import cardTemplateFigure from '~/assets/card-template-figure.png';
-
-export type OpenBuildTemplateBentoProps = HTMLAttributes;
-
-export const OpenBuildTemplateBento = ({ className, ...props }: OpenBuildTemplateBentoProps) => {
- return (
-
-
-
-
-
- Truly your own.
- Customise and expand.
-
-
-
-
-
-
- Open Source or Hosted.
- It’s up to you. Either clone our repository or rely on our easy to use hosting
- solution.
-
-
-
-
-
-
-
-
-
-
-
- Build on top.
- Make it your own through advanced customization and adjustability.
-
-
-
-
-
-
-
-
-
-
-
- Template Store (Soon).
- Choose a template from the community app store. Or submit your own template for others
- to use.
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/apps/web/src/components/(marketing)/password-reveal.tsx b/apps/web/src/components/(marketing)/password-reveal.tsx
deleted file mode 100644
index 7e1cb72a3..000000000
--- a/apps/web/src/components/(marketing)/password-reveal.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-'use client';
-
-import { useToast } from '@documenso/ui/primitives/use-toast';
-
-import { useCopyToClipboard } from '~/hooks/use-copy-to-clipboard';
-
-export type PasswordRevealProps = {
- password: string;
-};
-
-export const PasswordReveal = ({ password }: PasswordRevealProps) => {
- const { toast } = useToast();
- const [, copy] = useCopyToClipboard();
-
- const onCopyClick = () => {
- copy(password).then(() => {
- toast({
- title: 'Copied to clipboard',
- description: 'Your password has been copied to your clipboard.',
- });
- });
- };
-
- return (
-
- {password}
-
- );
-};
diff --git a/apps/web/src/components/(marketing)/pricing-table.tsx b/apps/web/src/components/(marketing)/pricing-table.tsx
deleted file mode 100644
index 73003abdc..000000000
--- a/apps/web/src/components/(marketing)/pricing-table.tsx
+++ /dev/null
@@ -1,180 +0,0 @@
-'use client';
-
-import { HTMLAttributes, useMemo, useState } from 'react';
-
-import Link from 'next/link';
-import { useSearchParams } from 'next/navigation';
-
-import { AnimatePresence, motion } from 'framer-motion';
-import { usePlausible } from 'next-plausible';
-
-import { cn } from '@documenso/ui/lib/utils';
-import { Button } from '@documenso/ui/primitives/button';
-
-import { ClaimPlanDialog } from './claim-plan-dialog';
-
-export type PricingTableProps = HTMLAttributes;
-
-const SELECTED_PLAN_BAR_LAYOUT_ID = 'selected-plan-bar';
-
-export const PricingTable = ({ className, ...props }: PricingTableProps) => {
- const params = useSearchParams();
- const event = usePlausible();
-
- const [period, setPeriod] = useState<'MONTHLY' | 'YEARLY'>(() =>
- // eslint-disable-next-line turbo/no-undeclared-env-vars
- params?.get('planId') === process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_YEARLY_PRICE_ID
- ? 'YEARLY'
- : 'MONTHLY',
- );
-
- const planId = useMemo(() => {
- if (period === 'MONTHLY') {
- // eslint-disable-next-line turbo/no-undeclared-env-vars
- return process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID;
- }
-
- // eslint-disable-next-line turbo/no-undeclared-env-vars
- return process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_YEARLY_PRICE_ID;
- }, [period]);
-
- return (
-
-
-
- setPeriod('MONTHLY')}
- >
- Monthly
- {period === 'MONTHLY' && (
-
- )}
-
-
- setPeriod('YEARLY')}
- >
- Yearly
-
- Save $60
-
- {period === 'YEARLY' && (
-
- )}
-
-
-
-
-
-
-
Self Hosted
-
Free
-
-
- For small teams and individuals who need a simple solution
-
-
-
- event('view-github')}
- >
- View on Github
-
-
-
-
-
Host your own instance
-
Full Control
-
Customizability
-
Docker Ready
-
Community Support
-
Free, Forever
-
-
-
-
-
Community
-
-
- {period === 'MONTHLY' && $30 }
- {period === 'YEARLY' && $300 }
-
-
-
-
- For fast-growing companies that aim to scale across multiple teams.
-
-
-
- Signup Now
-
-
-
-
Documenso Early Adopter Deal:
-
Join the movement
-
Simple signing solution
-
Email and Slack assistance
-
- Includes all upcoming features
-
-
Fixed, straightforward pricing
-
-
-
-
-
Enterprise
-
Pricing on request
-
-
- For large organizations that need extra flexibility and control.
-
-
-
event('enterprise-contact')}
- >
-
Contact Us
-
-
-
-
Everything in Community, plus:
-
Custom Subdomain
-
Compliance Check
-
Guaranteed Uptime
-
Reporting & Analysis
-
24/7 Support
-
-
-
-
- );
-};
diff --git a/apps/web/src/components/(marketing)/share-connect-paid-widget-bento.tsx b/apps/web/src/components/(marketing)/share-connect-paid-widget-bento.tsx
deleted file mode 100644
index 05b6a3232..000000000
--- a/apps/web/src/components/(marketing)/share-connect-paid-widget-bento.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import { HTMLAttributes } from 'react';
-
-import Image from 'next/image';
-
-import { cn } from '@documenso/ui/lib/utils';
-import { Card, CardContent } from '@documenso/ui/primitives/card';
-
-import backgroundPattern from '~/assets/background-pattern.png';
-import cardConnectionsFigure from '~/assets/card-connections-figure.png';
-import cardPaidFigure from '~/assets/card-paid-figure.png';
-import cardSharingFigure from '~/assets/card-sharing-figure.png';
-import cardWidgetFigure from '~/assets/card-widget-figure.png';
-
-export type ShareConnectPaidWidgetBentoProps = HTMLAttributes;
-
-export const ShareConnectPaidWidgetBento = ({
- className,
- ...props
-}: ShareConnectPaidWidgetBentoProps) => {
- return (
-
-
-
-
-
- Integrates with all your favourite tools.
- Send, connect, receive and embed everywhere.
-
-
-
-
-
-
- Easy Sharing (Soon).
- Receive your personal link to share with everyone you care about.
-
-
-
-
-
-
-
-
-
-
-
- Connections (Soon).
- Create connections and automations with Zapier and more to integrate with your
- favorite tools.
-
-
-
-
-
-
-
-
-
-
-
- Get paid (Soon).
- Integrated payments with stripe so you don’t have to worry about getting paid.
-
-
-
-
-
-
-
-
-
-
-
- React Widget (Soon).
- Easily embed Documenso into your product. Simply copy and paste our react widget into
- your application.
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/apps/web/src/components/(marketing)/widget.tsx b/apps/web/src/components/(marketing)/widget.tsx
deleted file mode 100644
index 1a15069e9..000000000
--- a/apps/web/src/components/(marketing)/widget.tsx
+++ /dev/null
@@ -1,400 +0,0 @@
-'use client';
-
-import { HTMLAttributes, KeyboardEvent, useMemo, useState } from 'react';
-
-import { zodResolver } from '@hookform/resolvers/zod';
-import { AnimatePresence, motion } from 'framer-motion';
-import { Loader } from 'lucide-react';
-import { usePlausible } from 'next-plausible';
-import { Controller, useForm } from 'react-hook-form';
-import { z } from 'zod';
-
-import { cn } from '@documenso/ui/lib/utils';
-import { Button } from '@documenso/ui/primitives/button';
-import { Card, CardContent } from '@documenso/ui/primitives/card';
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from '@documenso/ui/primitives/dialog';
-import { Input } from '@documenso/ui/primitives/input';
-import { SignaturePad } from '@documenso/ui/primitives/signature-pad';
-import { useToast } from '@documenso/ui/primitives/use-toast';
-
-import { claimPlan } from '~/api/claim-plan/fetcher';
-
-import { FormErrorMessage } from '../form/form-error-message';
-
-const ZWidgetFormSchema = z
- .object({
- email: z.string().email({ message: 'Please enter a valid email address.' }),
- name: z.string().min(3, { message: 'Please enter a valid name.' }),
- })
- .and(
- z.union([
- z.object({
- signatureDataUrl: z.string().min(1),
- signatureText: z.null().or(z.string().max(0)),
- }),
- z.object({
- signatureDataUrl: z.null().or(z.string().max(0)),
- signatureText: z.string().min(1),
- }),
- ]),
- );
-
-export type TWidgetFormSchema = z.infer;
-
-export type WidgetProps = HTMLAttributes;
-
-export const Widget = ({ className, children, ...props }: WidgetProps) => {
- const { toast } = useToast();
- const event = usePlausible();
-
- const [step, setStep] = useState<'EMAIL' | 'NAME' | 'SIGN'>('EMAIL');
- const [showSigningDialog, setShowSigningDialog] = useState(false);
- const [draftSignatureDataUrl, setDraftSignatureDataUrl] = useState(null);
-
- const {
- control,
- register,
- handleSubmit,
- setValue,
- trigger,
- watch,
- formState: { errors, isSubmitting, isValid },
- } = useForm({
- mode: 'onChange',
- defaultValues: {
- email: '',
- name: '',
- signatureDataUrl: null,
- signatureText: '',
- },
- resolver: zodResolver(ZWidgetFormSchema),
- });
-
- const signatureDataUrl = watch('signatureDataUrl');
- const signatureText = watch('signatureText');
-
- const stepsRemaining = useMemo(() => {
- if (step === 'NAME') {
- return 2;
- }
-
- if (step === 'SIGN') {
- return 1;
- }
-
- return 3;
- }, [step]);
-
- const onNextStepClick = () => {
- if (step === 'EMAIL') {
- setStep('NAME');
-
- setTimeout(() => {
- document.querySelector('#name')?.focus();
- }, 0);
- }
-
- if (step === 'NAME') {
- setStep('SIGN');
-
- setTimeout(() => {
- document.querySelector('#signatureText')?.focus();
- }, 0);
- }
- };
-
- const onEnterPress = (callback: () => void) => {
- return (e: KeyboardEvent) => {
- if (e.key === 'Enter') {
- e.preventDefault();
-
- callback();
- }
- };
- };
-
- const onSignatureConfirmClick = () => {
- setValue('signatureDataUrl', draftSignatureDataUrl);
- setValue('signatureText', '');
-
- trigger('signatureDataUrl');
- setShowSigningDialog(false);
- };
-
- const onFormSubmit = async ({
- email,
- name,
- signatureDataUrl,
- signatureText,
- }: TWidgetFormSchema) => {
- try {
- const delay = new Promise((resolve) => setTimeout(resolve, 1000));
-
- // eslint-disable-next-line turbo/no-undeclared-env-vars
- const planId = process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID;
-
- const claimPlanInput = signatureDataUrl
- ? {
- name,
- email,
- planId,
- signatureDataUrl: signatureDataUrl!,
- signatureText: null,
- }
- : {
- name,
- email,
- planId,
- signatureDataUrl: null,
- signatureText: signatureText!,
- };
-
- const [result] = await Promise.all([claimPlan(claimPlanInput), delay]);
-
- event('claim-plan-widget');
-
- window.location.href = result;
- } catch (error) {
- event('claim-plan-failed');
-
- toast({
- title: 'Something went wrong',
- description: error instanceof Error ? error.message : 'Please try again later.',
- variant: 'destructive',
- });
- }
- };
-
- return (
- <>
-
-
-
-
-
-
-
- Add your signature
-
-
-
- By signing you signal your support of Documenso's mission in a
- non-legally binding, but heartfelt way .
- You also unlock the option to purchase the early supporter plan including
- everything we build this year for fixed price.
-
-
-
-
-
- setShowSigningDialog(false)}>
- Cancel
-
-
- onSignatureConfirmClick()}>Confirm
-
-
-
- >
- );
-};
diff --git a/apps/web/src/components/formatter/document-status.tsx b/apps/web/src/components/formatter/document-status.tsx
index 4e1ccf742..126a52f4f 100644
--- a/apps/web/src/components/formatter/document-status.tsx
+++ b/apps/web/src/components/formatter/document-status.tsx
@@ -3,16 +3,17 @@ import { HTMLAttributes } from 'react';
import { CheckCircle2, Clock, File } from 'lucide-react';
import type { LucideIcon } from 'lucide-react/dist/lucide-react';
-import { DocumentStatus as InternalDocumentStatus } from '@documenso/prisma/client';
+import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status';
+import { SignatureIcon } from '@documenso/ui/icons/signature';
import { cn } from '@documenso/ui/lib/utils';
type FriendlyStatus = {
label: string;
- icon: LucideIcon;
+ icon?: LucideIcon;
color: string;
};
-const FRIENDLY_STATUS_MAP: Record = {
+const FRIENDLY_STATUS_MAP: Record = {
PENDING: {
label: 'Pending',
icon: Clock,
@@ -28,10 +29,19 @@ const FRIENDLY_STATUS_MAP: Record = {
icon: File,
color: 'text-yellow-500',
},
+ INBOX: {
+ label: 'Inbox',
+ icon: SignatureIcon,
+ color: 'text-muted-foreground',
+ },
+ ALL: {
+ label: 'All',
+ color: 'text-muted-foreground',
+ },
};
export type DocumentStatusProps = HTMLAttributes & {
- status: InternalDocumentStatus;
+ status: ExtendedDocumentStatus;
inheritColor?: boolean;
};
@@ -45,11 +55,13 @@ export const DocumentStatus = ({
return (
-
+ {Icon && (
+
+ )}
{label}
);
diff --git a/apps/web/src/components/forms/password.tsx b/apps/web/src/components/forms/password.tsx
index 7c595421e..508579b78 100644
--- a/apps/web/src/components/forms/password.tsx
+++ b/apps/web/src/components/forms/password.tsx
@@ -39,6 +39,7 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
const {
register,
handleSubmit,
+ reset,
formState: { errors, isSubmitting },
} = useForm({
values: {
@@ -56,6 +57,8 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
password,
});
+ reset();
+
toast({
title: 'Password updated',
description: 'Your password has been updated successfully.',
@@ -73,7 +76,7 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
title: 'An unknown error occurred',
variant: 'destructive',
description:
- 'We encountered an unknown error while attempting to sign you In. Please try again later.',
+ 'We encountered an unknown error while attempting to update your password. Please try again later.',
});
}
}
diff --git a/apps/web/src/components/forms/profile.tsx b/apps/web/src/components/forms/profile.tsx
index d65a0ce27..5b4045abb 100644
--- a/apps/web/src/components/forms/profile.tsx
+++ b/apps/web/src/components/forms/profile.tsx
@@ -21,7 +21,7 @@ import { FormErrorMessage } from '../form/form-error-message';
export const ZProfileFormSchema = z.object({
name: z.string().min(1),
- signature: z.string().min(1),
+ signature: z.string().min(1, 'Signature Pad cannot be empty'),
});
export type TProfileFormSchema = z.infer;
@@ -122,6 +122,7 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
/>
)}
/>
+