diff --git a/apps/web/src/components/payment/PaymentFlow.tsx b/apps/web/src/components/payment/PaymentFlow.tsx index 4a38aeae..c9eda91e 100644 --- a/apps/web/src/components/payment/PaymentFlow.tsx +++ b/apps/web/src/components/payment/PaymentFlow.tsx @@ -6,7 +6,7 @@ import { useRazorpay } from "@/hooks/useRazorpay"; import type { RazorpayOptions } from "@/lib/razorpay"; import PrimaryButton from "@/components/ui/custom-button"; import { useSession } from "next-auth/react"; -import { useRouter } from "next/navigation"; +import { useRouter, usePathname } from "next/navigation"; interface PaymentFlowProps { planId: string; // Required: Plan ID from database @@ -46,6 +46,7 @@ const PaymentFlow: React.FC = ({ }) => { const { data: session, status: sessionStatus } = useSession(); const router = useRouter(); + const pathname = usePathname(); const [isProcessing, setIsProcessing] = useState(false); const orderDataRef = useRef<{ orderId: string; @@ -85,6 +86,7 @@ const PaymentFlow: React.FC = ({ new Promise((resolve) => setTimeout(resolve, 3000)), // 3s timeout ]); } catch (refreshError) { + // log refresh errors separately without affecting payment flow console.warn( "subscription cache refresh failed (non-fatal):", refreshError @@ -123,8 +125,13 @@ const PaymentFlow: React.FC = ({ } if (sessionStatus === "unauthenticated" || !session) { - const redirectUrl = callbackUrl || "/pricing"; - router.push(`/login?callbackUrl=${encodeURIComponent(redirectUrl)}`); + router.push(`/login?callbackUrl=${encodeURIComponent(pathname)}`); + return; + } + + // check if session has accessToken - if not, re-authenticate + if (!session.accessToken) { + router.push(`/login?callbackUrl=${encodeURIComponent(pathname)}`); return; } @@ -173,10 +180,26 @@ const PaymentFlow: React.FC = ({ await initiatePayment(options); } catch (error: any) { - console.warn("Failed to create order:", error); + console.error("Failed to create order:", error); setIsProcessing(false); - const redirectUrl = callbackUrl || "/pricing"; - router.push(`/login?callbackUrl=${encodeURIComponent(redirectUrl)}`); + + // only redirect to login for authentication errors + const errorMsg = error?.message?.toLowerCase() || ""; + const isAuthError = + error?.data?.code === "UNAUTHORIZED" || + errorMsg.includes("unauthorized") || + errorMsg.includes("not authenticated") || + errorMsg.includes("authentication failed") || + errorMsg.includes("missing or invalid authorization"); + + if (isAuthError) { + router.push(`/login?callbackUrl=${encodeURIComponent(pathname)}`); + } else { + // show error message for non-auth errors + const errorMessage = + error?.message || "Failed to process payment. Please try again."; + alert(errorMessage); + } } }; diff --git a/apps/web/src/hooks/useSubscription.ts b/apps/web/src/hooks/useSubscription.ts index 943c3794..83925371 100644 --- a/apps/web/src/hooks/useSubscription.ts +++ b/apps/web/src/hooks/useSubscription.ts @@ -19,18 +19,21 @@ export function useSubscription() { isLoading, } = useSubscriptionStore(); + const utils = trpc.useUtils(); + // Fetch subscription status using tRPC const { data, isLoading: isFetching, isError, isFetched, + refetch, } = (trpc.user as any).subscriptionStatus.useQuery(undefined, { enabled: !!session?.user && status === "authenticated", - refetchOnWindowFocus: false, + refetchOnWindowFocus: true, // refetch when user returns to tab refetchOnMount: true, - staleTime: 5 * 60 * 1000, // Consider data fresh for 5 minutes - gcTime: 10 * 60 * 1000, // Keep in cache for 10 minutes + staleTime: 2 * 60 * 1000, // consider data fresh for 2 minutes (reduced from 5) + gcTime: 10 * 60 * 1000, // keep in cache for 10 minutes }); useEffect(() => { @@ -69,9 +72,16 @@ export function useSubscription() { reset, ]); + // manual refetch function for immediate cache invalidation + const refetchSubscription = async () => { + await (utils.user as any).subscriptionStatus.invalidate(); + await refetch(); + }; + return { isPaidUser, subscription, isLoading, + refetchSubscription, // expose manual refetch function }; } diff --git a/apps/web/src/lib/trpc-server.ts b/apps/web/src/lib/trpc-server.ts index fb8cebbd..3e757946 100644 --- a/apps/web/src/lib/trpc-server.ts +++ b/apps/web/src/lib/trpc-server.ts @@ -10,7 +10,7 @@ export const serverTrpc = createTRPCProxyClient({ links: [ httpBatchLink({ transformer: superjson, - url: `${process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000"}/trpc`, + url: `${process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080"}/trpc`, headers() { return {}; }, @@ -26,7 +26,7 @@ export function createAuthenticatedClient(session: Session) { links: [ httpBatchLink({ transformer: superjson, - url: `${process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000"}/trpc`, + url: `${process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080"}/trpc`, headers() { const token = session.accessToken; if (token) { diff --git a/apps/web/src/providers/trpc-provider.tsx b/apps/web/src/providers/trpc-provider.tsx index 356c9f96..dec193c8 100644 --- a/apps/web/src/providers/trpc-provider.tsx +++ b/apps/web/src/providers/trpc-provider.tsx @@ -23,7 +23,7 @@ export function TRPCProvider({ children }: { children: React.ReactNode }) { // Recreate client when session changes to ensure we get the latest token const trpcClient = useMemo(() => { - const baseUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000"; + const baseUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080"; const trpcUrl = baseUrl.endsWith("/trpc") ? baseUrl : `${baseUrl}/trpc`; return trpc.createClient({