- {t('freePlanMessage')}
+ {/* Lifetime plan message */}
+ {isLifetimeMember && (
+
+ {t('lifetimeMessage')}
+
+ )}
+
+ {/* Subscription plan message */}
+ {subscription && currentPrice && (
+
+
+ {t('price')}{' '}
+ {formatPrice(currentPrice.amount, currentPrice.currency)} /{' '}
+ {currentPrice.interval === PlanIntervals.MONTH
+ ? t('interval.month')
+ : currentPrice.interval === PlanIntervals.YEAR
+ ? t('interval.year')
+ : t('interval.oneTime')}
- )}
- {/* Lifetime plan message */}
- {isLifetimeMember && (
-
- {t('lifetimeMessage')}
-
- )}
-
- {/* Subscription plan message */}
- {subscription && currentPrice && (
-
+ {nextBillingDate && (
- {t('price')}{' '}
- {formatPrice(currentPrice.amount, currentPrice.currency)} /{' '}
- {currentPrice.interval === PlanIntervals.MONTH
- ? t('interval.month')
- : currentPrice.interval === PlanIntervals.YEAR
- ? t('interval.year')
- : t('interval.oneTime')}
+ {t('nextBillingDate')} {nextBillingDate}
+ )}
- {nextBillingDate && (
-
- {t('nextBillingDate')} {nextBillingDate}
+ {subscription.status === 'trialing' &&
+ subscription.currentPeriodEnd && (
+
+ {t('trialEnds')} {formatDate(subscription.currentPeriodEnd)}
)}
+
+ )}
+
+
+ {/* user is on free plan, show upgrade plan button */}
+ {isFreePlan && (
+
+ )}
- {subscription.status === 'trialing' &&
- subscription.currentPeriodEnd && (
-
- {t('trialEnds')} {formatDate(subscription.currentPeriodEnd)}
-
- )}
-
- )}
-
-
- {/* user is on free plan, show upgrade plan button */}
- {isFreePlan && (
-
- )}
+ {/* user is lifetime member, show manage billing button */}
+ {isLifetimeMember && currentUser && (
+
+ {t('manageBilling')}
+
+ )}
- {/* user is lifetime member, show manage billing button */}
- {isLifetimeMember && currentUser && (
-
- {t('manageBilling')}
-
- )}
-
- {/* user has subscription, show manage subscription button */}
- {subscription && currentUser && (
-
- {t('manageSubscription')}
-
- )}
-
-
-
+ {/* user has subscription, show manage subscription button */}
+ {subscription && currentUser && (
+
+ {t('manageSubscription')}
+
+ )}
+
+
);
}
diff --git a/src/components/settings/billing/credits-balance-card.tsx b/src/components/settings/billing/credits-balance-card.tsx
new file mode 100644
index 0000000..4483a6e
--- /dev/null
+++ b/src/components/settings/billing/credits-balance-card.tsx
@@ -0,0 +1,143 @@
+'use client';
+
+import { Button } from '@/components/ui/button';
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card';
+import { Skeleton } from '@/components/ui/skeleton';
+import { websiteConfig } from '@/config/website';
+import { useCredits } from '@/hooks/use-credits';
+import { LocaleLink, useLocaleRouter } from '@/i18n/navigation';
+import { cn } from '@/lib/utils';
+import { Routes } from '@/routes';
+import { CoinsIcon } from 'lucide-react';
+import { useTranslations } from 'next-intl';
+import { useSearchParams } from 'next/navigation';
+import { useEffect, useRef } from 'react';
+import { toast } from 'sonner';
+
+export default function CreditsBalanceCard() {
+ const t = useTranslations('Dashboard.settings.credits.balance');
+ const searchParams = useSearchParams();
+ const localeRouter = useLocaleRouter();
+ const hasHandledSession = useRef(false);
+
+ // Use the credits hook to get balance
+ const { balance, isLoading, error, refresh } = useCredits();
+
+ // Don't render if credits are disabled
+ if (!websiteConfig.credits.enableCredits) {
+ return null;
+ }
+
+ // Check for payment success and show success message
+ useEffect(() => {
+ const sessionId = searchParams.get('session_id');
+ if (sessionId && !hasHandledSession.current) {
+ hasHandledSession.current = true;
+ // Show success toast (delayed to avoid React lifecycle conflicts)
+ setTimeout(() => {
+ toast.success(t('creditsAdded'));
+ }, 0);
+
+ // Refresh credits data to show updated balance
+ refresh();
+
+ // Clean up URL parameters
+ const url = new URL(window.location.href);
+ url.searchParams.delete('session_id');
+ localeRouter.replace(Routes.SettingsBilling + url.search);
+ }
+ }, [searchParams, localeRouter, refresh]);
+
+ // Render loading skeleton
+ if (isLoading) {
+ return (
+
+
+ {t('title')}
+ {t('description')}
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ // Render error state
+ if (error) {
+ return (
+
+
+ {t('title')}
+ {t('description')}
+
+
+ {error}
+
+
+
+
+
+ );
+ }
+
+ return (
+
+
+ {t('title')}
+ {t('description')}
+
+
+ {/* Credits balance display */}
+
+
+
+
+ {balance.toLocaleString()}
+
+
+ {/*
{t('available')} */}
+
+
+ {/* Balance information */}
+ {/* {t('message')}
*/}
+
+
+
+
+
+ );
+}
diff --git a/src/components/settings/credits/credit-packages.tsx b/src/components/settings/credits/credit-packages.tsx
index f293186..8aedc1b 100644
--- a/src/components/settings/credits/credit-packages.tsx
+++ b/src/components/settings/credits/credit-packages.tsx
@@ -9,17 +9,12 @@ import {
CardTitle,
} from '@/components/ui/card';
import { getCreditPackages } from '@/config/credits-config';
-import { useCredits } from '@/hooks/use-credits';
+import { websiteConfig } from '@/config/website';
import { useCurrentUser } from '@/hooks/use-current-user';
-import { useLocaleRouter } from '@/i18n/navigation';
import { formatPrice } from '@/lib/formatter';
import { cn } from '@/lib/utils';
-import { Routes } from '@/routes';
-import { CircleCheckBigIcon, CoinsIcon, Loader2Icon } from 'lucide-react';
+import { CircleCheckBigIcon, CoinsIcon } from 'lucide-react';
import { useTranslations } from 'next-intl';
-import { useSearchParams } from 'next/navigation';
-import { useEffect, useRef } from 'react';
-import { toast } from 'sonner';
import { CreditCheckoutButton } from './credit-checkout-button';
/**
@@ -28,137 +23,89 @@ import { CreditCheckoutButton } from './credit-checkout-button';
*/
export function CreditPackages() {
const t = useTranslations('Dashboard.settings.credits.packages');
- const searchParams = useSearchParams();
- const localeRouter = useLocaleRouter();
- const hasHandledSession = useRef(false);
-
- // Use the new useCredits hook
- const { balance, isLoading, refresh } = useCredits();
// Get current user
const currentUser = useCurrentUser();
+ // Don't render if credits are disabled
+ if (!websiteConfig.credits.enableCredits) {
+ return null;
+ }
+
// show only enabled packages
const creditPackages = Object.values(getCreditPackages()).filter(
(pkg) => !pkg.disabled && pkg.price.priceId
);
- // Check for payment success and show success message
- useEffect(() => {
- const sessionId = searchParams.get('session_id');
- if (sessionId && !hasHandledSession.current) {
- hasHandledSession.current = true;
- // Show success toast (delayed to avoid React lifecycle conflicts)
- setTimeout(() => {
- toast.success(t('creditsAdded'));
- }, 0);
-
- // Refresh credits data to show updated balance
- refresh();
-
- // Clean up URL parameters
- const url = new URL(window.location.href);
- url.searchParams.delete('session_id');
- // Use Routes.SettingsCredits + url.search to properly handle locale routing
- localeRouter.replace(Routes.SettingsCredits + url.search);
- }
- }, [searchParams, localeRouter, refresh]);
-
return (
-
-
-
-
- {t('balance')}
-
-
-
-
-
- {/*
*/}
-
- {isLoading ? (
-
- ) : (
-
- {balance.toLocaleString()}
-
- )}
-
-
-
-
-
-
-
-
- {t('title')}
-
- {t('description')}
-
-
-
-
- {creditPackages.map((creditPackage) => (
-
- {creditPackage.popular && (
-
-
- {t('popular')}
-
-
- )}
-
-
- {/* Price and Credits - Left/Right Layout */}
-
-
-
-
- {creditPackage.credits.toLocaleString()}
-
-
-
-
- {formatPrice(
- creditPackage.price.amount,
- creditPackage.price.currency
- )}
-
-
-
-
-
-
- {creditPackage.description}
-
-
- {/* purchase button using checkout */}
-
+
+ {t('title')}
+
+ {t('description')}
+
+
+
+
+ {creditPackages.map((creditPackage) => (
+
+ {creditPackage.popular && (
+
+
- {t('purchase')}
-
-
-
- ))}
-
-
-
-
+ {t('popular')}
+
+
+ )}
+
+
+ {/* Price and Credits - Left/Right Layout */}
+
+
+
+
+ {creditPackage.credits.toLocaleString()}
+
+
+
+
+ {formatPrice(
+ creditPackage.price.amount,
+ creditPackage.price.currency
+ )}
+
+
+
+
+
+
+ {creditPackage.description}
+
+
+ {/* purchase button using checkout */}
+
+ {t('purchase')}
+
+
+
+ ))}
+
+
+
);
}