"use client" import { useEffect, useRef, useState } from "react" import { useMutation, useQueryClient } from "@tanstack/react-query" import { useSearchParams } from "next/navigation" import { useUserProfile } from "@/lib/queries/use-user-profile" import { queryKeys } from "@/lib/queries/query-keys" import { TIER_LIMITS } from "@asa-news/shared" import { classNames } from "@/lib/utilities" import { notify } from "@/lib/notify" function useCreateCheckoutSession() { return useMutation({ mutationFn: async ({ billingInterval, targetTier, }: { billingInterval: "monthly" | "yearly" targetTier: "pro" | "developer" }) => { const response = await fetch("/api/billing/create-checkout-session", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ billingInterval, targetTier }), }) if (!response.ok) { const data = await response.json() throw new Error(data.error || "failed to create checkout session") } const data = await response.json() return data as { url?: string; upgraded?: boolean } }, }) } function useCreatePortalSession() { return useMutation({ mutationFn: async () => { const response = await fetch("/api/billing/create-portal-session", { method: "POST", }) if (!response.ok) { const data = await response.json() throw new Error(data.error || "failed to create portal session") } const data = await response.json() return data as { url: string } }, }) } const PRO_FEATURES = [ `${TIER_LIMITS.pro.maximumFeeds} feeds`, `${TIER_LIMITS.pro.historyRetentionDays}-day history retention`, `${TIER_LIMITS.pro.refreshIntervalSeconds / 60}-minute refresh interval`, "authenticated feeds", "opml export", "manual feed refresh", ] const DEVELOPER_FEATURES = [ `${TIER_LIMITS.developer.maximumFeeds} feeds`, "everything in pro", "read-only rest api", "webhook push notifications", ] function UpgradeCard({ targetTier, features, monthlyPrice, yearlyPrice, }: { targetTier: "pro" | "developer" features: string[] monthlyPrice: string yearlyPrice: string }) { const [billingInterval, setBillingInterval] = useState< "monthly" | "yearly" >("yearly") const queryClient = useQueryClient() const createCheckoutSession = useCreateCheckoutSession() function handleUpgrade() { createCheckoutSession.mutate( { billingInterval, targetTier }, { onSuccess: (data) => { if (data.upgraded) { queryClient.invalidateQueries({ queryKey: queryKeys.userProfile.all, }) notify(`upgraded to ${targetTier}!`) } else if (data.url) { window.location.href = data.url } }, onError: (error: Error) => { notify("failed to start checkout: " + error.message) }, } ) } return (

upgrade to {targetTier}

) } export function BillingSettings() { const { data: userProfile, isLoading } = useUserProfile() const queryClient = useQueryClient() const searchParameters = useSearchParams() const hasShownSuccessToast = useRef(false) const createPortalSession = useCreatePortalSession() useEffect(() => { if ( searchParameters.get("billing") === "success" && !hasShownSuccessToast.current ) { hasShownSuccessToast.current = true queryClient.invalidateQueries({ queryKey: queryKeys.userProfile.all }) const tierName = userProfile?.tier === "developer" ? "Developer" : userProfile?.tier === "pro" ? "Pro" : null notify(tierName ? `welcome to ${tierName}` : "subscription activated") const url = new URL(window.location.href) url.searchParams.delete("billing") window.history.replaceState({}, "", url.pathname) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchParameters, queryClient]) if (isLoading) { return

loading billing ...

} if (!userProfile) { return

failed to load billing

} function handleManageSubscription() { createPortalSession.mutate(undefined, { onSuccess: (data) => { window.location.href = data.url }, onError: (error: Error) => { notify("failed to open billing portal: " + error.message) }, }) } const isPaidTier = userProfile.tier === "pro" || userProfile.tier === "developer" const isCancelling = userProfile.stripeSubscriptionStatus === "active" && isPaidTier && userProfile.stripeCurrentPeriodEnd !== null return (

current plan

{userProfile.tier} {userProfile.stripeSubscriptionStatus && ( ({userProfile.stripeSubscriptionStatus}) )}
{userProfile.tier === "free" && ( <> )} {userProfile.tier === "pro" && ( <>
{isCancelling && userProfile.stripeCurrentPeriodEnd && (

your pro plan is active until{" "} {new Date( userProfile.stripeCurrentPeriodEnd ).toLocaleDateString()}

)} {userProfile.stripeSubscriptionStatus === "past_due" && (

payment failed — please update your payment method

)}
)} {userProfile.tier === "developer" && (
{isCancelling && userProfile.stripeCurrentPeriodEnd && (

your developer plan is active until{" "} {new Date( userProfile.stripeCurrentPeriodEnd ).toLocaleDateString()}

)} {userProfile.stripeSubscriptionStatus === "past_due" && (

payment failed — please update your payment method

)}
)}
) }