diff --git a/.env.example b/.env.example index 7da56a2e3..a1faef3f0 100644 --- a/.env.example +++ b/.env.example @@ -16,6 +16,11 @@ NEXT_PUBLIC_WEBAPP_URL='http://localhost:3000' NEXTAUTH_SECRET='lorem ipsum sit dolor random string for encryption this could literally be anything' NEXTAUTH_URL='http://localhost:3000' +# SIGNING +CERT_FILE_PATH= +CERT_PASSPHRASE= +CERT_FILE_ENCODING= + # MAIL (NODEMAILER) # SENDGRID # Get a Sendgrid Api key here: https://signup.sendgrid.com @@ -37,6 +42,13 @@ SMTP_MAIL_PASSWORD='' # Sender for signing requests and completion mails. MAIL_FROM='documenso@localhost.com' +# STRIPE +STRIPE_API_KEY= +STRIPE_WEBHOOK_SECRET= +NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID= +NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_YEARLY_PRICE_ID= + #FEATURE FLAGS # Allow users to register via the /signup page. Otherwise they will be redirect to the home page. -ALLOW_SIGNUP=true +NEXT_PUBLIC_ALLOW_SIGNUP=true +NEXT_PUBLIC_ALLOW_SUBSCRIPTIONS=true \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 17ceca23e..36b7f475c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,7 +13,13 @@ "editor.codeActionsOnSave": { "source.removeUnusedImports": false }, - "typescript.tsdk": "node_modules\\typescript\\lib", - "spellright.language": ["de"], - "spellright.documentTypes": ["markdown", "latex", "plaintext"] + "typescript.tsdk": "node_modules/typescript/lib", + "spellright.language": [ + "de" + ], + "spellright.documentTypes": [ + "markdown", + "latex", + "plaintext" + ] } diff --git a/README.md b/README.md index c70655905..26316054f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,13 @@ -
+
++ ${(isAnnual ? plan.prices.yearly.price : plan.prices.monthly.price).toFixed(2)}{" "} + {isAnnual ? "/yr" : "/mo"} +
+ +
+ All you need for easy signing.
Includes everthing we build this year.
+
+ Your subscription is past due.{" "} + + Please update your payment information to avoid any service interruptions. + +
++ Your subscription is inactive. You can continue to view and edit your documents, + but you will not be able to send them or create new ones.{" "} + + You can update your payment information here + +
+{props.document.User.name ? `${props.document.User.name} (${props.document.User.email})` diff --git a/apps/web/components/layout.tsx b/apps/web/components/layout.tsx index 408472633..aa0fad8dc 100644 --- a/apps/web/components/layout.tsx +++ b/apps/web/components/layout.tsx @@ -1,7 +1,12 @@ import { useEffect } from "react"; +import Link from "next/link"; import { useRouter } from "next/router"; import { NEXT_PUBLIC_WEBAPP_URL } from "@documenso/lib/constants"; +import { useSubscription } from "@documenso/lib/stripe"; +import { BillingWarning } from "./billing-warning"; import Navigation from "./navigation"; +import { PaperAirplaneIcon } from "@heroicons/react/24/outline"; +import { SubscriptionStatus } from "@prisma/client"; import { useSession } from "next-auth/react"; function useRedirectToLoginIfUnauthenticated() { @@ -30,11 +35,16 @@ function useRedirectToLoginIfUnauthenticated() { export default function Layout({ children }: any) { useRedirectToLoginIfUnauthenticated(); + const { subscription } = useSubscription(); + return ( <>
Are you new here?{" "} - + Create a new Account
@@ -151,7 +153,7 @@ export default function Login(props: any) { - Hosted Documenso will be available soon™ + Hosted Documenso is here! )} diff --git a/apps/web/components/logo.tsx b/apps/web/components/logo.tsx index c1504e081..64d61bc76 100644 --- a/apps/web/components/logo.tsx +++ b/apps/web/components/logo.tsx @@ -4,16 +4,77 @@ import { classNames } from "@documenso/lib"; export default function Logo(props: any) { return ( <> - - - + > ); } diff --git a/apps/web/components/navigation.tsx b/apps/web/components/navigation.tsx index d0143a41c..c09d5b912 100644 --- a/apps/web/components/navigation.tsx +++ b/apps/web/components/navigation.tsx @@ -112,9 +112,13 @@ export default function TopNavigation() {- Forgot your passwort? Email hi@documenso.com to reset it. -
++ Subscriptions are not enabled on this instance, you have nothing to do here. +
+ )} + + {isSubscriptionsEnabled() && ( + <> ++ Your subscription is currently{" "} + + {subscription?.status && + subscription?.status !== SubscriptionStatus.INACTIVE + ? "Active" + : "Inactive"} + + . +
+ + {subscription?.status === SubscriptionStatus.PAST_DUE && ( ++ Your subscription is past due. Please update your payment details to + continue using the service without interruption. +
+ )} + +404
diff --git a/apps/web/pages/500.jsx b/apps/web/pages/500.jsx index 2735dded2..3589da952 100644 --- a/apps/web/pages/500.jsx +++ b/apps/web/pages/500.jsx @@ -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 ( <>diff --git a/apps/web/pages/_app.tsx b/apps/web/pages/_app.tsx index f8f86b960..a1193a681 100644 --- a/apps/web/pages/_app.tsx +++ b/apps/web/pages/_app.tsx @@ -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
= NextPage
& {
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 (