mirror of
https://github.com/lukevella/rallly.git
synced 2025-04-30 02:36:30 +02:00
💸 Create abstraction for handling pricing (#1215)
This commit is contained in:
parent
299b33df62
commit
a5ee4fafe5
22 changed files with 302 additions and 68 deletions
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
|
@ -64,6 +64,7 @@ jobs:
|
||||||
- name: Set environment variables
|
- name: Set environment variables
|
||||||
run: |
|
run: |
|
||||||
echo "DATABASE_URL=postgresql://postgres:postgres@localhost:5450/rallly" >> $GITHUB_ENV
|
echo "DATABASE_URL=postgresql://postgres:postgres@localhost:5450/rallly" >> $GITHUB_ENV
|
||||||
|
echo "STRIPE_SECRET_KEY=${{ secrets.STRIPE_SECRET_KEY }}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Create production build
|
- name: Create production build
|
||||||
run: yarn turbo build:test --filter=@rallly/web
|
run: yarn turbo build:test --filter=@rallly/web
|
||||||
|
|
1
apps/landing/next-env.d.ts
vendored
1
apps/landing/next-env.d.ts
vendored
|
@ -1,5 +1,6 @@
|
||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
|
/// <reference types="next/navigation-types/compat/navigation" />
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
"@rallly/languages": "*",
|
"@rallly/languages": "*",
|
||||||
"@rallly/tailwind-config": "*",
|
"@rallly/tailwind-config": "*",
|
||||||
"@rallly/ui": "*",
|
"@rallly/ui": "*",
|
||||||
|
"@rallly/billing": "*",
|
||||||
"@svgr/webpack": "^6.5.1",
|
"@svgr/webpack": "^6.5.1",
|
||||||
"@vercel/analytics": "^0.1.8",
|
"@vercel/analytics": "^0.1.8",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
"pricingDescription": "Get started for free. No login required.",
|
"pricingDescription": "Get started for free. No login required.",
|
||||||
"freeForever": "free forever",
|
"freeForever": "free forever",
|
||||||
"planPro": "Pro",
|
"planPro": "Pro",
|
||||||
"annualBillingDescription": "per month, billed annually",
|
|
||||||
"monthlyBillingDescription": "per month",
|
"monthlyBillingDescription": "per month",
|
||||||
"upgrade": "Upgrade",
|
"upgrade": "Upgrade",
|
||||||
"faq": "Frequently Asked Questions",
|
"faq": "Frequently Asked Questions",
|
||||||
|
@ -28,5 +27,7 @@
|
||||||
"planFree": "Free",
|
"planFree": "Free",
|
||||||
"keepPollsIndefinitely": "Keep polls indefinitely",
|
"keepPollsIndefinitely": "Keep polls indefinitely",
|
||||||
"whenPollInactive": "When does a poll become inactive?",
|
"whenPollInactive": "When does a poll become inactive?",
|
||||||
"whenPollInactiveAnswer": "Polls become inactive when all date options are in the past AND the poll has not been accessed for over 30 days. Inactive polls are automatically deleted if you do not have a paid subscription."
|
"whenPollInactiveAnswer": "Polls become inactive when all date options are in the past AND the poll has not been accessed for over 30 days. Inactive polls are automatically deleted if you do not have a paid subscription.",
|
||||||
|
"discount": "Save {amount}",
|
||||||
|
"yearlyBillingDescription": "per year"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import type { PricingData } from "@rallly/billing";
|
||||||
|
import { getProPricing } from "@rallly/billing";
|
||||||
|
import { Badge } from "@rallly/ui/badge";
|
||||||
import {
|
import {
|
||||||
BillingPlan,
|
BillingPlan,
|
||||||
BillingPlanDescription,
|
BillingPlanDescription,
|
||||||
|
@ -11,6 +14,7 @@ import {
|
||||||
import { Button } from "@rallly/ui/button";
|
import { Button } from "@rallly/ui/button";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@rallly/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@rallly/ui/tabs";
|
||||||
import { TrendingUpIcon } from "lucide-react";
|
import { TrendingUpIcon } from "lucide-react";
|
||||||
|
import { GetStaticProps } from "next";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useTranslation } from "next-i18next";
|
import { useTranslation } from "next-i18next";
|
||||||
import { NextSeo } from "next-seo";
|
import { NextSeo } from "next-seo";
|
||||||
|
@ -22,9 +26,6 @@ import { linkToApp } from "@/lib/linkToApp";
|
||||||
import { NextPageWithLayout } from "@/types";
|
import { NextPageWithLayout } from "@/types";
|
||||||
import { getStaticTranslations } from "@/utils/page-translations";
|
import { getStaticTranslations } from "@/utils/page-translations";
|
||||||
|
|
||||||
const monthlyPriceUsd = 7;
|
|
||||||
const annualPriceUsd = 42;
|
|
||||||
|
|
||||||
export const UpgradeButton = ({
|
export const UpgradeButton = ({
|
||||||
children,
|
children,
|
||||||
annual,
|
annual,
|
||||||
|
@ -48,7 +49,7 @@ export const UpgradeButton = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const PriceTables = () => {
|
const PriceTables = ({ pricingData }: { pricingData: PricingData }) => {
|
||||||
const [tab, setTab] = React.useState("yearly");
|
const [tab, setTab] = React.useState("yearly");
|
||||||
return (
|
return (
|
||||||
<Tabs value={tab} onValueChange={setTab}>
|
<Tabs value={tab} onValueChange={setTab}>
|
||||||
|
@ -57,8 +58,17 @@ const PriceTables = () => {
|
||||||
<TabsTrigger value="monthly">
|
<TabsTrigger value="monthly">
|
||||||
<Trans i18nKey="pricing:billingPeriodMonthly" defaults="Monthly" />
|
<Trans i18nKey="pricing:billingPeriodMonthly" defaults="Monthly" />
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="yearly">
|
<TabsTrigger value="yearly" className="inline-flex gap-x-2.5">
|
||||||
<Trans i18nKey="pricing:billingPeriodYearly" defaults="Yearly" />
|
<Trans i18nKey="pricing:billingPeriodYearly" defaults="Yearly" />
|
||||||
|
<Badge variant="green" className="inline-flex gap-2">
|
||||||
|
<Trans
|
||||||
|
i18nKey="pricing:discount"
|
||||||
|
defaults="Save {amount}"
|
||||||
|
values={{
|
||||||
|
amount: `$${(pricingData.monthly.amount * 12 - pricingData.yearly.amount) / 100}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Badge>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</div>
|
</div>
|
||||||
|
@ -115,18 +125,20 @@ const PriceTables = () => {
|
||||||
</BillingPlanDescription>
|
</BillingPlanDescription>
|
||||||
</BillingPlanHeader>
|
</BillingPlanHeader>
|
||||||
<TabsContent value="yearly">
|
<TabsContent value="yearly">
|
||||||
<BillingPlanPrice discount={`$${(annualPriceUsd / 12).toFixed(2)}`}>
|
<BillingPlanPrice>
|
||||||
${monthlyPriceUsd}
|
${pricingData.yearly.amount / 100}
|
||||||
</BillingPlanPrice>
|
</BillingPlanPrice>
|
||||||
<BillingPlanPeriod>
|
<BillingPlanPeriod>
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="pricing:annualBillingDescription"
|
i18nKey="pricing:yearlyBillingDescription"
|
||||||
defaults="per month, billed annually"
|
defaults="per year"
|
||||||
/>
|
/>
|
||||||
</BillingPlanPeriod>
|
</BillingPlanPeriod>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="monthly">
|
<TabsContent value="monthly">
|
||||||
<BillingPlanPrice>${monthlyPriceUsd}</BillingPlanPrice>
|
<BillingPlanPrice>
|
||||||
|
${pricingData.monthly.amount / 100}
|
||||||
|
</BillingPlanPrice>
|
||||||
<BillingPlanPeriod>
|
<BillingPlanPeriod>
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="pricing:monthlyBillingDescription"
|
i18nKey="pricing:monthlyBillingDescription"
|
||||||
|
@ -266,7 +278,9 @@ const FAQ = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Page: NextPageWithLayout = () => {
|
const Page: NextPageWithLayout<{ pricingData: PricingData }> = ({
|
||||||
|
pricingData,
|
||||||
|
}) => {
|
||||||
const { t } = useTranslation(["pricing"]);
|
const { t } = useTranslation(["pricing"]);
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto max-w-3xl">
|
<div className="mx-auto max-w-3xl">
|
||||||
|
@ -286,7 +300,7 @@ const Page: NextPageWithLayout = () => {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4 sm:space-y-6">
|
<div className="space-y-4 sm:space-y-6">
|
||||||
<PriceTables />
|
<PriceTables pricingData={pricingData} />
|
||||||
<div className="rounded-md border bg-gradient-to-b from-cyan-50 to-cyan-50/60 px-5 py-4 text-cyan-800">
|
<div className="rounded-md border bg-gradient-to-b from-cyan-50 to-cyan-50/60 px-5 py-4 text-cyan-800">
|
||||||
<div className="mb-2">
|
<div className="mb-2">
|
||||||
<TrendingUpIcon className="text-indigo mr-2 mt-0.5 size-6 shrink-0" />
|
<TrendingUpIcon className="text-indigo mr-2 mt-0.5 size-6 shrink-0" />
|
||||||
|
@ -317,4 +331,16 @@ Page.getLayout = getPageLayout;
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
|
|
||||||
export const getStaticProps = getStaticTranslations(["pricing"]);
|
export const getStaticProps: GetStaticProps = async (ctx) => {
|
||||||
|
const pricingData = await getProPricing();
|
||||||
|
const res = await getStaticTranslations(["pricing"])(ctx);
|
||||||
|
if ("props" in res) {
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
...res.props,
|
||||||
|
pricingData,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
|
@ -3,11 +3,23 @@
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"],
|
"@/*": [
|
||||||
"~/*": ["public/*"],
|
"src/*"
|
||||||
|
],
|
||||||
|
"~/*": [
|
||||||
|
"public/*"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"checkJs": false,
|
"checkJs": false,
|
||||||
|
"strictNullChecks": true
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": [
|
||||||
"exclude": ["node_modules"],
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
"@rallly/backend": "*",
|
"@rallly/backend": "*",
|
||||||
"@rallly/database": "*",
|
"@rallly/database": "*",
|
||||||
"@rallly/icons": "*",
|
"@rallly/icons": "*",
|
||||||
|
"@rallly/billing": "*",
|
||||||
"@rallly/languages": "*",
|
"@rallly/languages": "*",
|
||||||
"@rallly/tailwind-config": "*",
|
"@rallly/tailwind-config": "*",
|
||||||
"@rallly/ui": "*",
|
"@rallly/ui": "*",
|
||||||
|
|
|
@ -139,7 +139,6 @@
|
||||||
"billingStatus": "Billing Status",
|
"billingStatus": "Billing Status",
|
||||||
"billingStatusDescription": "Manage your subscription and billing details",
|
"billingStatusDescription": "Manage your subscription and billing details",
|
||||||
"freeForever": "free forever",
|
"freeForever": "free forever",
|
||||||
"annualBillingDescription": "per month, billed annually",
|
|
||||||
"billingStatusState": "Status",
|
"billingStatusState": "Status",
|
||||||
"billingStatusActive": "Active",
|
"billingStatusActive": "Active",
|
||||||
"billingStatusPaused": "Paused",
|
"billingStatusPaused": "Paused",
|
||||||
|
@ -228,7 +227,6 @@
|
||||||
"autoTimeZoneHelp": "Enable this setting to automatically adjust event times to each participant's local time zone.",
|
"autoTimeZoneHelp": "Enable this setting to automatically adjust event times to each participant's local time zone.",
|
||||||
"commentsDisabled": "Comments have been disabled",
|
"commentsDisabled": "Comments have been disabled",
|
||||||
"allParticipants": "All Participants",
|
"allParticipants": "All Participants",
|
||||||
"pollsListAll": "All",
|
|
||||||
"noParticipantsDescription": "Click <b>Share</b> to invite participants",
|
"noParticipantsDescription": "Click <b>Share</b> to invite participants",
|
||||||
"timeShownIn": "Times shown in {timeZone}",
|
"timeShownIn": "Times shown in {timeZone}",
|
||||||
"pollStatusPausedDescription": "Votes cannot be submitted or edited at this time",
|
"pollStatusPausedDescription": "Votes cannot be submitted or edited at this time",
|
||||||
|
@ -263,5 +261,7 @@
|
||||||
"pastEventsEmptyStateTitle": "No Past Events",
|
"pastEventsEmptyStateTitle": "No Past Events",
|
||||||
"pastEventsEmptyStateDescription": "When you schedule events, they will appear here.",
|
"pastEventsEmptyStateDescription": "When you schedule events, they will appear here.",
|
||||||
"activePollCount": "{{activePollCount}} Live",
|
"activePollCount": "{{activePollCount}} Live",
|
||||||
"createPoll": "Create poll"
|
"createPoll": "Create poll",
|
||||||
|
"yearlyDiscount": "Save {amount}",
|
||||||
|
"yearlyBillingDescription": "per year"
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,9 @@ import { Card } from "@rallly/ui/card";
|
||||||
import { Label } from "@rallly/ui/label";
|
import { Label } from "@rallly/ui/label";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { ArrowUpRight, CreditCardIcon, SendIcon } from "lucide-react";
|
import { ArrowUpRight, CreditCardIcon, SendIcon } from "lucide-react";
|
||||||
import Head from "next/head";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Script from "next/script";
|
import Script from "next/script";
|
||||||
import { useTranslation } from "next-i18next";
|
|
||||||
|
|
||||||
import { BillingPlans } from "@/components/billing/billing-plans";
|
|
||||||
import {
|
import {
|
||||||
Settings,
|
Settings,
|
||||||
SettingsContent,
|
SettingsContent,
|
||||||
|
@ -19,6 +16,8 @@ import { Trans } from "@/components/trans";
|
||||||
import { useSubscription } from "@/contexts/plan";
|
import { useSubscription } from "@/contexts/plan";
|
||||||
import { trpc } from "@/utils/trpc/client";
|
import { trpc } from "@/utils/trpc/client";
|
||||||
|
|
||||||
|
import { BillingPlans, PricingData } from "./billing-plans";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
@ -57,7 +56,7 @@ const BillingPortal = () => {
|
||||||
|
|
||||||
const proPlanIdMonthly = process.env.NEXT_PUBLIC_PRO_PLAN_ID_MONTHLY as string;
|
const proPlanIdMonthly = process.env.NEXT_PUBLIC_PRO_PLAN_ID_MONTHLY as string;
|
||||||
|
|
||||||
const SubscriptionStatus = () => {
|
const SubscriptionStatus = ({ pricingData }: { pricingData: PricingData }) => {
|
||||||
const data = useSubscription();
|
const data = useSubscription();
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
@ -67,7 +66,7 @@ const SubscriptionStatus = () => {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{!data.active ? (
|
{!data.active ? (
|
||||||
<BillingPlans />
|
<BillingPlans pricingData={pricingData} />
|
||||||
) : data.legacy ? (
|
) : data.legacy ? (
|
||||||
<LegacyBilling />
|
<LegacyBilling />
|
||||||
) : (
|
) : (
|
||||||
|
@ -238,16 +237,11 @@ const LegacyBilling = () => {
|
||||||
</div>
|
</div>
|
||||||
</SettingsSection>;
|
</SettingsSection>;
|
||||||
|
|
||||||
export function BillingPage() {
|
export function BillingPage({ pricingData }: { pricingData: PricingData }) {
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Settings>
|
<Settings>
|
||||||
<Head>
|
|
||||||
<title>{t("billing")}</title>
|
|
||||||
</Head>
|
|
||||||
<SettingsContent>
|
<SettingsContent>
|
||||||
<SubscriptionStatus />
|
<SubscriptionStatus pricingData={pricingData} />
|
||||||
<hr />
|
<hr />
|
||||||
<SettingsSection
|
<SettingsSection
|
||||||
title={<Trans i18nKey="support" defaults="Support" />}
|
title={<Trans i18nKey="support" defaults="Support" />}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { Badge } from "@rallly/ui/badge";
|
||||||
import {
|
import {
|
||||||
BillingPlan,
|
BillingPlan,
|
||||||
BillingPlanDescription,
|
BillingPlanDescription,
|
||||||
|
@ -15,9 +16,21 @@ import React from "react";
|
||||||
|
|
||||||
import { Trans } from "@/components/trans";
|
import { Trans } from "@/components/trans";
|
||||||
import { UpgradeButton } from "@/components/upgrade-button";
|
import { UpgradeButton } from "@/components/upgrade-button";
|
||||||
import { annualPriceUsd, monthlyPriceUsd } from "@/utils/constants";
|
|
||||||
|
|
||||||
export const BillingPlans = () => {
|
export type PricingData = {
|
||||||
|
monthly: {
|
||||||
|
id: string;
|
||||||
|
amount: number;
|
||||||
|
currency: string;
|
||||||
|
};
|
||||||
|
yearly: {
|
||||||
|
id: string;
|
||||||
|
amount: number;
|
||||||
|
currency: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BillingPlans = ({ pricingData }: { pricingData: PricingData }) => {
|
||||||
const [tab, setTab] = React.useState("yearly");
|
const [tab, setTab] = React.useState("yearly");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -28,8 +41,21 @@ export const BillingPlans = () => {
|
||||||
<TabsTrigger value="monthly">
|
<TabsTrigger value="monthly">
|
||||||
<Trans i18nKey="billingPeriodMonthly" />
|
<Trans i18nKey="billingPeriodMonthly" />
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="yearly">
|
<TabsTrigger value="yearly" className="inline-flex gap-x-2.5">
|
||||||
<Trans i18nKey="billingPeriodYearly" />
|
<Trans i18nKey="billingPeriodYearly" />
|
||||||
|
<Badge variant="green">
|
||||||
|
<Trans
|
||||||
|
i18nKey="yearlyDiscount"
|
||||||
|
defaults="Save {amount}"
|
||||||
|
values={{
|
||||||
|
amount: `$${
|
||||||
|
(pricingData.monthly.amount * 12 -
|
||||||
|
pricingData.yearly.amount) /
|
||||||
|
100
|
||||||
|
}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Badge>
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</div>
|
</div>
|
||||||
|
@ -85,20 +111,20 @@ export const BillingPlans = () => {
|
||||||
</div>
|
</div>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<TabsContent value="yearly">
|
<TabsContent value="yearly">
|
||||||
<BillingPlanPrice
|
<BillingPlanPrice>
|
||||||
discount={`$${(annualPriceUsd / 12).toFixed(2)}`}
|
${pricingData.yearly.amount / 100}
|
||||||
>
|
|
||||||
${monthlyPriceUsd}
|
|
||||||
</BillingPlanPrice>
|
</BillingPlanPrice>
|
||||||
<BillingPlanPeriod>
|
<BillingPlanPeriod>
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="annualBillingDescription"
|
i18nKey="yearlyBillingDescription"
|
||||||
defaults="per month, billed annually"
|
defaults="per year"
|
||||||
/>
|
/>
|
||||||
</BillingPlanPeriod>
|
</BillingPlanPeriod>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="monthly">
|
<TabsContent value="monthly">
|
||||||
<BillingPlanPrice>${monthlyPriceUsd}</BillingPlanPrice>
|
<BillingPlanPrice>
|
||||||
|
${pricingData.monthly.amount / 100}
|
||||||
|
</BillingPlanPrice>
|
||||||
<BillingPlanPeriod>
|
<BillingPlanPeriod>
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="monthlyBillingDescription"
|
i18nKey="monthlyBillingDescription"
|
|
@ -1,10 +1,17 @@
|
||||||
|
import { getProPricing } from "@rallly/billing";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
|
||||||
|
import { BillingPage } from "@/app/[locale]/(admin)/settings/billing/billing-page";
|
||||||
import { Params } from "@/app/[locale]/types";
|
import { Params } from "@/app/[locale]/types";
|
||||||
import { getTranslation } from "@/app/i18n";
|
import { getTranslation } from "@/app/i18n";
|
||||||
|
import { env } from "@/env";
|
||||||
import { BillingPage } from "./billing-page";
|
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
return <BillingPage />;
|
if (env.NEXT_PUBLIC_SELF_HOSTED === "true") {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
const prices = await getProPricing();
|
||||||
|
return <BillingPage pricingData={prices} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generateMetadata({ params }: { params: Params }) {
|
export async function generateMetadata({ params }: { params: Params }) {
|
||||||
|
|
|
@ -58,6 +58,7 @@ export const env = createEnv({
|
||||||
client: {
|
client: {
|
||||||
NEXT_PUBLIC_POSTHOG_API_KEY: z.string().optional(),
|
NEXT_PUBLIC_POSTHOG_API_KEY: z.string().optional(),
|
||||||
NEXT_PUBLIC_POSTHOG_API_HOST: z.string().url().optional(),
|
NEXT_PUBLIC_POSTHOG_API_HOST: z.string().url().optional(),
|
||||||
|
NEXT_PUBLIC_SELF_HOSTED: z.enum(["true", "false"]).optional(),
|
||||||
},
|
},
|
||||||
/*
|
/*
|
||||||
* Due to how Next.js bundles environment variables on Edge and Client,
|
* Due to how Next.js bundles environment variables on Edge and Client,
|
||||||
|
@ -88,6 +89,7 @@ export const env = createEnv({
|
||||||
ALLOWED_EMAILS: process.env.ALLOWED_EMAILS,
|
ALLOWED_EMAILS: process.env.ALLOWED_EMAILS,
|
||||||
NEXT_PUBLIC_POSTHOG_API_KEY: process.env.NEXT_PUBLIC_POSTHOG_API_KEY,
|
NEXT_PUBLIC_POSTHOG_API_KEY: process.env.NEXT_PUBLIC_POSTHOG_API_KEY,
|
||||||
NEXT_PUBLIC_POSTHOG_API_HOST: process.env.NEXT_PUBLIC_POSTHOG_API_HOST,
|
NEXT_PUBLIC_POSTHOG_API_HOST: process.env.NEXT_PUBLIC_POSTHOG_API_HOST,
|
||||||
|
NEXT_PUBLIC_SELF_HOSTED: process.env.NEXT_PUBLIC_SELF_HOSTED,
|
||||||
},
|
},
|
||||||
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
|
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { stripe } from "@rallly/backend/stripe";
|
import { getProPricing, stripe } from "@rallly/billing";
|
||||||
import { prisma } from "@rallly/database";
|
import { prisma } from "@rallly/database";
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
@ -61,6 +61,8 @@ export default async function handler(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const proPricingData = await getProPricing();
|
||||||
|
|
||||||
const session = await stripe.checkout.sessions.create({
|
const session = await stripe.checkout.sessions.create({
|
||||||
success_url: absoluteUrl(
|
success_url: absoluteUrl(
|
||||||
return_path ?? "/api/stripe/portal?session_id={CHECKOUT_SESSION_ID}",
|
return_path ?? "/api/stripe/portal?session_id={CHECKOUT_SESSION_ID}",
|
||||||
|
@ -97,8 +99,8 @@ export default async function handler(
|
||||||
{
|
{
|
||||||
price:
|
price:
|
||||||
period === "yearly"
|
period === "yearly"
|
||||||
? (process.env.STRIPE_YEARLY_PRICE as string)
|
? proPricingData.yearly.id
|
||||||
: (process.env.STRIPE_MONTHLY_PRICE as string),
|
: proPricingData.monthly.id,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -8,7 +8,4 @@ export const isSelfHosted = process.env.NEXT_PUBLIC_SELF_HOSTED === "true";
|
||||||
|
|
||||||
export const isFeedbackEnabled = false;
|
export const isFeedbackEnabled = false;
|
||||||
|
|
||||||
export const monthlyPriceUsd = 7;
|
|
||||||
|
|
||||||
export const annualPriceUsd = 42;
|
|
||||||
export const appVersion = process.env.NEXT_PUBLIC_APP_VERSION;
|
export const appVersion = process.env.NEXT_PUBLIC_APP_VERSION;
|
||||||
|
|
16
packages/billing/package.json
Normal file
16
packages/billing/package.json
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "@rallly/billing",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"exports": {
|
||||||
|
"./server/*": "./src/server/*.tsx",
|
||||||
|
"./next": "./src/next/index.ts",
|
||||||
|
".": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@rallly/ui": "*",
|
||||||
|
"stripe": "^13.2.0",
|
||||||
|
"@radix-ui/react-radio-group": "^1.2.0",
|
||||||
|
"next": "*"
|
||||||
|
}
|
||||||
|
}
|
1
packages/billing/src/index.ts
Normal file
1
packages/billing/src/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from "./lib/stripe";
|
20
packages/billing/src/lib/get-pricing.ts
Normal file
20
packages/billing/src/lib/get-pricing.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { stripe } from "..";
|
||||||
|
|
||||||
|
export async function getPricing() {
|
||||||
|
const prices = await stripe.prices.list({
|
||||||
|
lookup_keys: ["pro-monthly", "pro-yearly"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const [monthly, yearly] = prices.data;
|
||||||
|
|
||||||
|
return {
|
||||||
|
monthly: {
|
||||||
|
currency: monthly.currency,
|
||||||
|
price: monthly.unit_amount_decimal,
|
||||||
|
},
|
||||||
|
yearly: {
|
||||||
|
currency: yearly.currency,
|
||||||
|
price: yearly.unit_amount,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
35
packages/billing/src/lib/stripe.ts
Normal file
35
packages/billing/src/lib/stripe.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import Stripe from "stripe";
|
||||||
|
|
||||||
|
export type { Stripe } from "stripe";
|
||||||
|
|
||||||
|
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, {
|
||||||
|
apiVersion: "2023-08-16",
|
||||||
|
typescript: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function getProPricing() {
|
||||||
|
const prices = await stripe.prices.list({
|
||||||
|
lookup_keys: ["pro-monthly", "pro-yearly"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const [monthly, yearly] = prices.data;
|
||||||
|
|
||||||
|
if (monthly.unit_amount === null || yearly.unit_amount === null) {
|
||||||
|
throw new Error("Price not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
monthly: {
|
||||||
|
id: monthly.id,
|
||||||
|
amount: monthly.unit_amount,
|
||||||
|
currency: monthly.currency,
|
||||||
|
},
|
||||||
|
yearly: {
|
||||||
|
id: yearly.id,
|
||||||
|
amount: yearly.unit_amount,
|
||||||
|
currency: yearly.currency,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PricingData = Awaited<ReturnType<typeof getProPricing>>;
|
23
packages/billing/src/next/index.ts
Normal file
23
packages/billing/src/next/index.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { getPricing } from "../lib/get-pricing";
|
||||||
|
|
||||||
|
export async function GET(
|
||||||
|
request: NextRequest,
|
||||||
|
{
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: {
|
||||||
|
method: string;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
switch (params.method) {
|
||||||
|
case "pricing":
|
||||||
|
const data = await getPricing();
|
||||||
|
return NextResponse.json(data);
|
||||||
|
default:
|
||||||
|
return NextResponse.json({ message: "Method not found" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handlers = { GET };
|
5
packages/billing/tsconfig.json
Normal file
5
packages/billing/tsconfig.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"extends": "@rallly/tsconfig/next.json",
|
||||||
|
"include": ["**/*.ts", "**/*.tsx", "**/*.js"],
|
||||||
|
"exclude": ["node_modules"],
|
||||||
|
}
|
|
@ -37,20 +37,10 @@ export const BillingPlanDescription = ({
|
||||||
|
|
||||||
export const BillingPlanPrice = ({
|
export const BillingPlanPrice = ({
|
||||||
children,
|
children,
|
||||||
discount,
|
|
||||||
}: React.PropsWithChildren<{ discount?: React.ReactNode }>) => {
|
}: React.PropsWithChildren<{ discount?: React.ReactNode }>) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="flex items-center gap-4">
|
||||||
{discount ? (
|
<span className="text-3xl font-bold">{children}</span>
|
||||||
<>
|
|
||||||
<span className="mr-2 text-xl font-bold line-through">
|
|
||||||
{children}
|
|
||||||
</span>
|
|
||||||
<span className="text-3xl font-bold">{discount}</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<span className="text-3xl font-bold">{children}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
79
yarn.lock
79
yarn.lock
|
@ -2293,6 +2293,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.0.5-canary.46.tgz#b9b597baaba77a2836eaf836712a6e0afed1ca2d"
|
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.0.5-canary.46.tgz#b9b597baaba77a2836eaf836712a6e0afed1ca2d"
|
||||||
integrity sha512-dvNzrArTfe3VY1VIscpb3E2e7SZ1qwFe82WGzpOVbxilT3JcsnVGYF/uq8Jj1qKWPI5C/aePNXwA97JRNAXpRQ==
|
integrity sha512-dvNzrArTfe3VY1VIscpb3E2e7SZ1qwFe82WGzpOVbxilT3JcsnVGYF/uq8Jj1qKWPI5C/aePNXwA97JRNAXpRQ==
|
||||||
|
|
||||||
|
"@next/env@14.1.0":
|
||||||
|
version "14.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.1.0.tgz#43d92ebb53bc0ae43dcc64fb4d418f8f17d7a341"
|
||||||
|
integrity sha512-Py8zIo+02ht82brwwhTg36iogzFqGLPXlRGKQw5s+qP/kMNc4MAyDeEwBKDijk6zTIbegEgu8Qy7C1LboslQAw==
|
||||||
|
|
||||||
"@next/env@14.2.4":
|
"@next/env@14.2.4":
|
||||||
version "14.2.4"
|
version "14.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.4.tgz#5546813dc4f809884a37d257b254a5ce1b0248d7"
|
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.4.tgz#5546813dc4f809884a37d257b254a5ce1b0248d7"
|
||||||
|
@ -2310,6 +2315,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.5-canary.46.tgz#94c67fa212614892f94db120c92a9f4207da13b8"
|
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.5-canary.46.tgz#94c67fa212614892f94db120c92a9f4207da13b8"
|
||||||
integrity sha512-7Bq9rjWl4sq70Zkn6h6mn8/tgYTH2SQ8lIm8b/j1MAnTiJYyVBLapu//gT/cgtqx6y8SwSc2JNviBue35zeCNw==
|
integrity sha512-7Bq9rjWl4sq70Zkn6h6mn8/tgYTH2SQ8lIm8b/j1MAnTiJYyVBLapu//gT/cgtqx6y8SwSc2JNviBue35zeCNw==
|
||||||
|
|
||||||
|
"@next/swc-darwin-arm64@14.1.0":
|
||||||
|
version "14.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.0.tgz#70a57c87ab1ae5aa963a3ba0f4e59e18f4ecea39"
|
||||||
|
integrity sha512-nUDn7TOGcIeyQni6lZHfzNoo9S0euXnu0jhsbMOmMJUBfgsnESdjN97kM7cBqQxZa8L/bM9om/S5/1dzCrW6wQ==
|
||||||
|
|
||||||
"@next/swc-darwin-arm64@14.2.4":
|
"@next/swc-darwin-arm64@14.2.4":
|
||||||
version "14.2.4"
|
version "14.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.4.tgz#da9f04c34a3d5f0b8401ed745768420e4a604036"
|
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.4.tgz#da9f04c34a3d5f0b8401ed745768420e4a604036"
|
||||||
|
@ -2320,6 +2330,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.5-canary.46.tgz#25e2a5acfc5b20d3a25ad6adcfbfc91aaa44d79f"
|
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.5-canary.46.tgz#25e2a5acfc5b20d3a25ad6adcfbfc91aaa44d79f"
|
||||||
integrity sha512-3oI8rDVBZsfkTdqXwtRjxA85o0RIjZv9uuOLohfaIuFP3oZnCM0dRZREP2umYcFQRxdavW+TDJzYcqzKxYTujA==
|
integrity sha512-3oI8rDVBZsfkTdqXwtRjxA85o0RIjZv9uuOLohfaIuFP3oZnCM0dRZREP2umYcFQRxdavW+TDJzYcqzKxYTujA==
|
||||||
|
|
||||||
|
"@next/swc-darwin-x64@14.1.0":
|
||||||
|
version "14.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.0.tgz#0863a22feae1540e83c249384b539069fef054e9"
|
||||||
|
integrity sha512-1jgudN5haWxiAl3O1ljUS2GfupPmcftu2RYJqZiMJmmbBT5M1XDffjUtRUzP4W3cBHsrvkfOFdQ71hAreNQP6g==
|
||||||
|
|
||||||
"@next/swc-darwin-x64@14.2.4":
|
"@next/swc-darwin-x64@14.2.4":
|
||||||
version "14.2.4"
|
version "14.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.4.tgz#46dedb29ec5503bf171a72a3ecb8aac6e738e9d6"
|
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.4.tgz#46dedb29ec5503bf171a72a3ecb8aac6e738e9d6"
|
||||||
|
@ -2330,6 +2345,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.5-canary.46.tgz#00fad5be6cada895e513d81427c462c92abdae3f"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.5-canary.46.tgz#00fad5be6cada895e513d81427c462c92abdae3f"
|
||||||
integrity sha512-gXSS328bUWxBwQfeDFROOzFSzzoyX1075JxOeArLl63sV59cbnRrwHHhD4CWG1bYYzcHxHfVugZgvyCucaHCIw==
|
integrity sha512-gXSS328bUWxBwQfeDFROOzFSzzoyX1075JxOeArLl63sV59cbnRrwHHhD4CWG1bYYzcHxHfVugZgvyCucaHCIw==
|
||||||
|
|
||||||
|
"@next/swc-linux-arm64-gnu@14.1.0":
|
||||||
|
version "14.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.0.tgz#893da533d3fce4aec7116fe772d4f9b95232423c"
|
||||||
|
integrity sha512-RHo7Tcj+jllXUbK7xk2NyIDod3YcCPDZxj1WLIYxd709BQ7WuRYl3OWUNG+WUfqeQBds6kvZYlc42NJJTNi4tQ==
|
||||||
|
|
||||||
"@next/swc-linux-arm64-gnu@14.2.4":
|
"@next/swc-linux-arm64-gnu@14.2.4":
|
||||||
version "14.2.4"
|
version "14.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.4.tgz#c9697ab9eb422bd1d7ffd0eb0779cc2aefa9d4a1"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.4.tgz#c9697ab9eb422bd1d7ffd0eb0779cc2aefa9d4a1"
|
||||||
|
@ -2340,6 +2360,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.5-canary.46.tgz#a931a1312d3f5e66ea59c4b23e0ae90721f8e252"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.5-canary.46.tgz#a931a1312d3f5e66ea59c4b23e0ae90721f8e252"
|
||||||
integrity sha512-7QkBRKlDsjaWGbfIKh6qJK0HiHJISNGoKpwFTcnZvlhAEaydS5Hmu0zh64kbLRlzwXtkpj6/iCwjrWnHes59aA==
|
integrity sha512-7QkBRKlDsjaWGbfIKh6qJK0HiHJISNGoKpwFTcnZvlhAEaydS5Hmu0zh64kbLRlzwXtkpj6/iCwjrWnHes59aA==
|
||||||
|
|
||||||
|
"@next/swc-linux-arm64-musl@14.1.0":
|
||||||
|
version "14.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.0.tgz#d81ddcf95916310b8b0e4ad32b637406564244c0"
|
||||||
|
integrity sha512-v6kP8sHYxjO8RwHmWMJSq7VZP2nYCkRVQ0qolh2l6xroe9QjbgV8siTbduED4u0hlk0+tjS6/Tuy4n5XCp+l6g==
|
||||||
|
|
||||||
"@next/swc-linux-arm64-musl@14.2.4":
|
"@next/swc-linux-arm64-musl@14.2.4":
|
||||||
version "14.2.4"
|
version "14.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.4.tgz#cbbceb2008571c743b5a310a488d2e166d200a75"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.4.tgz#cbbceb2008571c743b5a310a488d2e166d200a75"
|
||||||
|
@ -2350,6 +2375,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.5-canary.46.tgz#32bf69fa93975ca3fef141121eaa8a1a67086694"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.5-canary.46.tgz#32bf69fa93975ca3fef141121eaa8a1a67086694"
|
||||||
integrity sha512-DS5wTjw3FtcLFVzRxLMJgmDNMoeaXp5qBdKUSBrKTq4zQnqUi99CGz2461DlUSxJCWPUgAVo23MdoQD6Siuk7A==
|
integrity sha512-DS5wTjw3FtcLFVzRxLMJgmDNMoeaXp5qBdKUSBrKTq4zQnqUi99CGz2461DlUSxJCWPUgAVo23MdoQD6Siuk7A==
|
||||||
|
|
||||||
|
"@next/swc-linux-x64-gnu@14.1.0":
|
||||||
|
version "14.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.0.tgz#18967f100ec19938354332dcb0268393cbacf581"
|
||||||
|
integrity sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ==
|
||||||
|
|
||||||
"@next/swc-linux-x64-gnu@14.2.4":
|
"@next/swc-linux-x64-gnu@14.2.4":
|
||||||
version "14.2.4"
|
version "14.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.4.tgz#d79184223f857bacffb92f643cb2943a43632568"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.4.tgz#d79184223f857bacffb92f643cb2943a43632568"
|
||||||
|
@ -2360,6 +2390,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.5-canary.46.tgz#59f221d83096b0362849fabbcda1fdc1671cf6b1"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.5-canary.46.tgz#59f221d83096b0362849fabbcda1fdc1671cf6b1"
|
||||||
integrity sha512-d409ur5JGj6HFp8DBu5M2oTh5EddDcrT+vjewQkAq/A7MZoAMAOH74xOFouEnJs0/dQ71XvH9Lw+1gJSnElcyQ==
|
integrity sha512-d409ur5JGj6HFp8DBu5M2oTh5EddDcrT+vjewQkAq/A7MZoAMAOH74xOFouEnJs0/dQ71XvH9Lw+1gJSnElcyQ==
|
||||||
|
|
||||||
|
"@next/swc-linux-x64-musl@14.1.0":
|
||||||
|
version "14.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.0.tgz#77077cd4ba8dda8f349dc7ceb6230e68ee3293cf"
|
||||||
|
integrity sha512-rbaIYFt2X9YZBSbH/CwGAjbBG2/MrACCVu2X0+kSykHzHnYH5FjHxwXLkcoJ10cX0aWCEynpu+rP76x0914atg==
|
||||||
|
|
||||||
"@next/swc-linux-x64-musl@14.2.4":
|
"@next/swc-linux-x64-musl@14.2.4":
|
||||||
version "14.2.4"
|
version "14.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.4.tgz#6b6c3e5ac02ca5e63394d280ec8ee607491902df"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.4.tgz#6b6c3e5ac02ca5e63394d280ec8ee607491902df"
|
||||||
|
@ -2370,6 +2405,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.5-canary.46.tgz#465d24227cd1b8840b85ee488327725e478da221"
|
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.5-canary.46.tgz#465d24227cd1b8840b85ee488327725e478da221"
|
||||||
integrity sha512-goyh/RCFtivflIOvbwircMxTSObETufm3pcxtI8rIz9+pg/M2MmK8/z48EZybkEcPKl41xu4s1iqXThy/jDPng==
|
integrity sha512-goyh/RCFtivflIOvbwircMxTSObETufm3pcxtI8rIz9+pg/M2MmK8/z48EZybkEcPKl41xu4s1iqXThy/jDPng==
|
||||||
|
|
||||||
|
"@next/swc-win32-arm64-msvc@14.1.0":
|
||||||
|
version "14.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.0.tgz#5f0b8cf955644104621e6d7cc923cad3a4c5365a"
|
||||||
|
integrity sha512-o1N5TsYc8f/HpGt39OUQpQ9AKIGApd3QLueu7hXk//2xq5Z9OxmV6sQfNp8C7qYmiOlHYODOGqNNa0e9jvchGQ==
|
||||||
|
|
||||||
"@next/swc-win32-arm64-msvc@14.2.4":
|
"@next/swc-win32-arm64-msvc@14.2.4":
|
||||||
version "14.2.4"
|
version "14.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.4.tgz#dbad3906e870dba84c5883d9d4c4838472e0697f"
|
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.4.tgz#dbad3906e870dba84c5883d9d4c4838472e0697f"
|
||||||
|
@ -2380,6 +2420,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.5-canary.46.tgz#0a65de42dcb8a8293ee0f8e3082d4d8c326f3d12"
|
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.5-canary.46.tgz#0a65de42dcb8a8293ee0f8e3082d4d8c326f3d12"
|
||||||
integrity sha512-SEnrOZ7ASXdd/GBq2x0IfpSbfamv1rZfcDeZZLF7kzu0pY7jDQwcW8zTKwwC8JH5CLGLfI3wD6wUYrA+PgJSCw==
|
integrity sha512-SEnrOZ7ASXdd/GBq2x0IfpSbfamv1rZfcDeZZLF7kzu0pY7jDQwcW8zTKwwC8JH5CLGLfI3wD6wUYrA+PgJSCw==
|
||||||
|
|
||||||
|
"@next/swc-win32-ia32-msvc@14.1.0":
|
||||||
|
version "14.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.0.tgz#21f4de1293ac5e5a168a412b139db5d3420a89d0"
|
||||||
|
integrity sha512-XXIuB1DBRCFwNO6EEzCTMHT5pauwaSj4SWs7CYnME57eaReAKBXCnkUE80p/pAZcewm7hs+vGvNqDPacEXHVkw==
|
||||||
|
|
||||||
"@next/swc-win32-ia32-msvc@14.2.4":
|
"@next/swc-win32-ia32-msvc@14.2.4":
|
||||||
version "14.2.4"
|
version "14.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.4.tgz#6074529b91ba49132922ce89a2e16d25d2ec235d"
|
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.4.tgz#6074529b91ba49132922ce89a2e16d25d2ec235d"
|
||||||
|
@ -2390,6 +2435,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.5-canary.46.tgz#e19326097b306c58eb47984acf7f7eca4485b604"
|
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.5-canary.46.tgz#e19326097b306c58eb47984acf7f7eca4485b604"
|
||||||
integrity sha512-NK1EJLyeUxgX9IHSxO0kN1Nk8VsaDfjHVYL4p9fM24e/9rG8jPcxquIQJ4Wy+ZdqxaVivqQ2eHrJYUpXpfOXmw==
|
integrity sha512-NK1EJLyeUxgX9IHSxO0kN1Nk8VsaDfjHVYL4p9fM24e/9rG8jPcxquIQJ4Wy+ZdqxaVivqQ2eHrJYUpXpfOXmw==
|
||||||
|
|
||||||
|
"@next/swc-win32-x64-msvc@14.1.0":
|
||||||
|
version "14.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.0.tgz#e561fb330466d41807123d932b365cf3d33ceba2"
|
||||||
|
integrity sha512-9WEbVRRAqJ3YFVqEZIxUqkiO8l1nool1LmNxygr5HWF8AcSYsEpneUDhmjUVJEzO2A04+oPtZdombzzPPkTtgg==
|
||||||
|
|
||||||
"@next/swc-win32-x64-msvc@14.2.4":
|
"@next/swc-win32-x64-msvc@14.2.4":
|
||||||
version "14.2.4"
|
version "14.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.4.tgz#e65a1c6539a671f97bb86d5183d6e3a1733c29c7"
|
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.4.tgz#e65a1c6539a671f97bb86d5183d6e3a1733c29c7"
|
||||||
|
@ -6344,9 +6394,9 @@ caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.300014
|
||||||
integrity sha512-c3dl911slnQhmxUIT4HhYzT7wnBK/XYpGnYLOj4nJBaRiw52Ibe7YxlDaAeRECvA786zCuExhxIUJ2K7nHMrBw==
|
integrity sha512-c3dl911slnQhmxUIT4HhYzT7wnBK/XYpGnYLOj4nJBaRiw52Ibe7YxlDaAeRECvA786zCuExhxIUJ2K7nHMrBw==
|
||||||
|
|
||||||
caniuse-lite@^1.0.30001579:
|
caniuse-lite@^1.0.30001579:
|
||||||
version "1.0.30001633"
|
version "1.0.30001583"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001633.tgz#45a4ade9fb9ec80a06537a6271ac1e0afadcb324"
|
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001583.tgz#abb2970cc370801dc7e27bf290509dc132cfa390"
|
||||||
integrity sha512-6sT0yf/z5jqf8tISAgpJDrmwOpLsrpnyCdD/lOZKvKkkJK4Dn0X5i7KF7THEZhOq+30bmhwBlNEaqPUiHiKtZg==
|
integrity sha512-acWTYaha8xfhA/Du/z4sNZjHUWjkiuoAi2LM+T/aL+kemKQgPT1xBb/YKjlQ0Qo8gvbHsGNplrEJ+9G3gL7i4Q==
|
||||||
|
|
||||||
caniuse-lite@^1.0.30001629:
|
caniuse-lite@^1.0.30001629:
|
||||||
version "1.0.30001636"
|
version "1.0.30001636"
|
||||||
|
@ -10367,6 +10417,29 @@ next-seo@^6.1.0:
|
||||||
resolved "https://registry.npmjs.org/next-seo/-/next-seo-6.1.0.tgz"
|
resolved "https://registry.npmjs.org/next-seo/-/next-seo-6.1.0.tgz"
|
||||||
integrity sha512-iMBpFoJsR5zWhguHJvsoBDxDSmdYTHtnVPB1ij+CD0NReQCP78ZxxbdL9qkKIf4oEuZEqZkrjAQLB0bkII7RYA==
|
integrity sha512-iMBpFoJsR5zWhguHJvsoBDxDSmdYTHtnVPB1ij+CD0NReQCP78ZxxbdL9qkKIf4oEuZEqZkrjAQLB0bkII7RYA==
|
||||||
|
|
||||||
|
next@*:
|
||||||
|
version "14.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/next/-/next-14.1.0.tgz#b31c0261ff9caa6b4a17c5af019ed77387174b69"
|
||||||
|
integrity sha512-wlzrsbfeSU48YQBjZhDzOwhWhGsy+uQycR8bHAOt1LY1bn3zZEcDyHQOEoN3aWzQ8LHCAJ1nqrWCc9XF2+O45Q==
|
||||||
|
dependencies:
|
||||||
|
"@next/env" "14.1.0"
|
||||||
|
"@swc/helpers" "0.5.2"
|
||||||
|
busboy "1.6.0"
|
||||||
|
caniuse-lite "^1.0.30001579"
|
||||||
|
graceful-fs "^4.2.11"
|
||||||
|
postcss "8.4.31"
|
||||||
|
styled-jsx "5.1.1"
|
||||||
|
optionalDependencies:
|
||||||
|
"@next/swc-darwin-arm64" "14.1.0"
|
||||||
|
"@next/swc-darwin-x64" "14.1.0"
|
||||||
|
"@next/swc-linux-arm64-gnu" "14.1.0"
|
||||||
|
"@next/swc-linux-arm64-musl" "14.1.0"
|
||||||
|
"@next/swc-linux-x64-gnu" "14.1.0"
|
||||||
|
"@next/swc-linux-x64-musl" "14.1.0"
|
||||||
|
"@next/swc-win32-arm64-msvc" "14.1.0"
|
||||||
|
"@next/swc-win32-ia32-msvc" "14.1.0"
|
||||||
|
"@next/swc-win32-x64-msvc" "14.1.0"
|
||||||
|
|
||||||
next@14.0.5-canary.46:
|
next@14.0.5-canary.46:
|
||||||
version "14.0.5-canary.46"
|
version "14.0.5-canary.46"
|
||||||
resolved "https://registry.yarnpkg.com/next/-/next-14.0.5-canary.46.tgz#003c8588488fd72bec70bcd4ea2f9ac65fd873f3"
|
resolved "https://registry.yarnpkg.com/next/-/next-14.0.5-canary.46.tgz#003c8588488fd72bec70bcd4ea2f9ac65fd873f3"
|
||||||
|
|
Loading…
Add table
Reference in a new issue