diff --git a/apps/marketing/content/blog/next.mdx b/apps/marketing/content/blog/next.mdx index c241cf3eb..2f83fe3ef 100644 --- a/apps/marketing/content/blog/next.mdx +++ b/apps/marketing/content/blog/next.mdx @@ -20,11 +20,11 @@ Today, I'm pleased to share with you a preview of the next Documenso. We redesigned the whole signing flow to make it more appealing and more convenient. -We improved the overall look and feel by making it more elegant and appropriately playful. Focused on the task at hand, but explicitly enjoying doing it. +We improved the overall look and feel by making it more elegant and appropriately playful. Focused on the task at hand, but explicitly enjoying doing it. **We call it happy minimalism.** -We paid particular attention to the moment of signing, which should be celebrated. +We paid particular attention to the moment of signing, which should be celebrated. The image below is the final bloom of the completion celebration we added: diff --git a/apps/marketing/content/blog/shop.mdx b/apps/marketing/content/blog/shop.mdx new file mode 100644 index 000000000..88f092ac3 --- /dev/null +++ b/apps/marketing/content/blog/shop.mdx @@ -0,0 +1,62 @@ +--- +title: Shop and Limited Edition "Mania" Shirt +description: Happy Launch Week Day 3. The limited edition "Malfunction Mania" shirt is here. Grab it, while you can. +authorName: 'Timur Ercan' +authorImage: '/blog/blog-author-timur.jpeg' +authorRole: 'Co-Founder' +date: 2023-09-27 +tags: + - Testing + - Rewrite + - Bounties +--- + +
+ + +
+ The Limited Edition "Malfunction Mania" Shirt - Only during Malfunction Mania +
+
+ +> TLDR; We have a fancy limited edition shirt. Contribute to Malfunction Mania to get one. + +We kicked off [Malfunction Mania](https://documenso.com/blog/malfunction-mania) yesterday, and the first [Issues](https://github.com/documenso/documenso/issues) are coming in. As mentioned, there will be dollar bounties, but we also wanted to celebrate entering the final stage of version 1.0 with something special. This is why we created the limited edition shirt for Malfunction Mania. It will only be available during the runtime of Malfunction Mania. We have yet to set an exact end date, the next event in October, however, is looming, ready to end MM. + +## Documenso Merch Shop + +The shirt will be available in our [merch shop](https://documen.so/shop) via a unique discount code. While the shirt will be gone after Malfunction Mania, the shop is here to stay and provide a well-deserved reward for great community members and contributors. + +
+ + +
+ Merch at Documenso is always given to those who deserve it. +
+
+ +## How to get the shirt + +If you have been following us, you know we are not big on formalities but highly value rewarding merit. That being said, any worthwhile contribution has a chance to get one. To inspire, here are a few ideas on how to contribute to securing one: + +- Report a bug with detailed reproduction details +- Fix a bug (you or somebody else reported) +- Analyze and describe a usability or user experience shortcoming +- Test the product in a systematic and least somewhat documented way +- Engage in discussion about the current version and its choices +- Raise awareness for Malfunction Mania and try out the [version currently in staging](https://documen.so/staging) +- Review the version with a video, stream, or screenshots and post about it +- Review existing or create missing documenso +- ... + +**[Follow Documenso on Twitter / X](https://documen.so/tw) and [join the Discord server](https://documen.so/discord) to get the latest about Malfunction Mania.** diff --git a/apps/marketing/public/blog/mania-shirt.png b/apps/marketing/public/blog/mania-shirt.png new file mode 100644 index 000000000..eab20119e Binary files /dev/null and b/apps/marketing/public/blog/mania-shirt.png differ diff --git a/apps/marketing/public/blog/shop.png b/apps/marketing/public/blog/shop.png new file mode 100644 index 000000000..432a5abe8 Binary files /dev/null and b/apps/marketing/public/blog/shop.png differ diff --git a/apps/marketing/src/components/(marketing)/single-player-mode/single-player-mode-success.tsx b/apps/marketing/src/components/(marketing)/single-player-mode/single-player-mode-success.tsx index 70bf58926..0c85fc65c 100644 --- a/apps/marketing/src/components/(marketing)/single-player-mode/single-player-mode-success.tsx +++ b/apps/marketing/src/components/(marketing)/single-player-mode/single-player-mode-success.tsx @@ -73,7 +73,7 @@ export const SinglePlayerModeSuccess = ({ className, document }: SinglePlayerMod )} -

+

You have signed {document.title}

@@ -84,17 +84,17 @@ export const SinglePlayerModeSuccess = ({ className, document }: SinglePlayerMod signingCelebrationImage={signingCelebration} /> -
+
{/* TODO: Hook this up */} - null); if (!recipientOrSender) { - return null; + return NextResponse.json({ error: 'Not found' }, { status: 404 }); } const isRecipient = 'Signature' in recipientOrSender; diff --git a/apps/web/src/app/(share)/share/[slug]/page.tsx b/apps/web/src/app/(share)/share/[slug]/page.tsx index 63449f29f..51684d384 100644 --- a/apps/web/src/app/(share)/share/[slug]/page.tsx +++ b/apps/web/src/app/(share)/share/[slug]/page.tsx @@ -1,11 +1,39 @@ import { Metadata } from 'next'; +import { headers } from 'next/headers'; +import { redirect } from 'next/navigation'; -import { Redirect } from './redirect'; +import { APP_BASE_URL } from '@documenso/lib/constants/app'; -export const metadata: Metadata = { - title: 'Documenso - Share', +type SharePageProps = { + params: { slug: string }; }; -export default function SharePage() { - return ; +export function generateMetadata({ params: { slug } }: SharePageProps) { + return { + title: 'Documenso - Share', + description: 'I just signed a document with Documenso!', + openGraph: { + title: 'Documenso - Join the open source signing revolution', + description: 'I just signed with Documenso!', + type: 'website', + images: [`${APP_BASE_URL}/share/${slug}/opengraph`], + }, + twitter: { + site: '@documenso', + card: 'summary_large_image', + images: [`${APP_BASE_URL}/share/${slug}/opengraph`], + description: 'I just signed with Documenso!', + }, + } satisfies Metadata; +} + +export default function SharePage() { + const userAgent = headers().get('User-Agent') ?? ''; + + // https://stackoverflow.com/questions/47026171/how-to-detect-bots-for-open-graph-with-user-agent + if (/bot|facebookexternalhit|WhatsApp|google|bing|duckduckbot|MetaInspector/i.test(userAgent)) { + return null; + } + + redirect(process.env.NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001'); } diff --git a/apps/web/src/app/(share)/share/[slug]/redirect.tsx b/apps/web/src/app/(share)/share/[slug]/redirect.tsx deleted file mode 100644 index 5b3af0771..000000000 --- a/apps/web/src/app/(share)/share/[slug]/redirect.tsx +++ /dev/null @@ -1,11 +0,0 @@ -'use client'; - -import { useEffect } from 'react'; - -export const Redirect = () => { - useEffect(() => { - window.location.href = process.env.NEXT_PUBLIC_MARKETING_URL ?? 'http://localhost:3001'; - }, []); - - return null; -}; diff --git a/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx b/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx index a89f1bb3f..a8081069f 100644 --- a/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx @@ -9,7 +9,7 @@ import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-f import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token'; import { DocumentStatus, FieldType } from '@documenso/prisma/client'; import { DocumentDownloadButton } from '@documenso/ui/components/document/document-download-button'; -import { SigningCard } from '@documenso/ui/components/signing-card'; +import { SigningCard3D } from '@documenso/ui/components/signing-card'; import signingCelebration from '~/assets/signing-celebration.png'; @@ -53,11 +53,11 @@ export default async function CompletedSigningPage({ recipient.email; return ( -
+
{/* Card with recipient */} - + -
+
{match(document.status) .with(DocumentStatus.COMPLETED, () => (
@@ -71,41 +71,44 @@ export default async function CompletedSigningPage({ Waiting for others to sign
))} + +

+ You have signed "{document.title}" +

+ + {match(document.status) + .with(DocumentStatus.COMPLETED, () => ( +

+ Everyone has signed! You will receive an Email copy of the signed document. +

+ )) + .otherwise(() => ( +

+ You will receive an Email copy of the signed document once everyone has signed. +

+ ))} + +
+ + + +
+ +

+ Want to send slick signing links like this one?{' '} + + Check out Documenso. + +

- -

- You have signed "{document.title}" -

- - {match(document.status) - .with(DocumentStatus.COMPLETED, () => ( -

- Everyone has signed! You will receive an Email copy of the signed document. -

- )) - .otherwise(() => ( -

- You will receive an Email copy of the signed document once everyone has signed. -

- ))} - -
- - - -
- -

- Want to send slick signing links like this one?{' '} - - Check out Documenso. - -

); } diff --git a/apps/web/src/app/(signing)/sign/[token]/page.tsx b/apps/web/src/app/(signing)/sign/[token]/page.tsx index 2d61f096e..f5f1f3c3d 100644 --- a/apps/web/src/app/(signing)/sign/[token]/page.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/page.tsx @@ -1,4 +1,4 @@ -import { notFound } from 'next/navigation'; +import { notFound, redirect } from 'next/navigation'; import { match } from 'ts-pattern'; @@ -9,7 +9,7 @@ import { viewedDocument } from '@documenso/lib/server-only/document/viewed-docum import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token'; import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token'; import { getFile } from '@documenso/lib/universal/upload/get-file'; -import { FieldType } from '@documenso/prisma/client'; +import { DocumentStatus, FieldType, SigningStatus } from '@documenso/prisma/client'; import { Card, CardContent } from '@documenso/ui/primitives/card'; import { ElementVisible } from '@documenso/ui/primitives/element-visible'; import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer'; @@ -53,6 +53,13 @@ export default async function SigningPage({ params: { token } }: SigningPageProp const user = await getServerComponentSession(); + if ( + document.status === DocumentStatus.COMPLETED || + recipient.signingStatus === SigningStatus.SIGNED + ) { + redirect(`/sign/${token}/complete`); + } + return (
diff --git a/apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx b/apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx index 9ef2cd105..8d611c2d1 100644 --- a/apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx +++ b/apps/web/src/components/(dashboard)/avatar/stack-avatars-with-tooltip.tsx @@ -40,7 +40,7 @@ export const StackAvatarsWithTooltip = ({ return ( - + {children || } diff --git a/packages/prisma/migrations/20230605122017_password_reset/migration.sql b/packages/prisma/migrations/20230605122017_password_reset/migration.sql new file mode 100644 index 000000000..782a60880 --- /dev/null +++ b/packages/prisma/migrations/20230605122017_password_reset/migration.sql @@ -0,0 +1,15 @@ +-- CreateTable +CREATE TABLE "PasswordResetToken" ( + "id" SERIAL NOT NULL, + "token" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "userId" INTEGER NOT NULL, + + CONSTRAINT "PasswordResetToken_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "PasswordResetToken_token_key" ON "PasswordResetToken"("token"); + +-- AddForeignKey +ALTER TABLE "PasswordResetToken" ADD CONSTRAINT "PasswordResetToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/packages/prisma/migrations/20230605164015_expire_password_reset_token/migration.sql b/packages/prisma/migrations/20230605164015_expire_password_reset_token/migration.sql new file mode 100644 index 000000000..a3a70e575 --- /dev/null +++ b/packages/prisma/migrations/20230605164015_expire_password_reset_token/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - Added the required column `expiry` to the `PasswordResetToken` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "PasswordResetToken" ADD COLUMN "expiry" TIMESTAMP(3) NOT NULL; diff --git a/packages/prisma/migrations/20230917190854_password_reset_token/migration.sql b/packages/prisma/migrations/20230917190854_password_reset_token/migration.sql index d22107691..2d127d917 100644 --- a/packages/prisma/migrations/20230917190854_password_reset_token/migration.sql +++ b/packages/prisma/migrations/20230917190854_password_reset_token/migration.sql @@ -1,3 +1,5 @@ +DROP TABLE IF EXISTS "PasswordResetToken" CASCADE; + -- CreateTable CREATE TABLE "PasswordResetToken" ( "id" SERIAL NOT NULL, diff --git a/packages/ui/components/signing-card.tsx b/packages/ui/components/signing-card.tsx index 496e451d0..a2dd66bae 100644 --- a/packages/ui/components/signing-card.tsx +++ b/packages/ui/components/signing-card.tsx @@ -148,7 +148,7 @@ const SigningCardContent = ({ className, name }: SigningCardContentProps) => { return ( { return (