refactor: replace usePayment hook and store with useCurrentPlan for improved payment state management
This commit is contained in:
parent
ff1e72df13
commit
ac8d4dee4b
@ -1,7 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { ActiveThemeProvider } from '@/components/layout/active-theme-provider';
|
||||
import { PaymentProvider } from '@/components/layout/payment-provider';
|
||||
import { QueryProvider } from '@/components/providers/query-provider';
|
||||
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||
import { websiteConfig } from '@/config/website';
|
||||
@ -63,9 +62,7 @@ export function Providers({ children, locale }: ProvidersProps) {
|
||||
>
|
||||
<ActiveThemeProvider>
|
||||
<RootProvider theme={theme} i18n={{ locale, locales, translations }}>
|
||||
<TooltipProvider>
|
||||
<PaymentProvider>{children}</PaymentProvider>
|
||||
</TooltipProvider>
|
||||
<TooltipProvider>{children}</TooltipProvider>
|
||||
</RootProvider>
|
||||
</ActiveThemeProvider>
|
||||
</ThemeProvider>
|
||||
|
@ -23,7 +23,6 @@ import { useLocalePathname, useLocaleRouter } from '@/i18n/navigation';
|
||||
import { LOCALES, routing } from '@/i18n/routing';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { useLocaleStore } from '@/stores/locale-store';
|
||||
import { usePaymentStore } from '@/stores/payment-store';
|
||||
import type { User } from 'better-auth';
|
||||
import {
|
||||
ChevronsUpDown,
|
||||
@ -55,7 +54,6 @@ export function SidebarUser({ user, className }: SidebarUserProps) {
|
||||
const pathname = useLocalePathname();
|
||||
const params = useParams();
|
||||
const { currentLocale, setCurrentLocale } = useLocaleStore();
|
||||
const { resetState } = usePaymentStore();
|
||||
const [, startTransition] = useTransition();
|
||||
const t = useTranslations();
|
||||
|
||||
@ -81,8 +79,7 @@ export function SidebarUser({ user, className }: SidebarUserProps) {
|
||||
fetchOptions: {
|
||||
onSuccess: () => {
|
||||
console.log('sign out success');
|
||||
// Reset payment state on sign out
|
||||
resetState();
|
||||
// TanStack Query automatically handles cache invalidation on sign out
|
||||
router.replace('/');
|
||||
},
|
||||
onError: (error) => {
|
||||
@ -100,7 +97,7 @@ export function SidebarUser({ user, className }: SidebarUserProps) {
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="cursor-pointer data-[state=open]:bg-sidebar-accent
|
||||
className="cursor-pointer data-[state=open]:bg-sidebar-accent
|
||||
data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<UserAvatar
|
||||
|
@ -9,8 +9,9 @@ import {
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { websiteConfig } from '@/config/website';
|
||||
import { usePayment } from '@/hooks/use-payment';
|
||||
import { useCurrentPlan } from '@/hooks/use-payment-query';
|
||||
import { LocaleLink } from '@/i18n/navigation';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { Routes } from '@/routes';
|
||||
import { SparklesIcon } from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
@ -23,14 +24,16 @@ export function UpgradeCard() {
|
||||
|
||||
const t = useTranslations('Dashboard.upgrade');
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const { isLoading, currentPlan, subscription } = usePayment();
|
||||
const { data: session } = authClient.useSession();
|
||||
const { data: paymentData, isLoading } = useCurrentPlan(session?.user?.id);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
// Don't show the upgrade card if the user has a lifetime membership or a subscription
|
||||
const isMember = currentPlan?.isLifetime || !!subscription;
|
||||
const isMember =
|
||||
paymentData?.currentPlan?.isLifetime || !!paymentData?.subscription;
|
||||
|
||||
if (!mounted || isLoading || isMember) {
|
||||
return null;
|
||||
|
@ -1,24 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { usePaymentStore } from '@/stores/payment-store';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
/**
|
||||
* Payment provider component
|
||||
*
|
||||
* This component is responsible for initializing the payment state
|
||||
* by fetching the current user's subscription and payment information when the app loads.
|
||||
*/
|
||||
export function PaymentProvider({ children }: { children: React.ReactNode }) {
|
||||
const { fetchPayment } = usePaymentStore();
|
||||
const { data: session } = authClient.useSession();
|
||||
|
||||
useEffect(() => {
|
||||
if (session?.user) {
|
||||
fetchPayment(session.user);
|
||||
}
|
||||
}, [session?.user, fetchPayment]);
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
@ -13,7 +13,6 @@ import {
|
||||
import { getAvatarLinks } from '@/config/avatar-config';
|
||||
import { LocaleLink, useLocaleRouter } from '@/i18n/navigation';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { usePaymentStore } from '@/stores/payment-store';
|
||||
import type { User } from 'better-auth';
|
||||
import { LogOutIcon } from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
@ -29,8 +28,6 @@ export function UserButtonMobile({ user }: UserButtonProps) {
|
||||
const avatarLinks = getAvatarLinks();
|
||||
const localeRouter = useLocaleRouter();
|
||||
const [open, setOpen] = useState(false);
|
||||
const { resetState } = usePaymentStore();
|
||||
|
||||
const closeDrawer = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
@ -40,8 +37,7 @@ export function UserButtonMobile({ user }: UserButtonProps) {
|
||||
fetchOptions: {
|
||||
onSuccess: () => {
|
||||
console.log('sign out success');
|
||||
// Reset payment state on sign out
|
||||
resetState();
|
||||
// TanStack Query automatically handles cache invalidation on sign out
|
||||
localeRouter.replace('/');
|
||||
},
|
||||
onError: (error) => {
|
||||
@ -64,7 +60,7 @@ export function UserButtonMobile({ user }: UserButtonProps) {
|
||||
<DrawerPortal>
|
||||
<DrawerOverlay className="fixed inset-0 z-40 bg-background/50" />
|
||||
<DrawerContent
|
||||
className="fixed inset-x-0 bottom-0 z-50 mt-24
|
||||
className="fixed inset-x-0 bottom-0 z-50 mt-24
|
||||
overflow-hidden rounded-t-[10px] border bg-background px-3 text-sm"
|
||||
>
|
||||
<DrawerHeader>
|
||||
|
@ -12,7 +12,6 @@ import { getAvatarLinks } from '@/config/avatar-config';
|
||||
import { websiteConfig } from '@/config/website';
|
||||
import { useLocaleRouter } from '@/i18n/navigation';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { usePaymentStore } from '@/stores/payment-store';
|
||||
import type { User } from 'better-auth';
|
||||
import { LogOutIcon } from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
@ -29,15 +28,12 @@ export function UserButton({ user }: UserButtonProps) {
|
||||
const avatarLinks = getAvatarLinks();
|
||||
const localeRouter = useLocaleRouter();
|
||||
const [open, setOpen] = useState(false);
|
||||
const { resetState } = usePaymentStore();
|
||||
|
||||
const handleSignOut = async () => {
|
||||
await authClient.signOut({
|
||||
fetchOptions: {
|
||||
onSuccess: () => {
|
||||
console.log('sign out success');
|
||||
// Reset payment state on sign out
|
||||
resetState();
|
||||
// TanStack Query automatically handles cache invalidation on sign out
|
||||
localeRouter.replace('/');
|
||||
},
|
||||
onError: (error) => {
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { getPricePlans } from '@/config/price-config';
|
||||
import { useMounted } from '@/hooks/use-mounted';
|
||||
import { usePayment } from '@/hooks/use-payment';
|
||||
import { useCurrentPlan } from '@/hooks/use-payment-query';
|
||||
import { LocaleLink, useLocaleRouter } from '@/i18n/navigation';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { formatDate } from '@/lib/formatter';
|
||||
@ -33,34 +33,37 @@ export default function BillingCard() {
|
||||
const hasHandledSession = useRef(false);
|
||||
const mounted = useMounted();
|
||||
|
||||
const {
|
||||
isLoading: isLoadingPayment,
|
||||
error: loadPaymentError,
|
||||
subscription,
|
||||
currentPlan: currentPlanFromStore,
|
||||
fetchPayment,
|
||||
} = usePayment();
|
||||
|
||||
// Get user session for customer ID
|
||||
const { data: session, isPending: isLoadingSession } =
|
||||
authClient.useSession();
|
||||
const currentUser = session?.user;
|
||||
|
||||
// TanStack Query hook for current plan and subscription
|
||||
const {
|
||||
data: paymentData,
|
||||
isLoading: isLoadingPayment,
|
||||
error: loadPaymentError,
|
||||
refetch: refetchPayment,
|
||||
} = useCurrentPlan(currentUser?.id);
|
||||
|
||||
const currentPlan = paymentData?.currentPlan;
|
||||
const subscription = paymentData?.subscription;
|
||||
|
||||
// Get price plans with translations - must be called here to maintain hook order
|
||||
const pricePlans = getPricePlans();
|
||||
const plans = Object.values(pricePlans);
|
||||
|
||||
// Convert current plan from store to a plan with translations
|
||||
const currentPlan = currentPlanFromStore
|
||||
? plans.find((plan) => plan.id === currentPlanFromStore?.id)
|
||||
// Convert current plan to a plan with translations
|
||||
const currentPlanWithTranslations = currentPlan
|
||||
? plans.find((plan) => plan.id === currentPlan?.id)
|
||||
: null;
|
||||
const isFreePlan = currentPlan?.isFree || false;
|
||||
const isLifetimeMember = currentPlan?.isLifetime || false;
|
||||
const isFreePlan = currentPlanWithTranslations?.isFree || false;
|
||||
const isLifetimeMember = currentPlanWithTranslations?.isLifetime || false;
|
||||
|
||||
// Get subscription price details
|
||||
const currentPrice =
|
||||
subscription &&
|
||||
currentPlan?.prices.find(
|
||||
currentPlanWithTranslations?.prices.find(
|
||||
(price) => price.priceId === subscription?.priceId
|
||||
);
|
||||
|
||||
@ -77,8 +80,8 @@ export default function BillingCard() {
|
||||
// Retry payment data fetching
|
||||
const handleRetry = useCallback(() => {
|
||||
// console.log('handleRetry, refetch payment info');
|
||||
fetchPayment(true);
|
||||
}, [fetchPayment]);
|
||||
refetchPayment();
|
||||
}, [refetchPayment]);
|
||||
|
||||
// Check for payment success and show success message
|
||||
useEffect(() => {
|
||||
@ -132,7 +135,9 @@ export default function BillingCard() {
|
||||
<CardDescription>{t('currentPlan.description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 flex-1">
|
||||
<div className="text-destructive text-sm">{loadPaymentError}</div>
|
||||
<div className="text-destructive text-sm">
|
||||
{loadPaymentError?.message}
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="mt-2 px-6 py-4 flex justify-end items-center bg-background rounded-none">
|
||||
<Button
|
||||
@ -149,7 +154,7 @@ export default function BillingCard() {
|
||||
}
|
||||
|
||||
// currentPlan maybe null, so we need to check if it is null
|
||||
if (!currentPlan) {
|
||||
if (!currentPlanWithTranslations) {
|
||||
return (
|
||||
<Card className={cn('w-full overflow-hidden pt-6 pb-0 flex flex-col')}>
|
||||
<CardHeader>
|
||||
@ -187,7 +192,9 @@ export default function BillingCard() {
|
||||
<CardContent className="space-y-4 flex-1">
|
||||
{/* Plan name and status */}
|
||||
<div className="flex items-center justify-start space-x-4">
|
||||
<div className="text-3xl font-medium">{currentPlan?.name}</div>
|
||||
<div className="text-3xl font-medium">
|
||||
{currentPlanWithTranslations?.name}
|
||||
</div>
|
||||
{subscription &&
|
||||
(subscription.status === 'trialing' ||
|
||||
subscription.status === 'active') && (
|
||||
|
@ -11,7 +11,8 @@ import {
|
||||
import { getCreditPackages } from '@/config/credits-config';
|
||||
import { websiteConfig } from '@/config/website';
|
||||
import { useCurrentUser } from '@/hooks/use-current-user';
|
||||
import { usePayment } from '@/hooks/use-payment';
|
||||
import { useCurrentPlan } from '@/hooks/use-payment-query';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { formatPrice } from '@/lib/formatter';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { CircleCheckBigIcon, CoinsIcon } from 'lucide-react';
|
||||
@ -31,7 +32,9 @@ export function CreditPackages() {
|
||||
|
||||
// Get current user and payment info
|
||||
const currentUser = useCurrentUser();
|
||||
const { currentPlan } = usePayment();
|
||||
const { data: session } = authClient.useSession();
|
||||
const { data: paymentData } = useCurrentPlan(session?.user?.id);
|
||||
const currentPlan = paymentData?.currentPlan;
|
||||
|
||||
// Get credit packages with translations - must be called here to maintain hook order
|
||||
const creditPackages = Object.values(getCreditPackages()).filter(
|
||||
|
@ -13,8 +13,9 @@ import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { websiteConfig } from '@/config/website';
|
||||
import { useCreditBalance, useCreditStats } from '@/hooks/use-credits-query';
|
||||
import { useMounted } from '@/hooks/use-mounted';
|
||||
import { usePayment } from '@/hooks/use-payment';
|
||||
import { useCurrentPlan } from '@/hooks/use-payment-query';
|
||||
import { useLocaleRouter } from '@/i18n/navigation';
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { formatDate } from '@/lib/formatter';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Routes } from '@/routes';
|
||||
@ -48,7 +49,9 @@ export default function CreditsBalanceCard() {
|
||||
} = useCreditBalance();
|
||||
|
||||
// Get payment info to check plan type
|
||||
const { currentPlan } = usePayment();
|
||||
const { data: session } = authClient.useSession();
|
||||
const { data: paymentData } = useCurrentPlan(session?.user?.id);
|
||||
const currentPlan = paymentData?.currentPlan;
|
||||
|
||||
// TanStack Query hook for credit statistics
|
||||
const {
|
||||
|
107
src/hooks/use-payment-query.ts
Normal file
107
src/hooks/use-payment-query.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import { getActiveSubscriptionAction } from '@/actions/get-active-subscription';
|
||||
import { getLifetimeStatusAction } from '@/actions/get-lifetime-status';
|
||||
import { getAllPricePlans } from '@/lib/price-plan';
|
||||
import type { PricePlan, Subscription } from '@/payment/types';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
// Query keys
|
||||
export const paymentKeys = {
|
||||
all: ['payment'] as const,
|
||||
subscription: (userId: string) =>
|
||||
[...paymentKeys.all, 'subscription', userId] as const,
|
||||
lifetime: (userId: string) =>
|
||||
[...paymentKeys.all, 'lifetime', userId] as const,
|
||||
currentPlan: (userId: string) =>
|
||||
[...paymentKeys.all, 'currentPlan', userId] as const,
|
||||
};
|
||||
|
||||
// Hook to fetch active subscription
|
||||
export function useActiveSubscription(userId: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: paymentKeys.subscription(userId || ''),
|
||||
queryFn: async (): Promise<Subscription | null> => {
|
||||
if (!userId) {
|
||||
throw new Error('User ID is required');
|
||||
}
|
||||
const result = await getActiveSubscriptionAction({ userId });
|
||||
if (!result?.data?.success) {
|
||||
throw new Error(result?.data?.error || 'Failed to fetch subscription');
|
||||
}
|
||||
return result.data.data || null;
|
||||
},
|
||||
enabled: !!userId,
|
||||
});
|
||||
}
|
||||
|
||||
// Hook to fetch lifetime status
|
||||
export function useLifetimeStatus(userId: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: paymentKeys.lifetime(userId || ''),
|
||||
queryFn: async (): Promise<boolean> => {
|
||||
if (!userId) {
|
||||
throw new Error('User ID is required');
|
||||
}
|
||||
const result = await getLifetimeStatusAction({ userId });
|
||||
if (!result?.data?.success) {
|
||||
throw new Error(
|
||||
result?.data?.error || 'Failed to fetch lifetime status'
|
||||
);
|
||||
}
|
||||
return result.data.isLifetimeMember || false;
|
||||
},
|
||||
enabled: !!userId,
|
||||
});
|
||||
}
|
||||
|
||||
// Hook to get current plan based on subscription and lifetime status
|
||||
export function useCurrentPlan(userId: string | undefined) {
|
||||
const {
|
||||
data: subscription,
|
||||
isLoading: isLoadingSubscription,
|
||||
error: subscriptionError,
|
||||
} = useActiveSubscription(userId);
|
||||
const {
|
||||
data: isLifetimeMember,
|
||||
isLoading: isLoadingLifetime,
|
||||
error: lifetimeError,
|
||||
} = useLifetimeStatus(userId);
|
||||
|
||||
return useQuery({
|
||||
queryKey: paymentKeys.currentPlan(userId || ''),
|
||||
queryFn: async (): Promise<{
|
||||
currentPlan: PricePlan | null;
|
||||
subscription: Subscription | null;
|
||||
}> => {
|
||||
const plans: PricePlan[] = getAllPricePlans();
|
||||
const freePlan = plans.find((plan) => plan.isFree);
|
||||
const lifetimePlan = plans.find((plan) => plan.isLifetime);
|
||||
|
||||
// If lifetime member, return lifetime plan
|
||||
if (isLifetimeMember) {
|
||||
return {
|
||||
currentPlan: lifetimePlan || null,
|
||||
subscription: null,
|
||||
};
|
||||
}
|
||||
|
||||
// If has active subscription, find the corresponding plan
|
||||
if (subscription) {
|
||||
const plan =
|
||||
plans.find((p) =>
|
||||
p.prices.find((price) => price.priceId === subscription.priceId)
|
||||
) || null;
|
||||
return {
|
||||
currentPlan: plan,
|
||||
subscription,
|
||||
};
|
||||
}
|
||||
|
||||
// Default to free plan
|
||||
return {
|
||||
currentPlan: freePlan || null,
|
||||
subscription: null,
|
||||
};
|
||||
},
|
||||
enabled: !!userId && !isLoadingSubscription && !isLoadingLifetime,
|
||||
});
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
import { authClient } from '@/lib/auth-client';
|
||||
import { usePaymentStore } from '@/stores/payment-store';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
/**
|
||||
* Hook for accessing and managing payment state
|
||||
*
|
||||
* This hook provides access to the payment state and methods to manage it.
|
||||
* It also automatically fetches payment information when the user changes.
|
||||
*/
|
||||
export function usePayment() {
|
||||
const {
|
||||
currentPlan,
|
||||
subscription,
|
||||
isLoading,
|
||||
error,
|
||||
fetchPayment: fetchPaymentFromStore,
|
||||
} = usePaymentStore();
|
||||
|
||||
const { data: session } = authClient.useSession();
|
||||
|
||||
const fetchPayment = useCallback(
|
||||
(force = false) => {
|
||||
const currentUser = session?.user;
|
||||
if (currentUser) {
|
||||
fetchPaymentFromStore(currentUser, force);
|
||||
}
|
||||
},
|
||||
[session?.user, fetchPaymentFromStore]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const currentUser = session?.user;
|
||||
if (currentUser) {
|
||||
fetchPaymentFromStore(currentUser);
|
||||
}
|
||||
}, [session?.user, fetchPaymentFromStore]);
|
||||
|
||||
return {
|
||||
// State
|
||||
currentPlan,
|
||||
subscription,
|
||||
isLoading,
|
||||
error,
|
||||
|
||||
// Methods
|
||||
fetchPayment,
|
||||
};
|
||||
}
|
@ -1,180 +0,0 @@
|
||||
import { getActiveSubscriptionAction } from '@/actions/get-active-subscription';
|
||||
import { getLifetimeStatusAction } from '@/actions/get-lifetime-status';
|
||||
import type { Session } from '@/lib/auth-types';
|
||||
import { getAllPricePlans } from '@/lib/price-plan';
|
||||
import type { PricePlan, Subscription } from '@/payment/types';
|
||||
import { create } from 'zustand';
|
||||
|
||||
// Cache duration: 2 minutes (optimized for better UX)
|
||||
const CACHE_DURATION = 2 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Payment state interface
|
||||
*/
|
||||
export interface PaymentState {
|
||||
// Current plan
|
||||
currentPlan: PricePlan | null;
|
||||
// Active subscription
|
||||
subscription: Subscription | null;
|
||||
// Loading state
|
||||
isLoading: boolean;
|
||||
// Error state
|
||||
error: string | null;
|
||||
// Last fetch timestamp to avoid frequent requests
|
||||
lastFetchTime: number | null;
|
||||
|
||||
// Actions
|
||||
fetchPayment: (
|
||||
user: Session['user'] | null | undefined,
|
||||
force?: boolean
|
||||
) => Promise<void>;
|
||||
resetState: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Payment store using Zustand
|
||||
* Manages the user's payment and subscription data globally
|
||||
*/
|
||||
export const usePaymentStore = create<PaymentState>((set, get) => ({
|
||||
// Initial state
|
||||
currentPlan: null,
|
||||
subscription: null,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
lastFetchTime: null,
|
||||
|
||||
/**
|
||||
* Fetch payment and subscription data for the current user
|
||||
* @param user Current user from auth session
|
||||
*/
|
||||
fetchPayment: async (user, force = false) => {
|
||||
// Skip if already loading
|
||||
if (get().isLoading) return;
|
||||
|
||||
// Skip if no user is provided
|
||||
if (!user) {
|
||||
set({
|
||||
currentPlan: null,
|
||||
subscription: null,
|
||||
error: null,
|
||||
lastFetchTime: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we have recent data (within cache duration) unless force refresh
|
||||
if (!force) {
|
||||
const { lastFetchTime } = get();
|
||||
const now = Date.now();
|
||||
if (lastFetchTime && now - lastFetchTime < CACHE_DURATION) {
|
||||
console.log('fetchPayment, use cached data');
|
||||
return; // Use cached data
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch subscription data
|
||||
set({ isLoading: true, error: null });
|
||||
|
||||
// Get all price plans
|
||||
const plans: PricePlan[] = getAllPricePlans();
|
||||
const freePlan = plans.find((plan) => plan.isFree);
|
||||
const lifetimePlan = plans.find((plan) => plan.isLifetime);
|
||||
|
||||
// Check if user is a lifetime member directly from the database
|
||||
let isLifetimeMember = false;
|
||||
try {
|
||||
const result = await getLifetimeStatusAction({ userId: user.id });
|
||||
if (result?.data?.success) {
|
||||
isLifetimeMember = result.data.isLifetimeMember || false;
|
||||
console.log('fetchPayment, lifetime status', isLifetimeMember);
|
||||
} else {
|
||||
console.warn(
|
||||
'fetchPayment, lifetime status error',
|
||||
result?.data?.error
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('fetchPayment, lifetime status error:', error);
|
||||
}
|
||||
|
||||
// If lifetime member, set the lifetime plan
|
||||
if (isLifetimeMember) {
|
||||
console.log('fetchPayment, set lifetime plan');
|
||||
set({
|
||||
currentPlan: lifetimePlan || null,
|
||||
subscription: null,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
lastFetchTime: Date.now(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if user has an active subscription
|
||||
const result = await getActiveSubscriptionAction({ userId: user.id });
|
||||
if (result?.data?.success) {
|
||||
const activeSubscription = result.data.data;
|
||||
|
||||
// Set subscription state
|
||||
if (activeSubscription) {
|
||||
const plan =
|
||||
plans.find((p) =>
|
||||
p.prices.find(
|
||||
(price) => price.priceId === activeSubscription.priceId
|
||||
)
|
||||
) || null;
|
||||
console.log('fetchPayment, subscription found, set pro plan');
|
||||
set({
|
||||
currentPlan: plan,
|
||||
subscription: activeSubscription,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
lastFetchTime: Date.now(),
|
||||
});
|
||||
} else {
|
||||
// No subscription found - set to free plan
|
||||
console.log('fetchPayment, no subscription found, set free plan');
|
||||
set({
|
||||
currentPlan: freePlan || null,
|
||||
subscription: null,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
lastFetchTime: Date.now(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Failed to fetch subscription
|
||||
console.error(
|
||||
'fetchPayment, subscription for user failed',
|
||||
result?.data?.error
|
||||
);
|
||||
set({
|
||||
error: result?.data?.error || 'Failed to fetch payment data',
|
||||
isLoading: false,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('fetchPayment, error:', error);
|
||||
set({
|
||||
error: 'Failed to fetch payment data',
|
||||
isLoading: false,
|
||||
});
|
||||
} finally {
|
||||
set({ isLoading: false });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset payment state
|
||||
*/
|
||||
resetState: () => {
|
||||
set({
|
||||
currentPlan: null,
|
||||
subscription: null,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
lastFetchTime: null,
|
||||
});
|
||||
},
|
||||
}));
|
Loading…
Reference in New Issue
Block a user