mirror of
https://github.com/documenso/documenso.git
synced 2025-11-18 02:32:00 +10:00
Merge branch 'main' into bugfix/long_filename
This commit is contained in:
@ -1,14 +1,16 @@
|
||||
import { Button } from "@documenso/ui";
|
||||
import Logo from "../components/logo";
|
||||
import { ArrowSmallLeftIcon } from "@heroicons/react/20/solid";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Custom404() {
|
||||
return (
|
||||
<>
|
||||
<main className="relative isolate min-h-full bg-gray-100">
|
||||
<div className="absolute top-10 left-10">
|
||||
<Logo className="w-10 md:w-20" />
|
||||
</div>
|
||||
<Link href="/" className="absolute top-10 left-10 flex gap-x-2 items-center">
|
||||
<Logo className="w-10" />
|
||||
<h2 className="text-2xl font-semibold">Documenso</h2>
|
||||
</Link>
|
||||
|
||||
<div className="mx-auto max-w-7xl px-6 py-48 text-center sm:py-40 lg:px-8">
|
||||
<p className="text-brown text-base font-semibold leading-8">404</p>
|
||||
|
||||
@ -2,14 +2,16 @@ import { Button } from "@documenso/ui";
|
||||
import Logo from "../components/logo";
|
||||
import { ArrowSmallLeftIcon } from "@heroicons/react/20/solid";
|
||||
import { EllipsisVerticalIcon } from "@heroicons/react/20/solid";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Custom500() {
|
||||
return (
|
||||
<>
|
||||
<div className="relative flex min-h-full flex-col items-center justify-center bg-black text-white">
|
||||
<div className="absolute top-10 left-10">
|
||||
<Logo dark className="w-10 md:w-20" />
|
||||
</div>
|
||||
<Link href="/" className="absolute top-10 left-10 flex gap-x-2 items-center invert">
|
||||
<Logo className="w-10" />
|
||||
<h2 className="text-2xl font-semibold text-black">Documenso</h2>
|
||||
</Link>
|
||||
|
||||
<div className="mt-20 max-w-7xl px-4 py-10">
|
||||
<p className="inline-flex items-center text-3xl font-bold sm:text-5xl">
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { ReactElement, ReactNode } from "react";
|
||||
import { NextPage } from "next";
|
||||
import type { AppProps } from "next/app";
|
||||
import { Montserrat, Qwigley } from "next/font/google";
|
||||
import { SubscriptionProvider } from "@documenso/lib/stripe/providers/subscription-provider";
|
||||
import "../../../node_modules/placeholder-loading/src/scss/placeholder-loading.scss";
|
||||
import "../../../node_modules/react-resizable/css/styles.css";
|
||||
import "../styles/tailwind.css";
|
||||
@ -10,6 +12,20 @@ import "react-tooltip/dist/react-tooltip.css";
|
||||
|
||||
export { coloredConsole } from "@documenso/lib";
|
||||
|
||||
const montserrat = Montserrat({
|
||||
subsets: ["latin"],
|
||||
weight: ["400", "700"],
|
||||
display: "swap",
|
||||
variable: "--font-sans",
|
||||
});
|
||||
|
||||
const qwigley = Qwigley({
|
||||
subsets: ["latin"],
|
||||
weight: ["400"],
|
||||
display: "swap",
|
||||
variable: "--font-qwigley",
|
||||
});
|
||||
|
||||
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
|
||||
getLayout?: (page: ReactElement) => ReactNode;
|
||||
};
|
||||
@ -20,13 +36,17 @@ type AppPropsWithLayout = AppProps & {
|
||||
|
||||
export default function App({
|
||||
Component,
|
||||
pageProps: { session, ...pageProps },
|
||||
pageProps: { session, initialSubscription, ...pageProps },
|
||||
}: AppPropsWithLayout) {
|
||||
const getLayout = Component.getLayout || ((page: any) => page);
|
||||
return (
|
||||
<SessionProvider session={session}>
|
||||
<Toaster position="top-center"></Toaster>
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
<SubscriptionProvider initialSubscription={initialSubscription}>
|
||||
<main className={`${montserrat.variable} h-full font-sans`}>
|
||||
<Toaster position="top-center" />
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
</main>
|
||||
</SubscriptionProvider>
|
||||
</SessionProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
import { Head, Html, Main, NextScript } from "next/document";
|
||||
import Script from "next/script";
|
||||
|
||||
export default function Document(props) {
|
||||
let pageProps = props.__NEXT_DATA__?.props?.pageProps;
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
<Html className="h-full scroll-smooth bg-gray-100 font-normal antialiased" lang="en">
|
||||
<Head>
|
||||
<meta name="color-scheme"></meta>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
</Head>
|
||||
<body className="flex h-full flex-col">
|
||||
<Main />
|
||||
@ -4,6 +4,7 @@ import { defaultHandler, defaultResponder } from "@documenso/lib/server";
|
||||
import { getUserFromToken } from "@documenso/lib/server";
|
||||
import prisma from "@documenso/prisma";
|
||||
import formidable from "formidable";
|
||||
import { isSubscribedServer } from "@documenso/lib/stripe";
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
@ -15,7 +16,17 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const form = formidable();
|
||||
|
||||
const user = await getUserFromToken(req, res);
|
||||
if (!user) return;
|
||||
if (!user) {
|
||||
return res.status(401).end();
|
||||
};
|
||||
|
||||
const isSubscribed = await isSubscribedServer(req);
|
||||
|
||||
if (!isSubscribed) {
|
||||
throw new Error("User is not subscribed.");
|
||||
}
|
||||
|
||||
|
||||
form.parse(req, async (err, fields, files) => {
|
||||
if (err) throw err;
|
||||
|
||||
|
||||
1
apps/web/pages/api/stripe/checkout-session.ts
Normal file
1
apps/web/pages/api/stripe/checkout-session.ts
Normal file
@ -0,0 +1 @@
|
||||
export { checkoutSessionHandler as default } from '@documenso/lib/stripe/handlers/checkout-session'
|
||||
1
apps/web/pages/api/stripe/portal-session.ts
Normal file
1
apps/web/pages/api/stripe/portal-session.ts
Normal file
@ -0,0 +1 @@
|
||||
export { portalSessionHandler as default } from "@documenso/lib/stripe/handlers/portal-session";
|
||||
1
apps/web/pages/api/stripe/subscription.ts
Normal file
1
apps/web/pages/api/stripe/subscription.ts
Normal file
@ -0,0 +1 @@
|
||||
export { getSubscriptionHandler as default } from '@documenso/lib/stripe/handlers/get-subscription'
|
||||
5
apps/web/pages/api/stripe/webhook.ts
Normal file
5
apps/web/pages/api/stripe/webhook.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export const config = {
|
||||
api: { bodyParser: false },
|
||||
};
|
||||
|
||||
export { webhookHandler as default } from "@documenso/lib/stripe/handlers/webhook";
|
||||
@ -1,4 +1,5 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { hashPassword } from "@documenso/lib/auth";
|
||||
import { defaultHandler, defaultResponder, getUserFromToken } from "@documenso/lib/server";
|
||||
import prisma from "@documenso/prisma";
|
||||
|
||||
@ -24,6 +25,13 @@ async function patchHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!user) return;
|
||||
|
||||
const updatedUser = req.body;
|
||||
|
||||
let password: string | undefined = undefined;
|
||||
|
||||
if (typeof updatedUser.password === "string" && updatedUser.password.length >= 6) {
|
||||
password = await hashPassword(updatedUser.password);
|
||||
}
|
||||
|
||||
await prisma.user
|
||||
.update({
|
||||
where: {
|
||||
@ -31,6 +39,7 @@ async function patchHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
},
|
||||
data: {
|
||||
name: updatedUser.name,
|
||||
password,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
@ -20,12 +20,15 @@ import {
|
||||
} from "@prisma/client";
|
||||
import { truncate } from "fs";
|
||||
import { Tooltip as ReactTooltip } from "react-tooltip";
|
||||
import { useSubscription } from "@documenso/lib/stripe";
|
||||
|
||||
type FormValues = {
|
||||
document: File;
|
||||
};
|
||||
|
||||
const DashboardPage: NextPageWithLayout = (props: any) => {
|
||||
const { hasSubscription } = useSubscription();
|
||||
|
||||
const stats = [
|
||||
{
|
||||
name: "Draft",
|
||||
@ -90,9 +93,12 @@ const DashboardPage: NextPageWithLayout = (props: any) => {
|
||||
</div>
|
||||
<div
|
||||
onClick={() => {
|
||||
document?.getElementById("fileUploadHelper")?.click();
|
||||
if (hasSubscription) {
|
||||
document?.getElementById("fileUploadHelper")?.click();
|
||||
}
|
||||
}}
|
||||
className="group hover:border-neon-600 duration-200 relative block w-full cursor-pointer rounded-lg border-2 border-dashed border-gray-300 p-12 text-center focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
|
||||
aria-disabled={!hasSubscription}
|
||||
className="group hover:border-neon-600 duration-200 relative block w-full cursor-pointer rounded-lg border-2 border-dashed border-gray-300 p-12 text-center focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 aria-disabled:opacity-50 aria-disabled:pointer-events-none">
|
||||
|
||||
<svg
|
||||
className="mx-auto h-12 w-12 text-gray-400 group-hover:text-gray-700 duration-200"
|
||||
|
||||
@ -20,9 +20,11 @@ import {
|
||||
} from "@heroicons/react/24/outline";
|
||||
import { DocumentStatus } from "@prisma/client";
|
||||
import { Tooltip as ReactTooltip } from "react-tooltip";
|
||||
import { useSubscription } from "@documenso/lib/stripe";
|
||||
|
||||
const DocumentsPage: NextPageWithLayout = (props: any) => {
|
||||
const router = useRouter();
|
||||
const { hasSubscription } = useSubscription();
|
||||
const [documents, setDocuments]: any[] = useState([]);
|
||||
const [filteredDocuments, setFilteredDocuments] = useState([]);
|
||||
|
||||
@ -135,6 +137,7 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
||||
<div className="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<Button
|
||||
icon={DocumentPlusIcon}
|
||||
disabled={!hasSubscription}
|
||||
onClick={() => {
|
||||
document?.getElementById("fileUploadHelper")?.click();
|
||||
}}>
|
||||
@ -378,6 +381,7 @@ const DocumentsPage: NextPageWithLayout = (props: any) => {
|
||||
<div className="mt-6">
|
||||
<Button
|
||||
icon={PlusIcon}
|
||||
disabled={!hasSubscription}
|
||||
onClick={() => {
|
||||
document?.getElementById("fileUploadHelper")?.click();
|
||||
}}>
|
||||
|
||||
@ -4,6 +4,7 @@ import { useRouter } from "next/router";
|
||||
import { NEXT_PUBLIC_WEBAPP_URL } from "@documenso/lib";
|
||||
import { getDocument } from "@documenso/lib/query";
|
||||
import { getUserFromToken } from "@documenso/lib/server";
|
||||
import { useSubscription } from "@documenso/lib/stripe";
|
||||
import { Breadcrumb, Button } from "@documenso/ui";
|
||||
import PDFEditor from "../../../components/editor/pdf-editor";
|
||||
import Layout from "../../../components/layout";
|
||||
@ -14,6 +15,7 @@ import { Document as PrismaDocument } from "@prisma/client";
|
||||
|
||||
const DocumentsDetailPage: NextPageWithLayout = (props: any) => {
|
||||
const router = useRouter();
|
||||
const { hasSubscription } = useSubscription();
|
||||
|
||||
return (
|
||||
<div className="mt-4">
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
import { DocumentStatus, Document as PrismaDocument, Recipient } from "@prisma/client";
|
||||
import { FormProvider, useFieldArray, useForm, useWatch } from "react-hook-form";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useSubscription } from "@documenso/lib/stripe";
|
||||
|
||||
export type FormValues = {
|
||||
signers: Array<Pick<Recipient, 'id' | 'email' | 'name' | 'sendStatus' | 'readStatus' | 'signingStatus'>>;
|
||||
@ -29,6 +30,7 @@ export type FormValues = {
|
||||
type FormSigner = FormValues["signers"][number];
|
||||
|
||||
const RecipientsPage: NextPageWithLayout = (props: any) => {
|
||||
const { hasSubscription } = useSubscription();
|
||||
const title: string = `"` + props?.document?.title + `"` + "Recipients | Documenso";
|
||||
const breadcrumbItems = [
|
||||
{
|
||||
@ -116,6 +118,7 @@ const RecipientsPage: NextPageWithLayout = (props: any) => {
|
||||
: setOpen(true);
|
||||
}}
|
||||
disabled={
|
||||
!hasSubscription ||
|
||||
(formValues.length || 0) === 0 ||
|
||||
!formValues.some(
|
||||
(r) => r.email && !hasEmailError(r) && r.sendStatus === "NOT_SENT"
|
||||
|
||||
@ -59,7 +59,7 @@ export async function getServerSideProps(context: any) {
|
||||
},
|
||||
});
|
||||
|
||||
const recipient = await prisma.recipient.findFirstOrThrow({
|
||||
const recipient = await prisma.recipient.findFirst({
|
||||
where: {
|
||||
token: recipientToken,
|
||||
},
|
||||
@ -68,12 +68,21 @@ export async function getServerSideProps(context: any) {
|
||||
},
|
||||
});
|
||||
|
||||
if (!recipient) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: "/404",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Document is already signed
|
||||
if (recipient.Document.status === DocumentStatus.COMPLETED) {
|
||||
return {
|
||||
redirect: {
|
||||
permanent: false,
|
||||
destination: `/documents/${recipient.Document.id}/signed`,
|
||||
destination: `/documents/${recipient.Document.id}/signed?token=${recipientToken}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ const Signed: NextPageWithLayout = (props: any) => {
|
||||
<p className="mt-4 text-center text-sm text-gray-600">
|
||||
Want to send slick signing links like this one?{" "}
|
||||
<Link href="https://documenso.com" className="text-neon hover:text-neon font-medium">
|
||||
Hosted Documenso is coming soon™
|
||||
Hosted Documenso is here!
|
||||
</Link>
|
||||
</p>
|
||||
</>
|
||||
|
||||
@ -24,11 +24,11 @@ export async function getServerSideProps(context: any) {
|
||||
},
|
||||
};
|
||||
|
||||
const ALLOW_SIGNUP = process.env.ALLOW_SIGNUP === "true";
|
||||
const ALLOW_SIGNUP = process.env.NEXT_PUBLIC_ALLOW_SIGNUP === "true";
|
||||
|
||||
return {
|
||||
props: {
|
||||
ALLOW_SIGNUP: ALLOW_SIGNUP,
|
||||
ALLOW_SIGNUP,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
1
apps/web/pages/settings/billing.tsx
Normal file
1
apps/web/pages/settings/billing.tsx
Normal file
@ -0,0 +1 @@
|
||||
export { default } from ".";
|
||||
@ -15,7 +15,7 @@ export default function SignupPage(props: { source: string }) {
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context: any) {
|
||||
if (process.env.ALLOW_SIGNUP !== "true")
|
||||
if (process.env.NEXT_PUBLIC_ALLOW_SIGNUP !== "true")
|
||||
return {
|
||||
redirect: {
|
||||
destination: "/login",
|
||||
|
||||
Reference in New Issue
Block a user