Merge branch 'main' into bugfix/long_filename
This commit is contained in:
@@ -1,8 +1,4 @@
|
||||
export const NEXT_PUBLIC_WEBAPP_URL =
|
||||
process.env.IS_PULL_REQUEST === "true"
|
||||
? process.env.RENDER_EXTERNAL_URL
|
||||
: process.env.NEXT_PUBLIC_WEBAPP_URL;
|
||||
|
||||
console.log("IS_PULL_REQUEST:" + process.env.IS_PULL_REQUEST);
|
||||
console.log("RENDER_EXTERNAL_URL:" + process.env.RENDER_EXTERNAL_URL);
|
||||
console.log("NEXT_PUBLIC_WEBAPP_URL:" + process.env.NEXT_PUBLIC_WEBAPP_URL);
|
||||
: process.env.NEXT_PUBLIC_WEBAPP_URL;
|
||||
@@ -17,7 +17,7 @@ export const signingCompleteTemplate = (message: string) => {
|
||||
A copy of the signed document has been attached to this email.
|
||||
</p>
|
||||
<p style="margin-top: 14px;">
|
||||
<small>Like Documenso? <a href="https://documenso.com">Hosted Documenso is coming soon™</a>.</small>
|
||||
<small>Like Documenso? <a href="https://documenso.com">Hosted Documenso is here!</a>.</small>
|
||||
</p>`;
|
||||
|
||||
const html = baseEmailTemplate(message, customContent);
|
||||
|
||||
@@ -21,7 +21,7 @@ export const signingRequestTemplate = (
|
||||
<small>If you have questions about this document, you should ask ${user.name}.</small>
|
||||
<hr size="1" style="height:1px;border:none;color:#e0e0e0;background-color:#e0e0e0">
|
||||
<p style="margin-top: 14px;">
|
||||
<small>Want to send you own signing links? <a href="https://documenso.com">Hosted Documenso is coming soon™</a>.</small>
|
||||
<small>Want to send you own signing links? <a href="https://documenso.com">Hosted Documenso is here!</a>.</small>
|
||||
</p>`;
|
||||
|
||||
const html = baseEmailTemplate(message, customContent);
|
||||
@@ -29,4 +29,4 @@ export const signingRequestTemplate = (
|
||||
return html;
|
||||
};
|
||||
|
||||
export default signingRequestTemplate;
|
||||
export default signingRequestTemplate;
|
||||
@@ -4,6 +4,10 @@
|
||||
"private": true,
|
||||
"main": "index.ts",
|
||||
"dependencies": {
|
||||
"bcryptjs": "^2.4.3"
|
||||
"@documenso/prisma": "*",
|
||||
"@prisma/client": "^4.8.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"micro": "^10.0.1",
|
||||
"stripe": "^12.4.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
24
packages/lib/process-env.d.ts
vendored
Normal file
24
packages/lib/process-env.d.ts
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
declare namespace NodeJS {
|
||||
export interface ProcessEnv {
|
||||
DATABASE_URL: string;
|
||||
NEXT_PUBLIC_WEBAPP_URL: string;
|
||||
NEXTAUTH_SECRET: string;
|
||||
NEXTAUTH_URL: string;
|
||||
|
||||
SENDGRID_API_KEY?: string;
|
||||
SMTP_MAIL_HOST?: string;
|
||||
SMTP_MAIL_PORT?: string;
|
||||
SMTP_MAIL_USER?: string;
|
||||
SMTP_MAIL_PASSWORD?: string;
|
||||
|
||||
MAIL_FROM: string;
|
||||
|
||||
STRIPE_API_KEY?: string;
|
||||
STRIPE_WEBHOOK_SECRET?: string;
|
||||
STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID?: string;
|
||||
STRIPE_COMMUNITY_PLAN_YEARLY_PRICE_ID?: string;
|
||||
|
||||
NEXT_PUBLIC_ALLOW_SIGNUP?: string;
|
||||
NEXT_PUBLIC_ALLOW_SUBSCRIPTIONS?: string;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { HttpError } from "@documenso/lib/server";
|
||||
import { NotFoundError, PrismaClientKnownRequestError } from "@prisma/client/runtime";
|
||||
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
|
||||
|
||||
export function getServerErrorFromUnknown(cause: unknown): HttpError {
|
||||
// Error was manually thrown and does not need to be parsed.
|
||||
@@ -18,7 +18,7 @@ export function getServerErrorFromUnknown(cause: unknown): HttpError {
|
||||
return new HttpError({ statusCode: 400, message: cause.message, cause });
|
||||
}
|
||||
|
||||
if (cause instanceof NotFoundError) {
|
||||
if (cause instanceof PrismaClientKnownRequestError) {
|
||||
return new HttpError({ statusCode: 404, message: cause.message, cause });
|
||||
}
|
||||
|
||||
|
||||
7
packages/lib/stripe/client.ts
Normal file
7
packages/lib/stripe/client.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import Stripe from 'stripe';
|
||||
|
||||
|
||||
export const stripe = new Stripe(process.env.STRIPE_API_KEY!, {
|
||||
apiVersion: "2022-11-15",
|
||||
typescript: true,
|
||||
});
|
||||
15
packages/lib/stripe/data/plans.ts
Normal file
15
packages/lib/stripe/data/plans.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export const STRIPE_PLANS = [
|
||||
{
|
||||
name: "Community Plan",
|
||||
prices: {
|
||||
monthly: {
|
||||
price: 30,
|
||||
priceId: process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID ?? "",
|
||||
},
|
||||
yearly: {
|
||||
price: 300,
|
||||
priceId: process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_YEARLY_PRICE_ID ?? "",
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
23
packages/lib/stripe/fetchers/checkout-session.ts
Normal file
23
packages/lib/stripe/fetchers/checkout-session.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { CheckoutSessionRequest, CheckoutSessionResponse } from "../handlers/checkout-session"
|
||||
|
||||
export type FetchCheckoutSessionOptions = CheckoutSessionRequest['body']
|
||||
|
||||
export const fetchCheckoutSession = async ({
|
||||
id,
|
||||
priceId
|
||||
}: FetchCheckoutSessionOptions) => {
|
||||
const response = await fetch('/api/stripe/checkout-session', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id,
|
||||
priceId
|
||||
})
|
||||
});
|
||||
|
||||
const json: CheckoutSessionResponse = await response.json();
|
||||
|
||||
return json;
|
||||
}
|
||||
14
packages/lib/stripe/fetchers/get-subscription.ts
Normal file
14
packages/lib/stripe/fetchers/get-subscription.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { GetSubscriptionResponse } from "../handlers/get-subscription";
|
||||
|
||||
export const fetchSubscription = async () => {
|
||||
const response = await fetch("/api/stripe/subscription", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const json: GetSubscriptionResponse = await response.json();
|
||||
|
||||
return json;
|
||||
};
|
||||
19
packages/lib/stripe/fetchers/portal-session.ts
Normal file
19
packages/lib/stripe/fetchers/portal-session.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { PortalSessionRequest, PortalSessionResponse } from "../handlers/portal-session";
|
||||
|
||||
export type FetchPortalSessionOptions = PortalSessionRequest["body"];
|
||||
|
||||
export const fetchPortalSession = async ({ id }: FetchPortalSessionOptions) => {
|
||||
const response = await fetch("/api/stripe/portal-session", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id,
|
||||
}),
|
||||
});
|
||||
|
||||
const json: PortalSessionResponse = await response.json();
|
||||
|
||||
return json;
|
||||
};
|
||||
35
packages/lib/stripe/guards/subscriptions.ts
Normal file
35
packages/lib/stripe/guards/subscriptions.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { GetServerSideProps, GetServerSidePropsContext, NextApiRequest } from "next";
|
||||
import { SubscriptionStatus } from "@prisma/client";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
|
||||
export const isSubscriptionsEnabled = () => {
|
||||
return process.env.NEXT_PUBLIC_ALLOW_SUBSCRIPTIONS === "true";
|
||||
};
|
||||
|
||||
export const isSubscribedServer = async (
|
||||
req: NextApiRequest | GetServerSidePropsContext["req"]
|
||||
) => {
|
||||
const { default: prisma } = await import("@documenso/prisma");
|
||||
|
||||
if (!isSubscriptionsEnabled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const token = await getToken({
|
||||
req,
|
||||
});
|
||||
|
||||
if (!token || !token.email) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const subscription = await prisma.subscription.findFirst({
|
||||
where: {
|
||||
User: {
|
||||
email: token.email,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return subscription !== null && subscription.status !== SubscriptionStatus.INACTIVE;
|
||||
};
|
||||
92
packages/lib/stripe/handlers/checkout-session.ts
Normal file
92
packages/lib/stripe/handlers/checkout-session.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import prisma from "@documenso/prisma";
|
||||
import { stripe } from "../client";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
|
||||
export type CheckoutSessionRequest = {
|
||||
body: {
|
||||
id?: string;
|
||||
priceId: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type CheckoutSessionResponse =
|
||||
| {
|
||||
success: false;
|
||||
message: string;
|
||||
}
|
||||
| {
|
||||
success: true;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export const checkoutSessionHandler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (!process.env.NEXT_PUBLIC_ALLOW_SUBSCRIPTIONS) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "Subscriptions are not enabled",
|
||||
});
|
||||
}
|
||||
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({
|
||||
success: false,
|
||||
message: "Method not allowed",
|
||||
});
|
||||
}
|
||||
|
||||
const token = await getToken({
|
||||
req,
|
||||
});
|
||||
|
||||
if (!token || !token.email) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: "Unauthorized",
|
||||
});
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
email: token.email,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "No user found",
|
||||
});
|
||||
}
|
||||
|
||||
const { id, priceId } = req.body;
|
||||
|
||||
if (typeof priceId !== "string") {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "No id or priceId found in request",
|
||||
});
|
||||
}
|
||||
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
customer: id,
|
||||
customer_email: user.email,
|
||||
client_reference_id: String(user.id),
|
||||
payment_method_types: ["card"],
|
||||
line_items: [
|
||||
{
|
||||
price: priceId,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
mode: "subscription",
|
||||
allow_promotion_codes: true,
|
||||
success_url: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/settings/billing?success=true`,
|
||||
cancel_url: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/settings/billing?canceled=true`,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
url: session.url,
|
||||
});
|
||||
};
|
||||
63
packages/lib/stripe/handlers/get-subscription.ts
Normal file
63
packages/lib/stripe/handlers/get-subscription.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import prisma from "@documenso/prisma";
|
||||
import { Subscription } from "@prisma/client";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
|
||||
export type GetSubscriptionRequest = never;
|
||||
|
||||
export type GetSubscriptionResponse =
|
||||
| {
|
||||
success: false;
|
||||
message: string;
|
||||
}
|
||||
| {
|
||||
success: true;
|
||||
subscription: Subscription;
|
||||
};
|
||||
|
||||
export const getSubscriptionHandler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (!process.env.NEXT_PUBLIC_ALLOW_SUBSCRIPTIONS) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "Subscriptions are not enabled",
|
||||
});
|
||||
}
|
||||
|
||||
if (req.method !== "GET") {
|
||||
return res.status(405).json({
|
||||
success: false,
|
||||
message: "Method not allowed",
|
||||
});
|
||||
}
|
||||
|
||||
const token = await getToken({
|
||||
req,
|
||||
});
|
||||
|
||||
if (!token || !token.email) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: "Unauthorized",
|
||||
});
|
||||
}
|
||||
|
||||
const subscription = await prisma.subscription.findFirst({
|
||||
where: {
|
||||
User: {
|
||||
email: token.email,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!subscription) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "No subscription found",
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
subscription,
|
||||
});
|
||||
};
|
||||
54
packages/lib/stripe/handlers/portal-session.ts
Normal file
54
packages/lib/stripe/handlers/portal-session.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { stripe } from "../client";
|
||||
|
||||
|
||||
export type PortalSessionRequest = {
|
||||
body: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type PortalSessionResponse =
|
||||
| {
|
||||
success: false;
|
||||
message: string;
|
||||
}
|
||||
| {
|
||||
success: true;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export const portalSessionHandler = async (req: NextApiRequest, res: NextApiResponse<PortalSessionResponse>) => {
|
||||
if (!process.env.NEXT_PUBLIC_ALLOW_SUBSCRIPTIONS) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "Subscriptions are not enabled",
|
||||
});
|
||||
}
|
||||
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({
|
||||
success: false,
|
||||
message: "Method not allowed",
|
||||
});
|
||||
}
|
||||
|
||||
const { id } = req.body;
|
||||
|
||||
if (typeof id !== "string") {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "No id found in request",
|
||||
});
|
||||
}
|
||||
|
||||
const session = await stripe.billingPortal.sessions.create({
|
||||
customer: id,
|
||||
return_url: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/settings/billing`,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
url: session.url,
|
||||
});
|
||||
};
|
||||
164
packages/lib/stripe/handlers/webhook.ts
Normal file
164
packages/lib/stripe/handlers/webhook.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import prisma from "@documenso/prisma";
|
||||
import { stripe } from "../client";
|
||||
import { SubscriptionStatus } from "@prisma/client";
|
||||
import { buffer } from "micro";
|
||||
import Stripe from "stripe";
|
||||
|
||||
const log = (...args: any[]) => console.log("[stripe]", ...args);
|
||||
|
||||
export const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (!process.env.NEXT_PUBLIC_ALLOW_SUBSCRIPTIONS) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "Subscriptions are not enabled",
|
||||
});
|
||||
}
|
||||
|
||||
const sig =
|
||||
typeof req.headers["stripe-signature"] === "string" ? req.headers["stripe-signature"] : "";
|
||||
|
||||
if (!sig) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "No signature found in request",
|
||||
});
|
||||
}
|
||||
|
||||
log("constructing body...")
|
||||
const body = await buffer(req);
|
||||
log("constructed body")
|
||||
|
||||
const event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
|
||||
log("event-type:", event.type);
|
||||
|
||||
if (event.type === "checkout.session.completed") {
|
||||
const session = event.data.object as Stripe.Checkout.Session;
|
||||
|
||||
const subscription = await stripe.subscriptions.retrieve(session.subscription as string);
|
||||
|
||||
const customerId =
|
||||
typeof subscription.customer === "string" ? subscription.customer : subscription.customer?.id;
|
||||
|
||||
await prisma.subscription.upsert({
|
||||
where: {
|
||||
customerId,
|
||||
},
|
||||
create: {
|
||||
customerId,
|
||||
status: SubscriptionStatus.ACTIVE,
|
||||
planId: subscription.id,
|
||||
priceId: subscription.items.data[0].price.id,
|
||||
periodEnd: new Date(subscription.current_period_end * 1000),
|
||||
userId: Number(session.client_reference_id as string),
|
||||
},
|
||||
update: {
|
||||
customerId,
|
||||
status: SubscriptionStatus.ACTIVE,
|
||||
planId: subscription.id,
|
||||
priceId: subscription.items.data[0].price.id,
|
||||
periodEnd: new Date(subscription.current_period_end * 1000),
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
message: "Webhook received",
|
||||
});
|
||||
}
|
||||
|
||||
if (event.type === "invoice.payment_succeeded") {
|
||||
const invoice = event.data.object as Stripe.Invoice;
|
||||
|
||||
const customerId =
|
||||
typeof invoice.customer === "string" ? invoice.customer : invoice.customer?.id;
|
||||
|
||||
const subscription = await stripe.subscriptions.retrieve(invoice.subscription as string);
|
||||
|
||||
await prisma.subscription.update({
|
||||
where: {
|
||||
customerId,
|
||||
},
|
||||
data: {
|
||||
status: SubscriptionStatus.ACTIVE,
|
||||
planId: subscription.id,
|
||||
priceId: subscription.items.data[0].price.id,
|
||||
periodEnd: new Date(subscription.current_period_end * 1000),
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
message: "Webhook received",
|
||||
});
|
||||
}
|
||||
|
||||
if (event.type === "invoice.payment_failed") {
|
||||
const failedInvoice = event.data.object as Stripe.Invoice;
|
||||
|
||||
const customerId = failedInvoice.customer as string;
|
||||
|
||||
await prisma.subscription.update({
|
||||
where: {
|
||||
customerId,
|
||||
},
|
||||
data: {
|
||||
status: SubscriptionStatus.PAST_DUE,
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
message: "Webhook received",
|
||||
});
|
||||
}
|
||||
|
||||
if (event.type === "customer.subscription.updated") {
|
||||
const updatedSubscription = event.data.object as Stripe.Subscription;
|
||||
|
||||
const customerId = updatedSubscription.customer as string;
|
||||
|
||||
await prisma.subscription.update({
|
||||
where: {
|
||||
customerId,
|
||||
},
|
||||
data: {
|
||||
status: SubscriptionStatus.ACTIVE,
|
||||
planId: updatedSubscription.id,
|
||||
priceId: updatedSubscription.items.data[0].price.id,
|
||||
periodEnd: new Date(updatedSubscription.current_period_end * 1000),
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
message: "Webhook received",
|
||||
});
|
||||
}
|
||||
|
||||
if (event.type === "customer.subscription.deleted") {
|
||||
const deletedSubscription = event.data.object as Stripe.Subscription;
|
||||
|
||||
const customerId = deletedSubscription.customer as string;
|
||||
|
||||
await prisma.subscription.update({
|
||||
where: {
|
||||
customerId,
|
||||
},
|
||||
data: {
|
||||
status: SubscriptionStatus.INACTIVE,
|
||||
},
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
message: "Webhook received",
|
||||
});
|
||||
}
|
||||
|
||||
log("Unhandled webhook event", event.type);
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "Unhandled webhook event",
|
||||
});
|
||||
};
|
||||
6
packages/lib/stripe/index.ts
Normal file
6
packages/lib/stripe/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from './data/plans'
|
||||
export * from './fetchers/checkout-session'
|
||||
export * from './fetchers/get-subscription'
|
||||
export * from './fetchers/portal-session'
|
||||
export * from './guards/subscriptions'
|
||||
export * from './providers/subscription-provider'
|
||||
89
packages/lib/stripe/providers/subscription-provider.tsx
Normal file
89
packages/lib/stripe/providers/subscription-provider.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import React, { createContext, useContext, useEffect, useMemo, useState } from "react";
|
||||
import { fetchSubscription } from "../fetchers/get-subscription";
|
||||
import { Subscription, SubscriptionStatus } from "@prisma/client";
|
||||
import { useSession } from "next-auth/react";
|
||||
|
||||
|
||||
export type SubscriptionContextValue = {
|
||||
subscription: Subscription | null;
|
||||
hasSubscription: boolean;
|
||||
isLoading: boolean;
|
||||
};
|
||||
|
||||
const SubscriptionContext = createContext<SubscriptionContextValue>({
|
||||
subscription: null,
|
||||
hasSubscription: false,
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
export const useSubscription = () => {
|
||||
const context = useContext(SubscriptionContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error(`useSubscription must be used within a SubscriptionProvider`);
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
export interface SubscriptionProviderProps {
|
||||
children: React.ReactNode;
|
||||
initialSubscription?: Subscription;
|
||||
}
|
||||
|
||||
export const SubscriptionProvider = ({
|
||||
children,
|
||||
initialSubscription,
|
||||
}: SubscriptionProviderProps) => {
|
||||
const session = useSession();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [subscription, setSubscription] = useState<Subscription | null>(
|
||||
initialSubscription || null
|
||||
);
|
||||
|
||||
const hasSubscription = useMemo(() => {
|
||||
console.log({
|
||||
"process.env.NEXT_PUBLIC_ALLOW_SUBSCRIPTIONS": process.env.NEXT_PUBLIC_ALLOW_SUBSCRIPTIONS,
|
||||
enabled: process.env.NEXT_PUBLIC_ALLOW_SUBSCRIPTIONS === "true",
|
||||
"subscription.status": subscription?.status,
|
||||
"subscription.periodEnd": subscription?.periodEnd,
|
||||
});
|
||||
|
||||
if (process.env.NEXT_PUBLIC_ALLOW_SUBSCRIPTIONS === "true") {
|
||||
return (
|
||||
subscription?.status === SubscriptionStatus.ACTIVE &&
|
||||
!!subscription?.periodEnd &&
|
||||
new Date(subscription.periodEnd) > new Date()
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}, [subscription]);
|
||||
|
||||
useEffect(() => {
|
||||
if (process.env.NEXT_PUBLIC_ALLOW_SUBSCRIPTIONS === "true" && session.data) {
|
||||
setIsLoading(true);
|
||||
fetchSubscription().then((res) => {
|
||||
if (res.success) {
|
||||
setSubscription(res.subscription);
|
||||
} else {
|
||||
setSubscription(null);
|
||||
}
|
||||
|
||||
setIsLoading(false);
|
||||
});
|
||||
}
|
||||
}, [session.data]);
|
||||
|
||||
return (
|
||||
<SubscriptionContext.Provider
|
||||
value={{
|
||||
subscription,
|
||||
hasSubscription,
|
||||
isLoading,
|
||||
}}>
|
||||
{children}
|
||||
</SubscriptionContext.Provider>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user