([]);
+ const [searchParams, setSearchParams] = useSearchParams();
+ const { refreshUser } = useAuthStore();
+
+ // Handle payment success redirect from Stripe
+ useEffect(() => {
+ const success = searchParams.get('success');
+ const sessionId = searchParams.get('session_id');
+
+ if (success === 'true') {
+ // Clear the query params to avoid re-triggering
+ setSearchParams({});
+
+ // Refresh user data to get updated account status
+ refreshUser().then(() => {
+ toast.success('Payment successful! Your subscription is now active.');
+ }).catch((err) => {
+ console.error('Failed to refresh user after payment:', err);
+ // Still show success message since payment was processed
+ toast.success('Payment successful! Please refresh the page to see updates.');
+ });
+ }
+ }, [searchParams, setSearchParams, refreshUser, toast]);
+
+ // Handle PayPal success redirect
+ useEffect(() => {
+ const paypal = searchParams.get('paypal');
+
+ if (paypal === 'success') {
+ setSearchParams({});
+ refreshUser().then(() => {
+ toast.success('PayPal payment successful! Your subscription is now active.');
+ }).catch(() => {
+ toast.success('PayPal payment successful! Please refresh the page to see updates.');
+ });
+ } else if (paypal === 'cancel') {
+ setSearchParams({});
+ toast.info('Payment was cancelled.');
+ }
+ }, [searchParams, setSearchParams, refreshUser, toast]);
useEffect(() => {
loadPlans();
diff --git a/frontend/src/pages/account/PlansAndBillingPage.tsx b/frontend/src/pages/account/PlansAndBillingPage.tsx
index 9cf81798..dac1656e 100644
--- a/frontend/src/pages/account/PlansAndBillingPage.tsx
+++ b/frontend/src/pages/account/PlansAndBillingPage.tsx
@@ -36,6 +36,7 @@ import PageMeta from '../../components/common/PageMeta';
import PageHeader from '../../components/common/PageHeader';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { usePageLoading } from '../../context/PageLoadingContext';
+import { formatCurrency } from '../../utils';
import {
getCreditBalance,
getCreditPackages,
@@ -711,7 +712,7 @@ export default function PlansAndBillingPage() {
|
{new Date(invoice.created_at).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
|
- ${invoice.total_amount} |
+ {formatCurrency(invoice.total_amount, invoice.currency)} |
{invoice.status}
diff --git a/frontend/src/store/authStore.ts b/frontend/src/store/authStore.ts
index 1d0edcb7..0178ef74 100644
--- a/frontend/src/store/authStore.ts
+++ b/frontend/src/store/authStore.ts
@@ -357,8 +357,20 @@ export const useAuthStore = create()(
throw new Error('Failed to save login session. Please try again.');
}
- // Return user data for success handling
- return userData;
+ // Return full response data for success handling (includes checkout_url for payment redirects)
+ console.log('Extracting checkout_url:', {
+ 'responseData.checkout_url': responseData.checkout_url,
+ 'data.checkout_url': data.checkout_url,
+ 'data.data?.checkout_url': data.data?.checkout_url,
+ responseData: responseData,
+ });
+
+ return {
+ ...userData,
+ checkout_url: responseData.checkout_url || data.checkout_url || data.data?.checkout_url,
+ checkout_session_id: responseData.checkout_session_id || data.checkout_session_id || data.data?.checkout_session_id,
+ paypal_order_id: responseData.paypal_order_id || data.paypal_order_id || data.data?.paypal_order_id,
+ };
} catch (error: any) {
// ALWAYS reset loading on error - critical to prevent stuck state
set({ loading: false });
diff --git a/frontend/src/utils/currency.ts b/frontend/src/utils/currency.ts
new file mode 100644
index 00000000..4196fccd
--- /dev/null
+++ b/frontend/src/utils/currency.ts
@@ -0,0 +1,68 @@
+/**
+ * Currency utilities for formatting amounts with proper symbols
+ */
+
+// Currency symbols map
+const CURRENCY_SYMBOLS: Record = {
+ USD: '$',
+ EUR: '€',
+ GBP: '£',
+ INR: '₹',
+ JPY: '¥',
+ CNY: '¥',
+ AUD: 'A$',
+ CAD: 'C$',
+ CHF: 'Fr',
+ SEK: 'kr',
+ NOK: 'kr',
+ DKK: 'kr',
+ PLN: 'zł',
+ BRL: 'R$',
+ ZAR: 'R',
+ AED: 'د.إ',
+ SAR: 'ر.س',
+ PKR: '₨',
+};
+
+/**
+ * Get currency symbol for a currency code
+ */
+export const getCurrencySymbol = (currencyCode?: string): string => {
+ if (!currencyCode) return '$';
+ const code = currencyCode.toUpperCase();
+ return CURRENCY_SYMBOLS[code] || `${code} `;
+};
+
+/**
+ * Format an amount with the proper currency symbol
+ * @param amount - The amount to format (string or number)
+ * @param currency - The currency code (e.g., 'USD', 'PKR')
+ * @param showDecimals - Whether to show decimal places (default: true for most currencies)
+ */
+export const formatCurrency = (
+ amount: string | number,
+ currency?: string,
+ showDecimals: boolean = true
+): string => {
+ const numAmount = typeof amount === 'string' ? parseFloat(amount) : amount;
+ if (isNaN(numAmount)) return '-';
+
+ const symbol = getCurrencySymbol(currency);
+
+ // For zero-decimal currencies like JPY, don't show decimals
+ const zeroDecimalCurrencies = ['JPY', 'KRW', 'VND'];
+ const shouldShowDecimals = showDecimals && !zeroDecimalCurrencies.includes(currency?.toUpperCase() || '');
+
+ const formatted = shouldShowDecimals
+ ? numAmount.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
+ : numAmount.toLocaleString('en-US', { maximumFractionDigits: 0 });
+
+ return `${symbol}${formatted}`;
+};
+
+/**
+ * Format price for display (typically USD)
+ */
+export const formatPrice = (price: string | number): string => {
+ return formatCurrency(price, 'USD');
+};
diff --git a/frontend/src/utils/date.ts b/frontend/src/utils/date.ts
index f9bd7ee2..0bd140cb 100644
--- a/frontend/src/utils/date.ts
+++ b/frontend/src/utils/date.ts
@@ -50,3 +50,46 @@ export function formatRelativeDate(dateString: string | Date): string {
}
}
+/**
+ * Format date to a standard display format
+ * @param dateString - ISO date string or Date object
+ * @param options - Intl.DateTimeFormat options
+ * @returns Formatted date string (e.g., "Jan 7, 2026")
+ */
+export function formatDate(
+ dateString: string | Date | null | undefined,
+ options: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric', year: 'numeric' }
+): string {
+ if (!dateString) return '-';
+
+ const date = typeof dateString === 'string' ? new Date(dateString) : dateString;
+
+ if (isNaN(date.getTime())) return '-';
+
+ return date.toLocaleDateString('en-US', options);
+}
+
+/**
+ * Format date and time to a standard display format
+ * @param dateString - ISO date string or Date object
+ * @returns Formatted date and time string (e.g., "Jan 7, 2026, 3:30 PM")
+ */
+export function formatDateTime(
+ dateString: string | Date | null | undefined
+): string {
+ if (!dateString) return '-';
+
+ const date = typeof dateString === 'string' ? new Date(dateString) : dateString;
+
+ if (isNaN(date.getTime())) return '-';
+
+ return date.toLocaleDateString('en-US', {
+ month: 'short',
+ day: 'numeric',
+ year: 'numeric',
+ hour: 'numeric',
+ minute: '2-digit',
+ hour12: true
+ });
+}
+
diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts
index edc33d3e..d17a9b1e 100644
--- a/frontend/src/utils/index.ts
+++ b/frontend/src/utils/index.ts
@@ -27,5 +27,12 @@ export {
formatDateTime,
} from './date';
+// Currency utilities
+export {
+ getCurrencySymbol,
+ formatCurrency,
+ formatPrice,
+} from './currency';
+
// Add other global utilities here as needed
|