diff --git a/example/convex/_generated/api.d.ts b/example/convex/_generated/api.d.ts index d46d7a3..53c1d60 100644 --- a/example/convex/_generated/api.d.ts +++ b/example/convex/_generated/api.d.ts @@ -85,7 +85,7 @@ export declare const components: { priceAmount?: number; priceCurrency?: string; productId: string; - recurringInterval?: string; + recurringInterval?: "month" | "year" | null; type?: string; }>; }; @@ -111,7 +111,7 @@ export declare const components: { modifiedAt: string | null; priceId: string; productId: string; - recurringInterval: string; + recurringInterval: "month" | "year" | null; startedAt: string | null; status: string; }; @@ -177,12 +177,12 @@ export declare const components: { priceAmount?: number; priceCurrency?: string; productId: string; - recurringInterval?: string; + recurringInterval?: "month" | "year" | null; type?: string; }>; }; productId: string; - recurringInterval: string; + recurringInterval: "month" | "year" | null; startedAt: string | null; status: string; } | null @@ -241,7 +241,7 @@ export declare const components: { priceAmount?: number; priceCurrency?: string; productId: string; - recurringInterval?: string; + recurringInterval?: "month" | "year" | null; type?: string; }>; } | null @@ -267,7 +267,7 @@ export declare const components: { modifiedAt: string | null; priceId: string; productId: string; - recurringInterval: string; + recurringInterval: "month" | "year" | null; startedAt: string | null; status: string; } | null @@ -299,7 +299,7 @@ export declare const components: { modifiedAt: string | null; priceId: string; productId: string; - recurringInterval: string; + recurringInterval: "month" | "year" | null; startedAt: string | null; status: string; }> @@ -307,7 +307,7 @@ export declare const components: { listProducts: FunctionReference< "query", "internal", - { includeArchived: boolean }, + { includeArchived?: boolean }, Array<{ _creationTime: number; _id: string; @@ -347,7 +347,7 @@ export declare const components: { priceAmount?: number; priceCurrency?: string; productId: string; - recurringInterval?: string; + recurringInterval?: "month" | "year" | null; type?: string; }>; }> @@ -411,12 +411,12 @@ export declare const components: { priceAmount?: number; priceCurrency?: string; productId: string; - recurringInterval?: string; + recurringInterval?: "month" | "year" | null; type?: string; }>; } | null; productId: string; - recurringInterval: string; + recurringInterval: "month" | "year" | null; startedAt: string | null; status: string; }> @@ -462,7 +462,7 @@ export declare const components: { priceAmount?: number; priceCurrency?: string; productId: string; - recurringInterval?: string; + recurringInterval?: "month" | "year" | null; type?: string; }>; }; @@ -488,7 +488,7 @@ export declare const components: { modifiedAt: string | null; priceId: string; productId: string; - recurringInterval: string; + recurringInterval: "month" | "year" | null; startedAt: string | null; status: string; }; diff --git a/example/convex/example.ts b/example/convex/example.ts index a10e6d5..bc644e8 100644 --- a/example/convex/example.ts +++ b/example/convex/example.ts @@ -2,15 +2,16 @@ import { Polar } from "@convex-dev/polar"; import { api, components } from "./_generated/api"; import { QueryCtx, mutation, query } from "./_generated/server"; import { v } from "convex/values"; -import { DataModel, Id } from "./_generated/dataModel"; +import { Id } from "./_generated/dataModel"; -const products = { - premium: "5fde8344-5fca-4d0b-adeb-2052cddfd9ed", - premiumPlus: "db548a6f-ff8c-4969-8f02-5f7301a36e7c", -}; - -export const polar = new Polar(components.polar, { - products, +export const polar = new Polar(components.polar, { + products: { + // These would probably be environment variables in a production app + premiumMonthly: "5fde8344-5fca-4d0b-adeb-2052cddfd9ed", + premiumYearly: "9bc5ed5f-2065-40a4-bd1f-e012e448d82f", + premiumPlusMonthly: "db548a6f-ff8c-4969-8f02-5f7301a36e7c", + premiumPlusYearly: "9ff9976e-459e-4ebc-8cde-b2ced74f8822", + }, getUserInfo: async (ctx) => { const user: { _id: Id<"users">; email: string } = await ctx.runQuery( api.example.getCurrentUser @@ -25,8 +26,11 @@ export const polar = new Polar(components.polar, { export const MAX_FREE_TODOS = 3; export const MAX_PREMIUM_TODOS = 6; -export const { changeCurrentSubscription, cancelCurrentSubscription } = - polar.api(); +export const { + changeCurrentSubscription, + cancelCurrentSubscription, + getProducts, +} = polar.api(); export const { generateCheckoutLink, generateCustomerPortalUrl } = polar.checkoutApi(); @@ -41,14 +45,17 @@ const currentUser = async (ctx: QueryCtx) => { const subscription = await polar.getCurrentSubscription(ctx, { userId: user._id, }); - const isPremiumPlus = - subscription?.product?.id === polar.products.premiumPlus; + const productKey = subscription?.productKey; const isPremium = - isPremiumPlus || subscription?.product?.id === polar.products.premium; + productKey === "premiumMonthly" || productKey === "premiumYearly"; + const isPremiumPlus = + productKey === "premiumPlusMonthly" || productKey === "premiumPlusYearly"; return { ...user, + isFree: !isPremium && !isPremiumPlus, isPremium, isPremiumPlus, + subscription, maxTodos: isPremiumPlus ? MAX_PREMIUM_TODOS : isPremium @@ -111,10 +118,15 @@ export const insertTodo = mutation({ .withIndex("userId", (q) => q.eq("userId", user._id)) .collect() ).length; - if (!user.isPremium && todoCount >= MAX_FREE_TODOS) { + const productKey = user.subscription?.productKey; + if (!productKey && todoCount >= MAX_FREE_TODOS) { throw new Error("Reached maximum number of todos for free plan"); } - if (!user.isPremiumPlus && todoCount >= MAX_PREMIUM_TODOS) { + if ( + (productKey === "premiumMonthly" || + productKey === "premiumPlusMonthly") && + todoCount >= MAX_PREMIUM_TODOS + ) { throw new Error("Reached maximum number of todos for premium plan"); } await ctx.db.insert("todos", { diff --git a/example/package.json b/example/package.json index 91bdf49..21c394f 100644 --- a/example/package.json +++ b/example/package.json @@ -27,6 +27,7 @@ "postcss": "^8.4.49", "react": "^18.3.1", "react-dom": "^18.3.1", + "remeda": "^2.20.2", "tailwind-merge": "^2.6.0", "tailwindcss": "^3.4.17", "use-debounce": "^10.0.4" diff --git a/example/src/App.tsx b/example/src/App.tsx index 23cd642..cae971e 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -90,11 +90,7 @@ export default function TodoList() { ))} - - + ); diff --git a/example/src/BillingSettings.tsx b/example/src/BillingSettings.tsx index 813b922..74b3dbc 100644 --- a/example/src/BillingSettings.tsx +++ b/example/src/BillingSettings.tsx @@ -1,36 +1,34 @@ import { Button } from "@/components/ui/button"; -import { ArrowLeft, ArrowRight, Check, Settings } from "lucide-react"; +import { ArrowLeft, Check, Settings } from "lucide-react"; import { CustomerPortalLink } from "../../src/react"; import { api } from "../convex/_generated/api"; import { useState } from "react"; import { UpgradeCTA } from "@/UpgradeCta"; +import { useQuery } from "convex/react"; -export function BillingSettings({ - isPremium, - isPremiumPlus, -}: { - isPremium: boolean; - isPremiumPlus: boolean; -}) { +export function BillingSettings() { + const user = useQuery(api.example.getCurrentUser); const [showPricingPlans, setShowPricingPlans] = useState(false); - const currentPlan = isPremiumPlus - ? "Premium Plus" - : isPremium - ? "Premium" - : "Free"; + const getFeatures = () => { + switch (user?.subscription?.productKey) { + case "premiumMonthly": + case "premiumYearly": + return ["Up to 6 todos", "Reduced ads", "Basic support"]; + case "premiumPlusMonthly": + case "premiumPlusYearly": + return [ + "Unlimited todos", + "No ads", + "Priority support", + "Advanced analytics", + ]; + default: + return ["Up to 3 todos", "Ad supported", "Community support"]; + } + }; - const currentPrice = isPremiumPlus - ? "$20/month or $200/year" - : isPremium - ? "$10/month or $100/year" - : "Free"; - - const features = isPremiumPlus - ? ["Unlimited todos", "No ads", "Priority support", "Advanced analytics"] - : isPremium - ? ["Up to 6 todos", "Reduced ads", "Basic support"] - : ["Up to 3 todos", "Ad supported", "Community support"]; + const currentPlan = user?.subscription?.product; return (
@@ -39,7 +37,7 @@ export function BillingSettings({ {showPricingPlans ? "Available Plans" : "Billing Settings"}
- {!showPricingPlans && (isPremium || isPremiumPlus) && ( + {!showPricingPlans && user?.subscription && ( Current Plan:
- {currentPlan} + {currentPlan?.name || "Free"} - {currentPrice !== "Free" && ( + {currentPlan && (
- {isPremiumPlus ? "$20/month" : "$10/month"} - - - or {isPremiumPlus ? "$200/year" : "$100/year"} + {currentPlan.prices[0].priceAmount}/ + {currentPlan.prices[0].recurringInterval}
)}
    - {features.map((feature) => ( + {getFeatures().map((feature) => (
  • )} {showPricingPlans && ( - +
    + +
    )}
); diff --git a/example/src/UpgradeCta.tsx b/example/src/UpgradeCta.tsx index eb395bf..493985b 100644 --- a/example/src/UpgradeCta.tsx +++ b/example/src/UpgradeCta.tsx @@ -2,19 +2,13 @@ import { Button } from "@/components/ui/button"; import { ArrowRight, Check, Star, Settings } from "lucide-react"; import { CheckoutLink, CustomerPortalLink } from "../../src/react"; import { api } from "../convex/_generated/api"; -import { useAction } from "convex/react"; +import { useAction, useQuery } from "convex/react"; import { useState } from "react"; import { ConfirmationModal } from "./ConfirmationModal"; -export function UpgradeCTA({ - isFree, - isPremium, - isPremiumPlus, -}: { - isFree: boolean; - isPremium: boolean; - isPremiumPlus: boolean; -}) { +export function UpgradeCTA() { + const user = useQuery(api.example.getCurrentUser); + const products = useQuery(api.example.getProducts); const changeCurrentSubscription = useAction( api.example.changeCurrentSubscription ); @@ -23,11 +17,11 @@ export function UpgradeCTA({ ); const [showDowngradeModal, setShowDowngradeModal] = useState(false); const [pendingDowngrade, setPendingDowngrade] = useState< - "premium" | "free" + "free" | "premiumMonthly" >(); const [showUpgradeModal, setShowUpgradeModal] = useState(false); const [pendingUpgrade, setPendingUpgrade] = useState< - "premium" | "premiumPlus" + "premiumMonthly" | "premiumPlusMonthly" >(); return ( @@ -35,12 +29,12 @@ export function UpgradeCTA({
- {isFree && ( + {user?.isFree && (
Current Plan
@@ -48,7 +42,7 @@ export function UpgradeCTA({

Free @@ -56,7 +50,9 @@ export function UpgradeCTA({
  • @@ -64,7 +60,9 @@ export function UpgradeCTA({
  • @@ -72,7 +70,9 @@ export function UpgradeCTA({
  • @@ -80,7 +80,9 @@ export function UpgradeCTA({
  • @@ -88,7 +90,7 @@ export function UpgradeCTA({

- {!isFree && ( + {!user?.isFree && ( )} - {isPremiumPlus && ( + {user?.isPremiumPlus && ( )} - {isFree && ( + {user?.isFree && products && ( )} - {isPremium && ( + {user?.isPremium && (
)} - {isFree && ( + {user?.isFree && products && (