Repository: BetrixDev/flowble
Pull request #8: success url Update Routes, Navigation, and Checkout for Payment Success Flow
Author: login: BetrixDev
Created: 2025-03-01T01:32:50Z | Merged: 2025-03-01T01:51:01Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Expanded pre-rendering to cover additional pages for a smoother navigation experience.
  - Enhanced the top navigation with an optional display for account tier badges.
  - Launched a new payment success page featuring real-time status updates, progress indicators, and automated redirection.
  - Improved checkout flow with a dedicated success page.
  - Added celebratory visual effects with a new dependency for confetti animations.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/app.config.ts b/app.config.ts
index d1eee4b..2e874af 100644
--- a/app.config.ts
+++ b/app.config.ts
@@ -12,7 +12,7 @@ export default defineConfig({
   server: {
     preset: "vercel",
     prerender: {
-      routes: ["/", "/pricing"],
+      routes: ["/", "/pricing", "/success", "/videos"],
     },
   },
 });
diff --git a/app/components/top-nav.tsx b/app/components/top-nav.tsx
index 6dedd71..9961887 100644
--- a/app/components/top-nav.tsx
+++ b/app/components/top-nav.tsx
@@ -17,7 +17,11 @@ import { AccountTierText } from "./account-tier-text";
 import { Badge } from "./ui/badge";
 import { safeParseAccountTier, TIER_TO_TEXT } from "@/lib/utils";
 
-export default function TopNav() {
+type TopNavProps = {
+  showTierPill?: boolean;
+};
+
+export default function TopNav({ showTierPill = true }: TopNavProps) {
   const { user } = useUser();
 
   const accountTier = safeParseAccountTier(user?.publicMetadata?.accountTier);
@@ -142,14 +146,16 @@ export default function TopNav() {
         </SignedOut>
         <SignedIn>
           <span className="md:flex hidden items-center gap-2">
-            <Badge variant="outline" className="h-8">
-              <AccountTierText
-                accountTier={accountTier}
-                className="pointer-events-none"
-              >
-                {TIER_TO_TEXT[accountTier]} Tier
-              </AccountTierText>
-            </Badge>
+            {showTierPill && (
+              <Badge variant="outline" className="h-8">
+                <AccountTierText
+                  accountTier={accountTier}
+                  className="pointer-events-none"
+                >
+                  {TIER_TO_TEXT[accountTier]} Tier
+                </AccountTierText>
+              </Badge>
+            )}
 
             <UserButton />
           </span>
diff --git a/app/routeTree.gen.ts b/app/routeTree.gen.ts
index 640e5bc..137d30e 100644
--- a/app/routeTree.gen.ts
+++ b/app/routeTree.gen.ts
@@ -10,180 +10,202 @@
 
 // Import Routes
 
-import { Route as rootRoute } from "./routes/__root";
-import { Route as VideosImport } from "./routes/videos";
-import { Route as PricingImport } from "./routes/pricing";
-import { Route as IndexImport } from "./routes/index";
-import { Route as SignUpSplatImport } from "./routes/sign-up.$";
-import { Route as SignInSplatImport } from "./routes/sign-in.$";
-import { Route as PVideoIdImport } from "./routes/p.$videoId";
+import { Route as rootRoute } from './routes/__root'
+import { Route as VideosImport } from './routes/videos'
+import { Route as SuccessImport } from './routes/success'
+import { Route as PricingImport } from './routes/pricing'
+import { Route as IndexImport } from './routes/index'
+import { Route as SignUpSplatImport } from './routes/sign-up.$'
+import { Route as SignInSplatImport } from './routes/sign-in.$'
+import { Route as PVideoIdImport } from './routes/p.$videoId'
 
 // Create/Update Routes
 
 const VideosRoute = VideosImport.update({
-  id: "/videos",
-  path: "/videos",
+  id: '/videos',
+  path: '/videos',
   getParentRoute: () => rootRoute,
-} as any);
+} as any)
+
+const SuccessRoute = SuccessImport.update({
+  id: '/success',
+  path: '/success',
+  getParentRoute: () => rootRoute,
+} as any)
 
 const PricingRoute = PricingImport.update({
-  id: "/pricing",
-  path: "/pricing",
+  id: '/pricing',
+  path: '/pricing',
   getParentRoute: () => rootRoute,
-} as any);
+} as any)
 
 const IndexRoute = IndexImport.update({
-  id: "/",
-  path: "/",
+  id: '/',
+  path: '/',
   getParentRoute: () => rootRoute,
-} as any);
+} as any)
 
 const SignUpSplatRoute = SignUpSplatImport.update({
-  id: "/sign-up/$",
-  path: "/sign-up/$",
+  id: '/sign-up/$',
+  path: '/sign-up/$',
   getParentRoute: () => rootRoute,
-} as any);
+} as any)
 
 const SignInSplatRoute = SignInSplatImport.update({
-  id: "/sign-in/$",
-  path: "/sign-in/$",
+  id: '/sign-in/$',
+  path: '/sign-in/$',
   getParentRoute: () => rootRoute,
-} as any);
+} as any)
 
 const PVideoIdRoute = PVideoIdImport.update({
-  id: "/p/$videoId",
-  path: "/p/$videoId",
+  id: '/p/$videoId',
+  path: '/p/$videoId',
   getParentRoute: () => rootRoute,
-} as any);
+} as any)
 
 // Populate the FileRoutesByPath interface
 
-declare module "@tanstack/react-router" {
+declare module '@tanstack/react-router' {
   interface FileRoutesByPath {
-    "/": {
-      id: "/";
-      path: "/";
-      fullPath: "/";
-      preLoaderRoute: typeof IndexImport;
-      parentRoute: typeof rootRoute;
-    };
-    "/pricing": {
-      id: "/pricing";
-      path: "/pricing";
-      fullPath: "/pricing";
-      preLoaderRoute: typeof PricingImport;
-      parentRoute: typeof rootRoute;
-    };
-    "/videos": {
-      id: "/videos";
-      path: "/videos";
-      fullPath: "/videos";
-      preLoaderRoute: typeof VideosImport;
-      parentRoute: typeof rootRoute;
-    };
-    "/p/$videoId": {
-      id: "/p/$videoId";
-      path: "/p/$videoId";
-      fullPath: "/p/$videoId";
-      preLoaderRoute: typeof PVideoIdImport;
-      parentRoute: typeof rootRoute;
-    };
-    "/sign-in/$": {
-      id: "/sign-in/$";
-      path: "/sign-in/$";
-      fullPath: "/sign-in/$";
-      preLoaderRoute: typeof SignInSplatImport;
-      parentRoute: typeof rootRoute;
-    };
-    "/sign-up/$": {
-      id: "/sign-up/$";
-      path: "/sign-up/$";
-      fullPath: "/sign-up/$";
-      preLoaderRoute: typeof SignUpSplatImport;
-      parentRoute: typeof rootRoute;
-    };
+    '/': {
+      id: '/'
+      path: '/'
+      fullPath: '/'
+      preLoaderRoute: typeof IndexImport
+      parentRoute: typeof rootRoute
+    }
+    '/pricing': {
+      id: '/pricing'
+      path: '/pricing'
+      fullPath: '/pricing'
+      preLoaderRoute: typeof PricingImport
+      parentRoute: typeof rootRoute
+    }
+    '/success': {
+      id: '/success'
+      path: '/success'
+      fullPath: '/success'
+      preLoaderRoute: typeof SuccessImport
+      parentRoute: typeof rootRoute
+    }
+    '/videos': {
+      id: '/videos'
+      path: '/videos'
+      fullPath: '/videos'
+      preLoaderRoute: typeof VideosImport
+      parentRoute: typeof rootRoute
+    }
+    '/p/$videoId': {
+      id: '/p/$videoId'
+      path: '/p/$videoId'
+      fullPath: '/p/$videoId'
+      preLoaderRoute: typeof PVideoIdImport
+      parentRoute: typeof rootRoute
+    }
+    '/sign-in/$': {
+      id: '/sign-in/$'
+      path: '/sign-in/$'
+      fullPath: '/sign-in/$'
+      preLoaderRoute: typeof SignInSplatImport
+      parentRoute: typeof rootRoute
+    }
+    '/sign-up/$': {
+      id: '/sign-up/$'
+      path: '/sign-up/$'
+      fullPath: '/sign-up/$'
+      preLoaderRoute: typeof SignUpSplatImport
+      parentRoute: typeof rootRoute
+    }
   }
 }
 
 // Create and export the route tree
 
 export interface FileRoutesByFullPath {
-  "/": typeof IndexRoute;
-  "/pricing": typeof PricingRoute;
-  "/videos": typeof VideosRoute;
-  "/p/$videoId": typeof PVideoIdRoute;
-  "/sign-in/$": typeof SignInSplatRoute;
-  "/sign-up/$": typeof SignUpSplatRoute;
+  '/': typeof IndexRoute
+  '/pricing': typeof PricingRoute
+  '/success': typeof SuccessRoute
+  '/videos': typeof VideosRoute
+  '/p/$videoId': typeof PVideoIdRoute
+  '/sign-in/$': typeof SignInSplatRoute
+  '/sign-up/$': typeof SignUpSplatRoute
 }
 
 export interface FileRoutesByTo {
-  "/": typeof IndexRoute;
-  "/pricing": typeof PricingRoute;
-  "/videos": typeof VideosRoute;
-  "/p/$videoId": typeof PVideoIdRoute;
-  "/sign-in/$": typeof SignInSplatRoute;
-  "/sign-up/$": typeof SignUpSplatRoute;
+  '/': typeof IndexRoute
+  '/pricing': typeof PricingRoute
+  '/success': typeof SuccessRoute
+  '/videos': typeof VideosRoute
+  '/p/$videoId': typeof PVideoIdRoute
+  '/sign-in/$': typeof SignInSplatRoute
+  '/sign-up/$': typeof SignUpSplatRoute
 }
 
 export interface FileRoutesById {
-  __root__: typeof rootRoute;
-  "/": typeof IndexRoute;
-  "/pricing": typeof PricingRoute;
-  "/videos": typeof VideosRoute;
-  "/p/$videoId": typeof PVideoIdRoute;
-  "/sign-in/$": typeof SignInSplatRoute;
-  "/sign-up/$": typeof SignUpSplatRoute;
+  __root__: typeof rootRoute
+  '/': typeof IndexRoute
+  '/pricing': typeof PricingRoute
+  '/success': typeof SuccessRoute
+  '/videos': typeof VideosRoute
+  '/p/$videoId': typeof PVideoIdRoute
+  '/sign-in/$': typeof SignInSplatRoute
+  '/sign-up/$': typeof SignUpSplatRoute
 }
 
 export interface FileRouteTypes {
-  fileRoutesByFullPath: FileRoutesByFullPath;
+  fileRoutesByFullPath: FileRoutesByFullPath
   fullPaths:
-    | "/"
-    | "/pricing"
-    | "/videos"
-    | "/p/$videoId"
-    | "/sign-in/$"
-    | "/sign-up/$";
-  fileRoutesByTo: FileRoutesByTo;
+    | '/'
+    | '/pricing'
+    | '/success'
+    | '/videos'
+    | '/p/$videoId'
+    | '/sign-in/$'
+    | '/sign-up/$'
+  fileRoutesByTo: FileRoutesByTo
   to:
-    | "/"
-    | "/pricing"
-    | "/videos"
-    | "/p/$videoId"
-    | "/sign-in/$"
-    | "/sign-up/$";
+    | '/'
+    | '/pricing'
+    | '/success'
+    | '/videos'
+    | '/p/$videoId'
+    | '/sign-in/$'
+    | '/sign-up/$'
   id:
-    | "__root__"
-    | "/"
-    | "/pricing"
-    | "/videos"
-    | "/p/$videoId"
-    | "/sign-in/$"
-    | "/sign-up/$";
-  fileRoutesById: FileRoutesById;
+    | '__root__'
+    | '/'
+    | '/pricing'
+    | '/success'
+    | '/videos'
+    | '/p/$videoId'
+    | '/sign-in/$'
+    | '/sign-up/$'
+  fileRoutesById: FileRoutesById
 }
 
 export interface RootRouteChildren {
-  IndexRoute: typeof IndexRoute;
-  PricingRoute: typeof PricingRoute;
-  VideosRoute: typeof VideosRoute;
-  PVideoIdRoute: typeof PVideoIdRoute;
-  SignInSplatRoute: typeof SignInSplatRoute;
-  SignUpSplatRoute: typeof SignUpSplatRoute;
+  IndexRoute: typeof IndexRoute
+  PricingRoute: typeof PricingRoute
+  SuccessRoute: typeof SuccessRoute
+  VideosRoute: typeof VideosRoute
+  PVideoIdRoute: typeof PVideoIdRoute
+  SignInSplatRoute: typeof SignInSplatRoute
+  SignUpSplatRoute: typeof SignUpSplatRoute
 }
 
 const rootRouteChildren: RootRouteChildren = {
   IndexRoute: IndexRoute,
   PricingRoute: PricingRoute,
+  SuccessRoute: SuccessRoute,
   VideosRoute: VideosRoute,
   PVideoIdRoute: PVideoIdRoute,
   SignInSplatRoute: SignInSplatRoute,
   SignUpSplatRoute: SignUpSplatRoute,
-};
+}
 
 export const routeTree = rootRoute
   ._addFileChildren(rootRouteChildren)
-  ._addFileTypes<FileRouteTypes>();
+  ._addFileTypes<FileRouteTypes>()
 
 /* ROUTE_MANIFEST_START
 {
@@ -193,6 +215,7 @@ export const routeTree = rootRoute
       "children": [
         "/",
         "/pricing",
+        "/success",
         "/videos",
         "/p/$videoId",
         "/sign-in/$",
@@ -205,6 +228,9 @@ export const routeTree = rootRoute
     "/pricing": {
       "filePath": "pricing.tsx"
     },
+    "/success": {
+      "filePath": "success.tsx"
+    },
     "/videos": {
       "filePath": "videos.tsx"
     },
diff --git a/app/routes/success.tsx b/app/routes/success.tsx
new file mode 100644
index 0000000..9a5ea63
--- /dev/null
+++ b/app/routes/success.tsx
@@ -0,0 +1,221 @@
+import { Footer } from "@/components/footer";
+import TopNav from "@/components/top-nav";
+import { HeroHighlight } from "@/components/ui/hero-highlight";
+import { getAuth } from "@clerk/tanstack-start/server";
+import { useQuery } from "@tanstack/react-query";
+import { createFileRoute, redirect, useRouter } from "@tanstack/react-router";
+import { createServerFn, useServerFn } from "@tanstack/start";
+import { getWebRequest } from "@tanstack/start/server";
+import { useRealtimeRun } from "@trigger.dev/react-hooks";
+import { runs, auth as triggerAuth } from "@trigger.dev/sdk/v3";
+import { CheckCircleIcon, Loader2Icon, XCircleIcon } from "lucide-react";
+import { useEffect, useState } from "react";
+import Confetti from "react-confetti-boom";
+import { z } from "zod";
+
+export const Route = createFileRoute("/success")({
+  component: RouteComponent,
+});
+
+const ERROR_STATUSES = [
+  "TIMED_OUT",
+  "EXPIRED",
+  "SYSTEM_FAILURE",
+  "INTERRUPTED",
+  "CRASHED",
+  "FAILED",
+  "CANCELED",
+];
+
+const numberSchema = z.number().min(0).max(100);
+
+type ProcessingStatus = "loading" | "success" | "error";
+
+function RouteComponent() {
+  const navigate = Route.useNavigate();
+
+  const getPolarEventHandlerForUserServer = useServerFn(
+    getPolarEventHandlerForUserServerFn
+  );
+
+  const [status, setStatus] = useState<ProcessingStatus>("loading");
+  const [errorMessage, setErrorMessage] = useState("");
+  const [progress, setProgress] = useState(0);
+
+  const { data, isLoading, isError } = useQuery({
+    queryKey: ["success", "polarEventHandler"],
+    queryFn: async () => {
+      const data = await getPolarEventHandlerForUserServer();
+
+      return data;
+    },
+  });
+
+  const { run } = useRealtimeRun(data?.runId, {
+    accessToken: data?.token,
+    enabled: !!data?.runId && !!data?.token && !isLoading,
+  });
+
+  useEffect(() => {
+    if (!isError) {
+      return;
+    }
+
+    setStatus("error");
+    setErrorMessage(
+      "Failed to fetch payment status. Don't worry, your payment will be processed soon"
+    );
+    setTimeout(() => {
+      navigate({ to: "/videos" });
+    }, 5000);
+  }, [isError, navigate]);
+
+  useEffect(() => {
+    if (!run?.metadata?.progress) {
+      return;
+    }
+
+    const { data: progressValue } = numberSchema.safeParse(
+      run.metadata.progress
+    );
+
+    if (progressValue) {
+      setProgress(progressValue);
+    }
+  }, [run]);
+
+  useEffect(() => {
+    if (!run?.status) {
+      return;
+    }
+
+    if (run.status === "COMPLETED") {
+      setStatus("success");
+      setTimeout(() => {
+        navigate({ to: "/videos" });
+      }, 3000);
+      return;
+    }
+
+    if (ERROR_STATUSES.includes(run.status)) {
+      setStatus("error");
+      setErrorMessage(
+        "An error occurred while processing your payment. Please contact support"
+      );
+
+      setTimeout(() => {
+        navigate({ to: "/videos" });
+      }, 7500);
+
+      return;
+    }
+  }, [run, navigate]);
+
+  return (
+    <HeroHighlight>
+      <div className="absolute inset-0">
+        {status === "success" && (
+          <Confetti
+            particleCount={75}
+            shapeSize={15}
+            deg={250}
+            effectCount={1}
+            spreadDeg={100}
+            x={0.5}
+            y={0.5}
+            launchSpeed={1}
+          />
+        )}
+      </div>
+      <TopNav showTierPill={false} />
+      <div className="w-full min-h-[calc(100dvh-8rem)] flex items-center justify-center">
+        {/* Status Card */}
+        <div className="bg-zinc-900 border border-zinc-800 rounded-xl p-8 shadow-lg">
+          {/* Header */}
+          <div className="text-center mb-6">
+            <h1 className="text-2xl font-bold mb-2">
+              {status === "loading" && "Processing Your Order"}
+              {status === "success" && "Order Complete!"}
+              {status === "error" && "Processing Error"}
+            </h1>
+            <p className="text-zinc-400">
+              {status === "loading" &&
+                "Please wait while we process your payment..."}
+              {status === "success" &&
+                "Your payment has been processed successfully."}
+              {status === "error" && errorMessage}
+            </p>
+          </div>
+
+          {/* Status Icon */}
+          <div className="flex justify-center mb-6">
+            {status === "loading" && (
+              <div className="relative">
+                <Loader2Icon className="h-20 w-20 text-red-500 animate-spin" />
+                <div className="absolute inset-0 flex items-center justify-center">
+                  <span className="text-sm font-medium">{progress}%</span>
+                </div>
+              </div>
+            )}
+            {status === "success" && (
+              <CheckCircleIcon className="h-20 w-20 text-green-500" />
+            )}
+            {status === "error" && (
+              <XCircleIcon className="h-20 w-20 text-red-500" />
+            )}
+          </div>
+
+          {/* Progress Bar (only shown during loading) */}
+          {status === "loading" && (
+            <div className="w-full bg-zinc-800 rounded-full h-2 mb-6">
+              <div
+                className="bg-red-500 h-2 rounded-full transition-all duration-300 ease-in-out"
+                style={{ width: `${progress}%` }}
+              />
+            </div>
+          )}
+        </div>
+      </div>
+      <Footer />
+    </HeroHighlight>
+  );
+}
+
+const getPolarEventHandlerForUserServerFn = createServerFn({
+  method: "POST",
+}).handler(async () => {
+  try {
+    const { userId } = await getAuth(getWebRequest()!);
+
+    if (!userId) {
+      throw redirect({ to: "/" });
+    }
+
+    const runningTasks = await runs.list({ tag: userId });
+
+    const polarEventHandler = runningTasks.data.find(
+      (t) => t.taskIdentifier === "polar-event-handler"
+    );
+
+    if (!polarEventHandler) {
+      throw redirect({ to: "/videos" });
+    }
+
+    const accessToken = await triggerAuth.createPublicToken({
+      expirationTime: 1000 * 60 * 5,
+      scopes: {
+        read: {
+          tags: [userId],
+        },
+      },
+    });
+
+    return {
+      token: accessToken,
+      runId: polarEventHandler.id,
+    };
+  } catch (error) {
+    console.error("Error in getPolarEventHandlerForUserServerFn:", error);
+    throw redirect({ to: "/videos" });
+  }
+});
diff --git a/app/server-fns/polar.ts b/app/server-fns/polar.ts
index c42ab9a..b11e022 100644
--- a/app/server-fns/polar.ts
+++ b/app/server-fns/polar.ts
@@ -133,6 +133,7 @@ export const getCheckoutUrlServerFn = createServerFn({
       productId: selectedPrice.id,
       customerId: user.polarCustomerId,
       customerEmail: user.email,
+      successUrl: "https://www.slipstream.video/success",
     });
 
     return {
diff --git a/app/trigger/handle-polar-event.ts b/app/trigger/handle-polar-event.ts
index 4ed76af..a143d65 100644
--- a/app/trigger/handle-polar-event.ts
+++ b/app/trigger/handle-polar-event.ts
@@ -1,6 +1,7 @@
 import {
   AbortTaskRunError,
   logger,
+  metadata,
   schemaTask,
   tasks,
 } from "@trigger.dev/sdk/v3";
@@ -28,6 +29,8 @@ export const handlePolarEventTask = schemaTask({
     preset: "micro",
   },
   run: async ({ event }) => {
+    metadata.set("progress", 0);
+
     const { env } = await import("../lib/env");
 
     const clerk = createClerkClient({ secretKey: env.CLERK_SECRET_KEY });
@@ -35,6 +38,8 @@ export const handlePolarEventTask = schemaTask({
     if (event.type === "subscription.active") {
       logger.info("Subscription active");
 
+      metadata.set("progress", 15);
+
       const user = await db.query.users.findFirst({
         where: (table, { eq }) =>
           eq(table.polarCustomerId, event.data.customer.id),
@@ -44,6 +49,8 @@ export const handlePolarEventTask = schemaTask({
         },
       });
 
+      metadata.set("progress", 33);
+
       if (!user) {
         throw new AbortTaskRunError("User not found");
       }
@@ -63,6 +70,8 @@ export const handlePolarEventTask = schemaTask({
           .where(eq(users.id, user.id)),
       ];
 
+      metadata.set("progress", 45);
+
       if (oldAccountTier === "free") {
         dbCalls.push(
           db
@@ -81,6 +90,8 @@ export const handlePolarEventTask = schemaTask({
         // @ts-expect-error drizzle has stricter types than we need for these batch calls
         db.batch(dbCalls),
       ]);
+
+      metadata.set("progress", 70);
     } else if (event.type === "subscription.revoked") {
       logger.info("Subscription revoked");
 
@@ -151,5 +162,7 @@ export const handlePolarEventTask = schemaTask({
     } else {
       throw new AbortTaskRunError("Unknown event type");
     }
+
+    metadata.set("progress", 100);
   },
 });
diff --git a/package.json b/package.json
index f11af3d..fa3156a 100644
--- a/package.json
+++ b/package.json
@@ -69,6 +69,7 @@
     "mini-svg-data-uri": "^1.4.4",
     "nanoid": "^5.0.7",
     "react": "19.0.0-rc-66855b96-20241106",
+    "react-confetti-boom": "^1.1.2",
     "react-dom": "19.0.0-rc-66855b96-20241106",
     "sharp": "^0.33.5",
     "sonner": "^1.5.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a8756de..325cb8e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -167,6 +167,9 @@ importers:
       react:
         specifier: 19.0.0-rc-66855b96-20241106
         version: 19.0.0-rc-66855b96-20241106
+      react-confetti-boom:
+        specifier: ^1.1.2
+        version: 1.1.2(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106)
       react-dom:
         specifier: 19.0.0-rc-66855b96-20241106
         version: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106)
@@ -5886,6 +5889,13 @@ packages:
   rc9@2.1.2:
     resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==}
 
+  react-confetti-boom@1.1.2:
+    resolution: {integrity: sha512-JeJojpoIcF5xcNhkM3ifCW+XL6ScTOJUrr4eTLcrwa0rrLqc9hW3DTyn2IJ806PGWJHp2PjpMyWS2h0SAoadzg==}
+    engines: {node: '>=16.0.0'}
+    peerDependencies:
+      react: '>=16.8.0'
+      react-dom: '>=16.8.0'
+
   react-dom@19.0.0-rc-66855b96-20241106:
     resolution: {integrity: sha512-D25vdaytZ1wFIRiwNU98NPQ/upS2P8Co4/oNoa02PzHbh8deWdepjm5qwZM/46OdSiGv4WSWwxP55RO9obqJEQ==}
     peerDependencies:
@@ -13553,6 +13563,11 @@ snapshots:
       defu: 6.1.4
       destr: 2.0.3
 
+  react-confetti-boom@1.1.2(react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106))(react@19.0.0-rc-66855b96-20241106):
+    dependencies:
+      react: 19.0.0-rc-66855b96-20241106
+      react-dom: 19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106)
+
   react-dom@19.0.0-rc-66855b96-20241106(react@19.0.0-rc-66855b96-20241106):
     dependencies:
       react: 19.0.0-rc-66855b96-20241106


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>app/server-fns/polar.ts (1)</summary><blockquote>

`136-136`: **Consider using environment-based URL configuration.**

The success URL is hardcoded with the production domain. Consider making this URL configurable based on the environment for better maintainability across development, staging, and production environments.

```diff
-      successUrl: "https://www.slipstream.video/success",
+      successUrl: `${process.env.NODE_ENV === "production" ? "https://www.slipstream.video" : "http://localhost:3001"}/success`,
```

</blockquote></details>
<details>
<summary>app/routes/success.tsx (3)</summary><blockquote>

`1-15`: **Imports look good, but consider organizing them.**

All necessary dependencies are imported. The imports include UI components, authentication, routing, and state management libraries.


Consider organizing imports by category (React, UI components, utilities, etc.) for better readability.

---

`73-96`: **Status update logic could be more robust.**

The status update logic correctly handles successful and error states but doesn't have a fallback for unknown statuses.

Consider adding a fallback for unknown statuses to handle unexpected scenarios:

```diff
  useEffect(() => {
    if (!run?.status) {
      return;
    }

    if (run.status === "COMPLETED") {
      setStatus("success");
      navigate({ to: "/videos" });
      return;
    }

    if (ERROR_STATUSES.includes(run.status)) {
      setStatus("error");
      setErrorMessage(
        "An error occurred while processing your payment. Please contact support"
      );

      setTimeout(() => {
        navigate({ to: "/videos" });
      }, 7500);

      return;
    }
+   
+   // Handle any unexpected statuses
+   if (run.status !== "RUNNING") {
+     setStatus("error");
+     setErrorMessage("Unexpected payment status. Please contact support.");
+     
+     setTimeout(() => {
+       navigate({ to: "/videos" });
+     }, 7500);
+   }
  }, [run, navigate]);
```

---

`166-198`: **Server function has proper authentication and error handling.**

The server function correctly verifies authentication and finds the relevant task, with appropriate redirects when needed.


Consider adding more specific error handling for different types of failures:

```diff
const getPolarEventHandlerForUserServerFn = createServerFn({
  method: "POST",
}).handler(async () => {
  try {
    const { userId } = await getAuth(getWebRequest()!);

    if (!userId) {
      throw redirect({ to: "/" });
    }

    const runningTasks = await runs.list({ tag: userId });

    const polarEventHandler = runningTasks.data.find(
      (t) => t.taskIdentifier === "polar-event-handler"
    );

    if (!polarEventHandler) {
      throw redirect({ to: "/videos" });
    }

    const accessToken = await triggerAuth.createPublicToken({
      expirationTime: 1000 * 60 * 5,
      scopes: {
        read: {
          tags: [userId],
        },
      },
    });

    return {
      token: accessToken,
      runId: polarEventHandler.id,
    };
+  } catch (error) {
+    console.error("Error in getPolarEventHandlerForUserServerFn:", error);
+    if (error instanceof Response) {
+      throw error; // Pass through redirects
+    }
+    throw redirect({ to: "/videos" });
+  }
});
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 6fd65f2d85c15002a5ab5b7bbeb9a54535010d46 and 4f9738c30f5609f548eb97ced9a6cb27f8d50883.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `pnpm-lock.yaml` is excluded by `!**/pnpm-lock.yaml`

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `app.config.ts` (1 hunks)
* `app/components/top-nav.tsx` (2 hunks)
* `app/routeTree.gen.ts` (3 hunks)
* `app/routes/success.tsx` (1 hunks)
* `app/server-fns/polar.ts` (1 hunks)
* `app/trigger/handle-polar-event.ts` (6 hunks)
* `package.json` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (25)</summary><blockquote>

<details>
<summary>package.json (1)</summary>

`72-72`: <details>
<summary>✅ Verification successful</summary>

**New dependency added for confetti effects.**

The addition of `react-confetti-boom` seems appropriate for enhancing the success page experience with visual effects.


---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Check react-confetti-boom version and popularity metrics

# Check NPM information
npm view react-confetti-boom --json | jq '{version: .version, description: .description, downloads: ."dist-tags", license: .license, lastPublished: .time.modified}'

# Check weekly downloads
npm view react-confetti-boom download-counts
```

Length of output: 455

---



</details>

**Dependency Verification Successful**

The review comment regarding the new dependency on `react-confetti-boom` has been validated. The NPM inquiry confirms that version **1.1.2**—a customizable React confetti explosion component—has been added, carries an MIT license, and was recently updated (last published on 2024-12-21). This addition is appropriate to enhance the success page with visual effects.

- **File:** `package.json` (Line 72)
- **Dependency:** `"react-confetti-boom": "^1.1.2"`
- **Verification Details:** Confirmed version, description, license, and last published information via NPM.

</details>
<details>
<summary>app.config.ts (1)</summary>

`15-15`: **Added prerendering for new routes.**

The addition of `/success` and `/videos` routes to the prerender configuration aligns with the new success page implementation and ensures optimal performance for these paths.

</details>
<details>
<summary>app/components/top-nav.tsx (3)</summary>

`20-22`: **Good addition of optional prop type.**

The TopNavProps type with optional showTierPill property is well-defined and follows TypeScript best practices.

---

`24-24`: **Component signature updated with backward compatibility.**

The updated function signature with default value for showTierPill maintains backward compatibility while adding the new functionality.

---

`149-158`: **Implemented conditional rendering for account tier badge.**

The conditional rendering implementation looks good. This allows hiding the account tier badge in specific contexts, likely for the success page where it might not be needed.

</details>
<details>
<summary>app/trigger/handle-polar-event.ts (7)</summary>

`4-4`: **Good addition of progress tracking.**

Adding the metadata import enables progress tracking for this task, which provides better visibility into the payment processing status.

---

`32-33`: **Good practice initializing progress.**

Setting the initial progress to 0 at the start of the task provides a clear baseline.

---

`41-42`: **Progress tracking after subscription confirmation.**

Good placement of progress tracking after confirming the subscription is active.

---

`52-53`: **Progress tracking after user lookup.**

Appropriate progress update after querying the user from the database.

---

`73-74`: **Progress tracking before database updates.**

Good progression of progress values, showing incremental steps through the process.

---

`94-95`: **Progress tracking after batch operations.**

Incrementing progress to 70% after completing database updates provides a good indication of approaching completion.

---

`165-167`: **Final progress update ensures completion is recorded.**

Setting progress to 100% at the end of the function ensures the completion is properly tracked, regardless of which path was taken.

</details>
<details>
<summary>app/routeTree.gen.ts (5)</summary>

`13-21`: **Route imports look good.**

The imports have been updated to include the new success route, maintaining the same pattern as existing routes.

---

`30-35`: **Success route declaration is properly configured.**

The success route is correctly defined with consistent ID and path parameters, following the same pattern as other routes.

---

`84-90`: **Route interface definition is consistent.**

The success route is properly integrated into the FileRoutesByPath interface with all required properties.

---

`127-127`: **Success route is consistently integrated throughout the file.**

The success route has been properly added to all necessary interfaces and route collections.



Also applies to: 137-137, 148-148, 160-160, 169-169, 178-178, 189-189, 199-199

---

`218-219`: **Route manifest is properly updated.**

The success route is correctly included in the route manifest.



Also applies to: 231-233

</details>
<details>
<summary>app/routes/success.tsx (8)</summary>

`16-18`: **Route definition is correctly implemented.**

The route is properly defined using the TanStack Router's createFileRoute function.

---

`20-28`: **Good practice defining error statuses as constants.**

Defining the error statuses as constants improves code readability and maintainability.

---

`30-32`: **Schema validation is a good practice.**

Using zod for schema validation ensures type safety for the progress value.

---

`41-43`: **State management is well-structured.**

The component correctly uses useState for managing processing status, error messages, and progress.

---

`54-57`: **Real-time updates are properly configured.**

The useRealtimeRun hook is correctly configured to only be enabled when the necessary data is available.

---

`59-71`: **Progress update logic is robust.**

The progress update logic correctly handles schema validation and only updates the state when valid progress data is available.

---

`112-112`: **Good UX decision to hide tier pill on success page.**

Setting `showTierPill={false}` on the TopNav component is a good UX decision for the success page, as the user's tier might be in transition.

---

`132-158`: **UI feedback is well-implemented.**

The UI provides clear feedback on the current status with appropriate icons and a progress bar, which enhances the user experience.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-01T01:36:27Z
user: login: BetrixDev
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-01T01:41:14Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-01T01:41:50Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>app/routes/success.tsx (1)</summary><blockquote>

`45-52`: _⚠️ Potential issue_

**Missing error handling in the query function.**

The query implementation is still missing error handling which could lead to unhandled exceptions. While there is error handling in the useEffect based on `isError`, the query function itself should handle errors properly.



```diff
  const { data, isLoading, isError } = useQuery({
    queryKey: ["success", "polarEventHandler"],
    queryFn: async () => {
+     try {
        const data = await getPolarEventHandlerForUserServer();
        return data;
+     } catch (error) {
+       console.error("Error fetching payment status:", error);
+       throw error;
+     }
    },
  });
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>app/routes/success.tsx (4)</summary><blockquote>

`87-110`: **Consider adding a default timeout for long-running processes.**

While the code handles completed and error states well, there's no timeout handling for processes that might be stuck in a running state for too long.


```diff
  useEffect(() => {
    if (!run?.status) {
      return;
    }

    if (run.status === "COMPLETED") {
      setStatus("success");
      navigate({ to: "/videos" });
      return;
    }

    if (ERROR_STATUSES.includes(run.status)) {
      setStatus("error");
      setErrorMessage(
        "An error occurred while processing your payment. Please contact support"
      );

      setTimeout(() => {
        navigate({ to: "/videos" });
      }, 7500);

      return;
    }
+   
+   // Handle long-running processes
+   if (run.status === "RUNNING" && run.startedAt) {
+     const startTime = new Date(run.startedAt).getTime();
+     const currentTime = Date.now();
+     const runningTimeInSeconds = (currentTime - startTime) / 1000;
+     
+     // If running for more than 2 minutes, show a message
+     if (runningTimeInSeconds > 120) {
+       setErrorMessage(
+         "This is taking longer than expected. You can leave this page and we'll email you when complete."
+       );
+     }
+   }
  }, [run, navigate]);
```

---

`148-155`: **Loading indicator uses red, which could be misinterpreted as an error.**

The loading spinner uses red color (`text-red-500`), which typically indicates an error or warning. Consider using a neutral or brand color for the loading state.


```diff
            {status === "loading" && (
              <div className="relative">
-                <Loader2Icon className="h-20 w-20 text-red-500 animate-spin" />
+                <Loader2Icon className="h-20 w-20 text-blue-500 animate-spin" />
                <div className="absolute inset-0 flex items-center justify-center">
                  <span className="text-sm font-medium">{progress}%</span>
                </div>
              </div>
            )}
```

---

`166-171`: **Progress bar uses red color, which may signify error.**

Similar to the loading indicator, the progress bar uses red color which may confuse users by suggesting an error state while the payment is still processing.


```diff
            <div className="w-full bg-zinc-800 rounded-full h-2 mb-6">
              <div
-                className="bg-red-500 h-2 rounded-full transition-all duration-300 ease-in-out"
+                className="bg-blue-500 h-2 rounded-full transition-all duration-300 ease-in-out"
                style={{ width: `${progress}%` }}
              />
            </div>
```

---

`94-94`: **Consider extracting the navigation path to a constant.**

The "/videos" path is hardcoded in multiple places. This could lead to maintenance issues if the path changes in the future.


```diff
+ const REDIRECT_PATH = "/videos";
  
  // Then replace all instances of "/videos" with REDIRECT_PATH
  // For example:
- navigate({ to: "/videos" });
+ navigate({ to: REDIRECT_PATH });
```


Also applies to: 105-105

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 4f9738c30f5609f548eb97ced9a6cb27f8d50883 and 15f3a3bb3afae7617396ffc1d4da1fb335a964ea.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `app/routes/success.tsx` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>app/routes/success.tsx (3)</summary>

`59-71`: **Good implementation of error handling effect.**

The useEffect for handling query errors is implemented well, showing an appropriate error message and redirecting after a timeout.

---

`73-85`: **Proper validation and handling of progress updates.**

The code correctly validates and sets the progress value using Zod schema validation. This is a good practice to ensure type safety.

---

`183-216`: **Well-implemented server function with proper error handling.**

The server function is well-structured with appropriate authentication checks, error handling, and redirection logic. The try-catch block properly logs errors and redirects users when necessary.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-01T01:46:33Z
user: login: BetrixDev
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-01T01:48:12Z
user: login: BetrixDev
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-01T01:48:21Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-01T01:48:34Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-01T01:48:42Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>app/routes/success.tsx (4)</summary><blockquote>

`6-6`: **Remove unused import.**

The `useRouter` is imported but not used anywhere in the component.

```diff
-import { createFileRoute, redirect, useRouter } from "@tanstack/react-router";
+import { createFileRoute, redirect } from "@tanstack/react-router";
```

---

`45-52`: **Add retry policy for payment processing reliability.**

For critical operations like payment processing, it's a good practice to add a retry mechanism to handle temporary network issues.

```diff
  const { data, isLoading, isError } = useQuery({
    queryKey: ["success", "polarEventHandler"],
+   retry: 3,
+   retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 10000),
    queryFn: async () => {
      const data = await getPolarEventHandlerForUserServer();

      return data;
    },
  });
```

---

`154-154`: **Consider using a neutral color for the loading spinner.**

Using red for the loading spinner might confuse users as red typically indicates an error. Consider using a more neutral color like blue or purple for the loading state.

```diff
-               <Loader2Icon className="h-20 w-20 text-red-500 animate-spin" />
+               <Loader2Icon className="h-20 w-20 text-blue-500 animate-spin" />
```

---

`172-172`: **Ensure color consistency with loading spinner.**

If you change the loading spinner color, make sure to also update the progress bar color to maintain design consistency.

```diff
-               className="bg-red-500 h-2 rounded-full transition-all duration-300 ease-in-out"
+               className="bg-blue-500 h-2 rounded-full transition-all duration-300 ease-in-out"
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 15f3a3bb3afae7617396ffc1d4da1fb335a964ea and 89cb075b854664495d0c94e8028dc94bed801677.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `app/routes/success.tsx` (1 hunks)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-01T01:50:23Z

Review comments:
diff_hunk: @@ -0,0 +1,198 @@
+import { Footer } from "@/components/footer";
+import TopNav from "@/components/top-nav";
+import { HeroHighlight } from "@/components/ui/hero-highlight";
+import { getAuth } from "@clerk/tanstack-start/server";
+import { useQuery } from "@tanstack/react-query";
+import { createFileRoute, redirect, useRouter } from "@tanstack/react-router";
+import { createServerFn, useServerFn } from "@tanstack/start";
+import { getWebRequest } from "@tanstack/start/server";
+import { useRealtimeRun } from "@trigger.dev/react-hooks";
+import { runs, auth as triggerAuth } from "@trigger.dev/sdk/v3";
+import { CheckCircleIcon, Loader2Icon, XCircleIcon } from "lucide-react";
+import { useEffect, useState } from "react";
+import { z } from "zod";
+import Confetti from "react-confetti-boom";
+
+export const Route = createFileRoute("/success")({
+  component: RouteComponent,
+});
+
+const ERROR_STATUSES = [
+  "TIMED_OUT",
+  "EXPIRED",
+  "SYSTEM_FAILURE",
+  "INTERRUPTED",
+  "CRASHED",
+  "FAILED",
+  "CANCELED",
+];
+
+const numberSchema = z.number().min(0).max(100);
+
+type ProcessingStatus = "loading" | "success" | "error";
+
+function RouteComponent() {
+  const navigate = Route.useNavigate();
+
+  const getPolarEventHandlerForUserServer = useServerFn(
+    getPolarEventHandlerForUserServerFn
+  );
+
+  const [status, setStatus] = useState<ProcessingStatus>("loading");
+  const [errorMessage, setErrorMessage] = useState("");
+  const [progress, setProgress] = useState(0);
+
+  const { data, isLoading } = useQuery({
+    queryKey: ["success", "polarEventHandler"],
+    queryFn: async () => {
+      const data = await getPolarEventHandlerForUserServer();
+
+      return data;
+    },
+  });
path: app/routes/success.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Query implementation needs error handling.**

The query implementation is missing error handling which could lead to unhandled exceptions.

Add error handling to the query to manage failed requests:

```diff
  const { data, isLoading } = useQuery({
    queryKey: ["success", "polarEventHandler"],
    queryFn: async () => {
+     try {
        const data = await getPolarEventHandlerForUserServer();
        return data;
+     } catch (error) {
+       setStatus("error");
+       setErrorMessage("Failed to fetch payment status. Please try again later.");
+       setTimeout(() => {
+         navigate({ to: "/videos" });
+       }, 5000);
+       throw error;
+     }
    },
  });
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  const { data, isLoading } = useQuery({
    queryKey: ["success", "polarEventHandler"],
    queryFn: async () => {
      try {
        const data = await getPolarEventHandlerForUserServer();
        return data;
      } catch (error) {
        setStatus("error");
        setErrorMessage("Failed to fetch payment status. Please try again later.");
        setTimeout(() => {
          navigate({ to: "/videos" });
        }, 5000);
        throw error;
      }
    },
  });
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-01T01:36:27Z
author_association: NONE
diff_hunk: @@ -0,0 +1,198 @@
+import { Footer } from "@/components/footer";
+import TopNav from "@/components/top-nav";
+import { HeroHighlight } from "@/components/ui/hero-highlight";
+import { getAuth } from "@clerk/tanstack-start/server";
+import { useQuery } from "@tanstack/react-query";
+import { createFileRoute, redirect, useRouter } from "@tanstack/react-router";
+import { createServerFn, useServerFn } from "@tanstack/start";
+import { getWebRequest } from "@tanstack/start/server";
+import { useRealtimeRun } from "@trigger.dev/react-hooks";
+import { runs, auth as triggerAuth } from "@trigger.dev/sdk/v3";
+import { CheckCircleIcon, Loader2Icon, XCircleIcon } from "lucide-react";
+import { useEffect, useState } from "react";
+import { z } from "zod";
+import Confetti from "react-confetti-boom";
+
+export const Route = createFileRoute("/success")({
+  component: RouteComponent,
+});
+
+const ERROR_STATUSES = [
+  "TIMED_OUT",
+  "EXPIRED",
+  "SYSTEM_FAILURE",
+  "INTERRUPTED",
+  "CRASHED",
+  "FAILED",
+  "CANCELED",
+];
+
+const numberSchema = z.number().min(0).max(100);
+
+type ProcessingStatus = "loading" | "success" | "error";
+
+function RouteComponent() {
+  const navigate = Route.useNavigate();
+
+  const getPolarEventHandlerForUserServer = useServerFn(
+    getPolarEventHandlerForUserServerFn
+  );
+
+  const [status, setStatus] = useState<ProcessingStatus>("loading");
+  const [errorMessage, setErrorMessage] = useState("");
+  const [progress, setProgress] = useState(0);
+
+  const { data, isLoading } = useQuery({
+    queryKey: ["success", "polarEventHandler"],
+    queryFn: async () => {
+      const data = await getPolarEventHandlerForUserServer();
+
+      return data;
+    },
+  });
path: app/routes/success.tsx
user: login: BetrixDev
body: Did a similar fix
created_at: 2025-03-01T01:41:13Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,198 @@
+import { Footer } from "@/components/footer";
+import TopNav from "@/components/top-nav";
+import { HeroHighlight } from "@/components/ui/hero-highlight";
+import { getAuth } from "@clerk/tanstack-start/server";
+import { useQuery } from "@tanstack/react-query";
+import { createFileRoute, redirect, useRouter } from "@tanstack/react-router";
+import { createServerFn, useServerFn } from "@tanstack/start";
+import { getWebRequest } from "@tanstack/start/server";
+import { useRealtimeRun } from "@trigger.dev/react-hooks";
+import { runs, auth as triggerAuth } from "@trigger.dev/sdk/v3";
+import { CheckCircleIcon, Loader2Icon, XCircleIcon } from "lucide-react";
+import { useEffect, useState } from "react";
+import { z } from "zod";
+import Confetti from "react-confetti-boom";
+
+export const Route = createFileRoute("/success")({
+  component: RouteComponent,
+});
+
+const ERROR_STATUSES = [
+  "TIMED_OUT",
+  "EXPIRED",
+  "SYSTEM_FAILURE",
+  "INTERRUPTED",
+  "CRASHED",
+  "FAILED",
+  "CANCELED",
+];
+
+const numberSchema = z.number().min(0).max(100);
+
+type ProcessingStatus = "loading" | "success" | "error";
+
+function RouteComponent() {
+  const navigate = Route.useNavigate();
+
+  const getPolarEventHandlerForUserServer = useServerFn(
+    getPolarEventHandlerForUserServerFn
+  );
+
+  const [status, setStatus] = useState<ProcessingStatus>("loading");
+  const [errorMessage, setErrorMessage] = useState("");
+  const [progress, setProgress] = useState(0);
+
+  const { data, isLoading } = useQuery({
+    queryKey: ["success", "polarEventHandler"],
+    queryFn: async () => {
+      const data = await getPolarEventHandlerForUserServer();
+
+      return data;
+    },
+  });
path: app/routes/success.tsx
user: login: coderabbitai[bot]
body: <details>
<summary>🧩 Analysis chain</summary>

🏁 Script executed:
```
#!/bin/bash
cat app/routes/success.tsx
```

Length of output: 6156

---



</details>

@BetrixDev I can see you implemented error handling in a different but equally effective way. Instead of using a try-catch block in the queryFn, you added:

1. The `isError` flag to your useQuery
2. A dedicated useEffect that watches for error state
3. Error handling in the server function

Your implementation actually provides a cleaner separation of concerns between data fetching and UI state management. Nice work!

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-01T01:41:50Z
author_association: NONE
diff_hunk: @@ -0,0 +1,217 @@
+import { Footer } from "@/components/footer";
+import TopNav from "@/components/top-nav";
+import { HeroHighlight } from "@/components/ui/hero-highlight";
+import { getAuth } from "@clerk/tanstack-start/server";
+import { useQuery } from "@tanstack/react-query";
+import { createFileRoute, redirect, useRouter } from "@tanstack/react-router";
+import { createServerFn, useServerFn } from "@tanstack/start";
+import { getWebRequest } from "@tanstack/start/server";
+import { useRealtimeRun } from "@trigger.dev/react-hooks";
+import { runs, auth as triggerAuth } from "@trigger.dev/sdk/v3";
+import { CheckCircleIcon, Loader2Icon, XCircleIcon } from "lucide-react";
+import { useEffect, useState } from "react";
+import Confetti from "react-confetti-boom";
+import { z } from "zod";
+
+export const Route = createFileRoute("/success")({
+  component: RouteComponent,
+});
+
+const ERROR_STATUSES = [
+  "TIMED_OUT",
+  "EXPIRED",
+  "SYSTEM_FAILURE",
+  "INTERRUPTED",
+  "CRASHED",
+  "FAILED",
+  "CANCELED",
+];
+
+const numberSchema = z.number().min(0).max(100);
+
+type ProcessingStatus = "loading" | "success" | "error";
+
+function RouteComponent() {
+  const navigate = Route.useNavigate();
+
+  const getPolarEventHandlerForUserServer = useServerFn(
+    getPolarEventHandlerForUserServerFn
+  );
+
+  const [status, setStatus] = useState<ProcessingStatus>("loading");
+  const [errorMessage, setErrorMessage] = useState("");
+  const [progress, setProgress] = useState(0);
+
+  const { data, isLoading, isError } = useQuery({
+    queryKey: ["success", "polarEventHandler"],
+    queryFn: async () => {
+      const data = await getPolarEventHandlerForUserServer();
+
+      return data;
+    },
+  });
+
+  const { run } = useRealtimeRun(data?.runId, {
+    accessToken: data?.token,
+    enabled: !!data?.runId && !!data?.token && !isLoading,
+  });
+
+  useEffect(() => {
+    if (!isError) {
+      return;
+    }
+
+    setStatus("error");
+    setErrorMessage(
+      "Failed to fetch payment status. Don't worry, your payment will be processed soon"
+    );
+    setTimeout(() => {
+      navigate({ to: "/videos" });
+    }, 5000);
+  }, [isError, navigate]);
+
+  useEffect(() => {
+    if (!run?.metadata?.progress) {
+      return;
+    }
+
+    const { data: progressValue } = numberSchema.safeParse(
+      run.metadata.progress
+    );
+
+    if (progressValue) {
+      setProgress(progressValue);
+    }
+  }, [run]);
+
+  useEffect(() => {
+    if (!run?.status) {
+      return;
+    }
+
+    if (run.status === "COMPLETED") {
+      setStatus("success");
+      navigate({ to: "/videos" });
+      return;
+    }
path: app/routes/success.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Success state redirects immediately without allowing users to see success message.**

When the status is "COMPLETED", the code immediately redirects to "/videos" without giving users time to see the success message or experience the confetti effect. Consider adding a short delay before redirecting.


```diff
    if (run.status === "COMPLETED") {
      setStatus("success");
-      navigate({ to: "/videos" });
+      // Give users time to see the success message and confetti
+      setTimeout(() => {
+        navigate({ to: "/videos" });
+      }, 3000);
      return;
    }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    if (run.status === "COMPLETED") {
      setStatus("success");
      // Give users time to see the success message and confetti
      setTimeout(() => {
        navigate({ to: "/videos" });
      }, 3000);
      return;
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-01T01:46:32Z
author_association: NONE
diff_hunk: @@ -0,0 +1,217 @@
+import { Footer } from "@/components/footer";
+import TopNav from "@/components/top-nav";
+import { HeroHighlight } from "@/components/ui/hero-highlight";
+import { getAuth } from "@clerk/tanstack-start/server";
+import { useQuery } from "@tanstack/react-query";
+import { createFileRoute, redirect, useRouter } from "@tanstack/react-router";
+import { createServerFn, useServerFn } from "@tanstack/start";
+import { getWebRequest } from "@tanstack/start/server";
+import { useRealtimeRun } from "@trigger.dev/react-hooks";
+import { runs, auth as triggerAuth } from "@trigger.dev/sdk/v3";
+import { CheckCircleIcon, Loader2Icon, XCircleIcon } from "lucide-react";
+import { useEffect, useState } from "react";
+import Confetti from "react-confetti-boom";
+import { z } from "zod";
+
+export const Route = createFileRoute("/success")({
+  component: RouteComponent,
+});
+
+const ERROR_STATUSES = [
+  "TIMED_OUT",
+  "EXPIRED",
+  "SYSTEM_FAILURE",
+  "INTERRUPTED",
+  "CRASHED",
+  "FAILED",
+  "CANCELED",
+];
+
+const numberSchema = z.number().min(0).max(100);
+
+type ProcessingStatus = "loading" | "success" | "error";
+
+function RouteComponent() {
+  const navigate = Route.useNavigate();
+
+  const getPolarEventHandlerForUserServer = useServerFn(
+    getPolarEventHandlerForUserServerFn
+  );
+
+  const [status, setStatus] = useState<ProcessingStatus>("loading");
+  const [errorMessage, setErrorMessage] = useState("");
+  const [progress, setProgress] = useState(0);
+
+  const { data, isLoading, isError } = useQuery({
+    queryKey: ["success", "polarEventHandler"],
+    queryFn: async () => {
+      const data = await getPolarEventHandlerForUserServer();
+
+      return data;
+    },
+  });
+
+  const { run } = useRealtimeRun(data?.runId, {
+    accessToken: data?.token,
+    enabled: !!data?.runId && !!data?.token && !isLoading,
+  });
+
+  useEffect(() => {
+    if (!isError) {
+      return;
+    }
+
+    setStatus("error");
+    setErrorMessage(
+      "Failed to fetch payment status. Don't worry, your payment will be processed soon"
+    );
+    setTimeout(() => {
+      navigate({ to: "/videos" });
+    }, 5000);
+  }, [isError, navigate]);
+
+  useEffect(() => {
+    if (!run?.metadata?.progress) {
+      return;
+    }
+
+    const { data: progressValue } = numberSchema.safeParse(
+      run.metadata.progress
+    );
+
+    if (progressValue) {
+      setProgress(progressValue);
+    }
+  }, [run]);
+
+  useEffect(() => {
+    if (!run?.status) {
+      return;
+    }
+
+    if (run.status === "COMPLETED") {
+      setStatus("success");
+      navigate({ to: "/videos" });
+      return;
+    }
+
+    if (ERROR_STATUSES.includes(run.status)) {
+      setStatus("error");
+      setErrorMessage(
+        "An error occurred while processing your payment. Please contact support"
+      );
+
+      setTimeout(() => {
+        navigate({ to: "/videos" });
+      }, 7500);
+
+      return;
+    }
+  }, [run, navigate]);
+
+  return (
+    <HeroHighlight>
+      <div className="absolute inset-0">
+        <Confetti
+          particleCount={75}
+          shapeSize={15}
+          deg={250}
+          effectCount={1}
+          spreadDeg={100}
+          x={0.5}
+          y={0.5}
+          launchSpeed={1}
+        />
+      </div>
path: app/routes/success.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Confetti is triggered on component mount, not just on success.**

The confetti effect appears on initial load rather than being triggered only when payment processing succeeds. This could confuse users who see celebration effects while their payment is still processing.


```diff
-      <div className="absolute inset-0">
-        <Confetti
-          particleCount={75}
-          shapeSize={15}
-          deg={250}
-          effectCount={1}
-          spreadDeg={100}
-          x={0.5}
-          y={0.5}
-          launchSpeed={1}
-        />
-      </div>
+      {status === "success" && (
+        <div className="absolute inset-0">
+          <Confetti
+            particleCount={75}
+            shapeSize={15}
+            deg={250}
+            effectCount={1}
+            spreadDeg={100}
+            x={0.5}
+            y={0.5}
+            launchSpeed={1}
+          />
+        </div>
+      )}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
      {status === "success" && (
        <div className="absolute inset-0">
          <Confetti
            particleCount={75}
            shapeSize={15}
            deg={250}
            effectCount={1}
            spreadDeg={100}
            x={0.5}
            y={0.5}
            launchSpeed={1}
          />
        </div>
      )}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-01T01:46:32Z
author_association: NONE
diff_hunk: @@ -0,0 +1,217 @@
+import { Footer } from "@/components/footer";
+import TopNav from "@/components/top-nav";
+import { HeroHighlight } from "@/components/ui/hero-highlight";
+import { getAuth } from "@clerk/tanstack-start/server";
+import { useQuery } from "@tanstack/react-query";
+import { createFileRoute, redirect, useRouter } from "@tanstack/react-router";
+import { createServerFn, useServerFn } from "@tanstack/start";
+import { getWebRequest } from "@tanstack/start/server";
+import { useRealtimeRun } from "@trigger.dev/react-hooks";
+import { runs, auth as triggerAuth } from "@trigger.dev/sdk/v3";
+import { CheckCircleIcon, Loader2Icon, XCircleIcon } from "lucide-react";
+import { useEffect, useState } from "react";
+import Confetti from "react-confetti-boom";
+import { z } from "zod";
+
+export const Route = createFileRoute("/success")({
+  component: RouteComponent,
+});
+
+const ERROR_STATUSES = [
+  "TIMED_OUT",
+  "EXPIRED",
+  "SYSTEM_FAILURE",
+  "INTERRUPTED",
+  "CRASHED",
+  "FAILED",
+  "CANCELED",
+];
+
+const numberSchema = z.number().min(0).max(100);
+
+type ProcessingStatus = "loading" | "success" | "error";
+
+function RouteComponent() {
+  const navigate = Route.useNavigate();
+
+  const getPolarEventHandlerForUserServer = useServerFn(
+    getPolarEventHandlerForUserServerFn
+  );
+
+  const [status, setStatus] = useState<ProcessingStatus>("loading");
+  const [errorMessage, setErrorMessage] = useState("");
+  const [progress, setProgress] = useState(0);
+
+  const { data, isLoading, isError } = useQuery({
+    queryKey: ["success", "polarEventHandler"],
+    queryFn: async () => {
+      const data = await getPolarEventHandlerForUserServer();
+
+      return data;
+    },
+  });
+
+  const { run } = useRealtimeRun(data?.runId, {
+    accessToken: data?.token,
+    enabled: !!data?.runId && !!data?.token && !isLoading,
+  });
+
+  useEffect(() => {
+    if (!isError) {
+      return;
+    }
+
+    setStatus("error");
+    setErrorMessage(
+      "Failed to fetch payment status. Don't worry, your payment will be processed soon"
+    );
+    setTimeout(() => {
+      navigate({ to: "/videos" });
+    }, 5000);
+  }, [isError, navigate]);
+
+  useEffect(() => {
+    if (!run?.metadata?.progress) {
+      return;
+    }
+
+    const { data: progressValue } = numberSchema.safeParse(
+      run.metadata.progress
+    );
+
+    if (progressValue) {
+      setProgress(progressValue);
+    }
+  }, [run]);
+
+  useEffect(() => {
+    if (!run?.status) {
+      return;
+    }
+
+    if (run.status === "COMPLETED") {
+      setStatus("success");
+      navigate({ to: "/videos" });
+      return;
+    }
+
+    if (ERROR_STATUSES.includes(run.status)) {
+      setStatus("error");
+      setErrorMessage(
+        "An error occurred while processing your payment. Please contact support"
+      );
+
+      setTimeout(() => {
+        navigate({ to: "/videos" });
+      }, 7500);
+
+      return;
+    }
+  }, [run, navigate]);
+
+  return (
+    <HeroHighlight>
+      <div className="absolute inset-0">
+        <Confetti
+          particleCount={75}
+          shapeSize={15}
+          deg={250}
+          effectCount={1}
+          spreadDeg={100}
+          x={0.5}
+          y={0.5}
+          launchSpeed={1}
+        />
+      </div>
path: app/routes/success.tsx
user: login: BetrixDev
body: Good idea, implemented
created_at: 2025-03-01T01:48:12Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,217 @@
+import { Footer } from "@/components/footer";
+import TopNav from "@/components/top-nav";
+import { HeroHighlight } from "@/components/ui/hero-highlight";
+import { getAuth } from "@clerk/tanstack-start/server";
+import { useQuery } from "@tanstack/react-query";
+import { createFileRoute, redirect, useRouter } from "@tanstack/react-router";
+import { createServerFn, useServerFn } from "@tanstack/start";
+import { getWebRequest } from "@tanstack/start/server";
+import { useRealtimeRun } from "@trigger.dev/react-hooks";
+import { runs, auth as triggerAuth } from "@trigger.dev/sdk/v3";
+import { CheckCircleIcon, Loader2Icon, XCircleIcon } from "lucide-react";
+import { useEffect, useState } from "react";
+import Confetti from "react-confetti-boom";
+import { z } from "zod";
+
+export const Route = createFileRoute("/success")({
+  component: RouteComponent,
+});
+
+const ERROR_STATUSES = [
+  "TIMED_OUT",
+  "EXPIRED",
+  "SYSTEM_FAILURE",
+  "INTERRUPTED",
+  "CRASHED",
+  "FAILED",
+  "CANCELED",
+];
+
+const numberSchema = z.number().min(0).max(100);
+
+type ProcessingStatus = "loading" | "success" | "error";
+
+function RouteComponent() {
+  const navigate = Route.useNavigate();
+
+  const getPolarEventHandlerForUserServer = useServerFn(
+    getPolarEventHandlerForUserServerFn
+  );
+
+  const [status, setStatus] = useState<ProcessingStatus>("loading");
+  const [errorMessage, setErrorMessage] = useState("");
+  const [progress, setProgress] = useState(0);
+
+  const { data, isLoading, isError } = useQuery({
+    queryKey: ["success", "polarEventHandler"],
+    queryFn: async () => {
+      const data = await getPolarEventHandlerForUserServer();
+
+      return data;
+    },
+  });
+
+  const { run } = useRealtimeRun(data?.runId, {
+    accessToken: data?.token,
+    enabled: !!data?.runId && !!data?.token && !isLoading,
+  });
+
+  useEffect(() => {
+    if (!isError) {
+      return;
+    }
+
+    setStatus("error");
+    setErrorMessage(
+      "Failed to fetch payment status. Don't worry, your payment will be processed soon"
+    );
+    setTimeout(() => {
+      navigate({ to: "/videos" });
+    }, 5000);
+  }, [isError, navigate]);
+
+  useEffect(() => {
+    if (!run?.metadata?.progress) {
+      return;
+    }
+
+    const { data: progressValue } = numberSchema.safeParse(
+      run.metadata.progress
+    );
+
+    if (progressValue) {
+      setProgress(progressValue);
+    }
+  }, [run]);
+
+  useEffect(() => {
+    if (!run?.status) {
+      return;
+    }
+
+    if (run.status === "COMPLETED") {
+      setStatus("success");
+      navigate({ to: "/videos" });
+      return;
+    }
path: app/routes/success.tsx
user: login: BetrixDev
body: implemented
created_at: 2025-03-01T01:48:21Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,217 @@
+import { Footer } from "@/components/footer";
+import TopNav from "@/components/top-nav";
+import { HeroHighlight } from "@/components/ui/hero-highlight";
+import { getAuth } from "@clerk/tanstack-start/server";
+import { useQuery } from "@tanstack/react-query";
+import { createFileRoute, redirect, useRouter } from "@tanstack/react-router";
+import { createServerFn, useServerFn } from "@tanstack/start";
+import { getWebRequest } from "@tanstack/start/server";
+import { useRealtimeRun } from "@trigger.dev/react-hooks";
+import { runs, auth as triggerAuth } from "@trigger.dev/sdk/v3";
+import { CheckCircleIcon, Loader2Icon, XCircleIcon } from "lucide-react";
+import { useEffect, useState } from "react";
+import Confetti from "react-confetti-boom";
+import { z } from "zod";
+
+export const Route = createFileRoute("/success")({
+  component: RouteComponent,
+});
+
+const ERROR_STATUSES = [
+  "TIMED_OUT",
+  "EXPIRED",
+  "SYSTEM_FAILURE",
+  "INTERRUPTED",
+  "CRASHED",
+  "FAILED",
+  "CANCELED",
+];
+
+const numberSchema = z.number().min(0).max(100);
+
+type ProcessingStatus = "loading" | "success" | "error";
+
+function RouteComponent() {
+  const navigate = Route.useNavigate();
+
+  const getPolarEventHandlerForUserServer = useServerFn(
+    getPolarEventHandlerForUserServerFn
+  );
+
+  const [status, setStatus] = useState<ProcessingStatus>("loading");
+  const [errorMessage, setErrorMessage] = useState("");
+  const [progress, setProgress] = useState(0);
+
+  const { data, isLoading, isError } = useQuery({
+    queryKey: ["success", "polarEventHandler"],
+    queryFn: async () => {
+      const data = await getPolarEventHandlerForUserServer();
+
+      return data;
+    },
+  });
+
+  const { run } = useRealtimeRun(data?.runId, {
+    accessToken: data?.token,
+    enabled: !!data?.runId && !!data?.token && !isLoading,
+  });
+
+  useEffect(() => {
+    if (!isError) {
+      return;
+    }
+
+    setStatus("error");
+    setErrorMessage(
+      "Failed to fetch payment status. Don't worry, your payment will be processed soon"
+    );
+    setTimeout(() => {
+      navigate({ to: "/videos" });
+    }, 5000);
+  }, [isError, navigate]);
+
+  useEffect(() => {
+    if (!run?.metadata?.progress) {
+      return;
+    }
+
+    const { data: progressValue } = numberSchema.safeParse(
+      run.metadata.progress
+    );
+
+    if (progressValue) {
+      setProgress(progressValue);
+    }
+  }, [run]);
+
+  useEffect(() => {
+    if (!run?.status) {
+      return;
+    }
+
+    if (run.status === "COMPLETED") {
+      setStatus("success");
+      navigate({ to: "/videos" });
+      return;
+    }
+
+    if (ERROR_STATUSES.includes(run.status)) {
+      setStatus("error");
+      setErrorMessage(
+        "An error occurred while processing your payment. Please contact support"
+      );
+
+      setTimeout(() => {
+        navigate({ to: "/videos" });
+      }, 7500);
+
+      return;
+    }
+  }, [run, navigate]);
+
+  return (
+    <HeroHighlight>
+      <div className="absolute inset-0">
+        <Confetti
+          particleCount={75}
+          shapeSize={15}
+          deg={250}
+          effectCount={1}
+          spreadDeg={100}
+          x={0.5}
+          y={0.5}
+          launchSpeed={1}
+        />
+      </div>
path: app/routes/success.tsx
user: login: coderabbitai[bot]
body: @BetrixDev Thank you for implementing the suggestion! The confetti effect will now only appear when the payment is successful, providing a better user experience.

🐰✨

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-01T01:48:34Z
author_association: NONE
diff_hunk: @@ -0,0 +1,217 @@
+import { Footer } from "@/components/footer";
+import TopNav from "@/components/top-nav";
+import { HeroHighlight } from "@/components/ui/hero-highlight";
+import { getAuth } from "@clerk/tanstack-start/server";
+import { useQuery } from "@tanstack/react-query";
+import { createFileRoute, redirect, useRouter } from "@tanstack/react-router";
+import { createServerFn, useServerFn } from "@tanstack/start";
+import { getWebRequest } from "@tanstack/start/server";
+import { useRealtimeRun } from "@trigger.dev/react-hooks";
+import { runs, auth as triggerAuth } from "@trigger.dev/sdk/v3";
+import { CheckCircleIcon, Loader2Icon, XCircleIcon } from "lucide-react";
+import { useEffect, useState } from "react";
+import Confetti from "react-confetti-boom";
+import { z } from "zod";
+
+export const Route = createFileRoute("/success")({
+  component: RouteComponent,
+});
+
+const ERROR_STATUSES = [
+  "TIMED_OUT",
+  "EXPIRED",
+  "SYSTEM_FAILURE",
+  "INTERRUPTED",
+  "CRASHED",
+  "FAILED",
+  "CANCELED",
+];
+
+const numberSchema = z.number().min(0).max(100);
+
+type ProcessingStatus = "loading" | "success" | "error";
+
+function RouteComponent() {
+  const navigate = Route.useNavigate();
+
+  const getPolarEventHandlerForUserServer = useServerFn(
+    getPolarEventHandlerForUserServerFn
+  );
+
+  const [status, setStatus] = useState<ProcessingStatus>("loading");
+  const [errorMessage, setErrorMessage] = useState("");
+  const [progress, setProgress] = useState(0);
+
+  const { data, isLoading, isError } = useQuery({
+    queryKey: ["success", "polarEventHandler"],
+    queryFn: async () => {
+      const data = await getPolarEventHandlerForUserServer();
+
+      return data;
+    },
+  });
+
+  const { run } = useRealtimeRun(data?.runId, {
+    accessToken: data?.token,
+    enabled: !!data?.runId && !!data?.token && !isLoading,
+  });
+
+  useEffect(() => {
+    if (!isError) {
+      return;
+    }
+
+    setStatus("error");
+    setErrorMessage(
+      "Failed to fetch payment status. Don't worry, your payment will be processed soon"
+    );
+    setTimeout(() => {
+      navigate({ to: "/videos" });
+    }, 5000);
+  }, [isError, navigate]);
+
+  useEffect(() => {
+    if (!run?.metadata?.progress) {
+      return;
+    }
+
+    const { data: progressValue } = numberSchema.safeParse(
+      run.metadata.progress
+    );
+
+    if (progressValue) {
+      setProgress(progressValue);
+    }
+  }, [run]);
+
+  useEffect(() => {
+    if (!run?.status) {
+      return;
+    }
+
+    if (run.status === "COMPLETED") {
+      setStatus("success");
+      navigate({ to: "/videos" });
+      return;
+    }
path: app/routes/success.tsx
user: login: coderabbitai[bot]
body: @BetrixDev Great! This change will provide a better user experience by allowing users to see the success confirmation and enjoy the confetti effect before being redirected to the videos page.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-03-01T01:48:41Z
author_association: NONE
diff_hunk: @@ -0,0 +1,221 @@
+import { Footer } from "@/components/footer";
+import TopNav from "@/components/top-nav";
+import { HeroHighlight } from "@/components/ui/hero-highlight";
+import { getAuth } from "@clerk/tanstack-start/server";
+import { useQuery } from "@tanstack/react-query";
+import { createFileRoute, redirect, useRouter } from "@tanstack/react-router";
+import { createServerFn, useServerFn } from "@tanstack/start";
+import { getWebRequest } from "@tanstack/start/server";
+import { useRealtimeRun } from "@trigger.dev/react-hooks";
+import { runs, auth as triggerAuth } from "@trigger.dev/sdk/v3";
+import { CheckCircleIcon, Loader2Icon, XCircleIcon } from "lucide-react";
+import { useEffect, useState } from "react";
+import Confetti from "react-confetti-boom";
+import { z } from "zod";
+
+export const Route = createFileRoute("/success")({
+  component: RouteComponent,
+});
+
+const ERROR_STATUSES = [
+  "TIMED_OUT",
+  "EXPIRED",
+  "SYSTEM_FAILURE",
+  "INTERRUPTED",
+  "CRASHED",
+  "FAILED",
+  "CANCELED",
+];
+
+const numberSchema = z.number().min(0).max(100);
+
+type ProcessingStatus = "loading" | "success" | "error";
+
+function RouteComponent() {
+  const navigate = Route.useNavigate();
+
+  const getPolarEventHandlerForUserServer = useServerFn(
+    getPolarEventHandlerForUserServerFn
+  );
+
+  const [status, setStatus] = useState<ProcessingStatus>("loading");
+  const [errorMessage, setErrorMessage] = useState("");
+  const [progress, setProgress] = useState(0);
+
+  const { data, isLoading, isError } = useQuery({
+    queryKey: ["success", "polarEventHandler"],
+    queryFn: async () => {
+      const data = await getPolarEventHandlerForUserServer();
+
+      return data;
+    },
+  });
+
+  const { run } = useRealtimeRun(data?.runId, {
+    accessToken: data?.token,
+    enabled: !!data?.runId && !!data?.token && !isLoading,
+  });
+
+  useEffect(() => {
+    if (!isError) {
+      return;
+    }
+
+    setStatus("error");
+    setErrorMessage(
+      "Failed to fetch payment status. Don't worry, your payment will be processed soon"
+    );
+    setTimeout(() => {
+      navigate({ to: "/videos" });
+    }, 5000);
+  }, [isError, navigate]);
+
+  useEffect(() => {
+    if (!run?.metadata?.progress) {
+      return;
+    }
+
+    const { data: progressValue } = numberSchema.safeParse(
+      run.metadata.progress
+    );
+
+    if (progressValue) {
+      setProgress(progressValue);
+    }
+  }, [run]);
+
+  useEffect(() => {
+    if (!run?.status) {
+      return;
+    }
+
+    if (run.status === "COMPLETED") {
+      setStatus("success");
+      setTimeout(() => {
+        navigate({ to: "/videos" });
+      }, 3000);
+      return;
+    }
+
+    if (ERROR_STATUSES.includes(run.status)) {
+      setStatus("error");
+      setErrorMessage(
+        "An error occurred while processing your payment. Please contact support"
+      );
+
+      setTimeout(() => {
+        navigate({ to: "/videos" });
+      }, 7500);
+
+      return;
+    }
+  }, [run, navigate]);
+
+  return (
+    <HeroHighlight>
+      <div className="absolute inset-0">
+        {status === "success" && (
+          <Confetti
+            particleCount={75}
+            shapeSize={15}
+            deg={250}
+            effectCount={1}
+            spreadDeg={100}
+            x={0.5}
+            y={0.5}
+            launchSpeed={1}
+          />
+        )}
+      </div>
+      <TopNav showTierPill={false} />
+      <div className="w-full min-h-[calc(100dvh-8rem)] flex items-center justify-center">
+        {/* Status Card */}
+        <div className="bg-zinc-900 border border-zinc-800 rounded-xl p-8 shadow-lg">
+          {/* Header */}
+          <div className="text-center mb-6">
+            <h1 className="text-2xl font-bold mb-2">
+              {status === "loading" && "Processing Your Order"}
+              {status === "success" && "Order Complete!"}
+              {status === "error" && "Processing Error"}
+            </h1>
+            <p className="text-zinc-400">
+              {status === "loading" &&
+                "Please wait while we process your payment..."}
+              {status === "success" &&
+                "Your payment has been processed successfully."}
+              {status === "error" && errorMessage}
+            </p>
+          </div>
+
+          {/* Status Icon */}
+          <div className="flex justify-center mb-6">
+            {status === "loading" && (
+              <div className="relative">
+                <Loader2Icon className="h-20 w-20 text-red-500 animate-spin" />
+                <div className="absolute inset-0 flex items-center justify-center">
+                  <span className="text-sm font-medium">{progress}%</span>
+                </div>
+              </div>
+            )}
+            {status === "success" && (
+              <CheckCircleIcon className="h-20 w-20 text-green-500" />
+            )}
+            {status === "error" && (
+              <XCircleIcon className="h-20 w-20 text-red-500" />
+            )}
+          </div>
+
+          {/* Progress Bar (only shown during loading) */}
+          {status === "loading" && (
+            <div className="w-full bg-zinc-800 rounded-full h-2 mb-6">
+              <div
+                className="bg-red-500 h-2 rounded-full transition-all duration-300 ease-in-out"
+                style={{ width: `${progress}%` }}
+              />
+            </div>
+          )}
+        </div>
+      </div>
+      <Footer />
+    </HeroHighlight>
+  );
+}
+
+const getPolarEventHandlerForUserServerFn = createServerFn({
+  method: "POST",
+}).handler(async () => {
+  try {
+    const { userId } = await getAuth(getWebRequest()!);
+
+    if (!userId) {
+      throw redirect({ to: "/" });
+    }
path: app/routes/success.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Use a more defensive approach for handling potential null web request.**

The non-null assertion operator (`!`) on `getWebRequest()` could lead to runtime errors if the request is unexpectedly null. Consider a more defensive approach.

```diff
  try {
-   const { userId } = await getAuth(getWebRequest()!);
+   const request = getWebRequest();
+   if (!request) {
+     throw new Error("No web request found");
+   }
+   const { userId } = await getAuth(request);

    if (!userId) {
      throw redirect({ to: "/" });
    }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  try {
    const request = getWebRequest();
    if (!request) {
      throw new Error("No web request found");
    }
    const { userId } = await getAuth(request);

    if (!userId) {
      throw redirect({ to: "/" });
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-01T01:50:22Z
author_association: NONE

PR comments:
user: login: vercel[bot]
created_at: 2025-03-01T01:32:55Z
body: [vc]: #gAIpSV0FkTsMk4hUMv7bBJ1kcUC23LWOCNQve1Q8Pzs=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJzbGlwc3RyZWFtIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2JldHJpeGRldi9zbGlwc3RyZWFtLzZib0RleUdrS2ZZRWVhdUtHZXFkNDJ2cVJxenoiLCJwcmV2aWV3VXJsIjoic2xpcHN0cmVhbS1naXQtc3VjY2Vzcy11cmwtYmV0cml4ZGV2LnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJyb290RGlyZWN0b3J5IjpudWxsfV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Updated (UTC) |
| :--- | :----- | :------ | :------ |
| **slipstream** | ✅ Ready ([Inspect](https://vercel.com/betrixdev/slipstream/6boDeyGkKfYEeauKGeqd42vqRqzz)) | [Visit Preview](https://slipstream-git-success-url-betrixdev.vercel.app) | Mar 1, 2025 1:49am |


author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-03-01T01:32:56Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
The updates modify several parts of the application. The configuration now includes additional routes for prerendering. The top navigation component has been updated with an optional property for conditionally displaying an account tier badge. A new “success” route is integrated into the route tree, and a corresponding component is added to handle payment processing states using asynchronous data fetching and realtime updates. Additionally, the checkout process now includes a success URL, progress tracking is added in a polar event handler, and a new dependency is introduced.

## Changes

| File(s)                                          | Summary                                                                                                                                                                                                                                                                                                                       |
|--------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `app.config.ts`                                  | Updated the prerender routes array in the server configuration by adding two new routes: `"/success"` and `"/videos"`.                                                                                                                                                                                                      |
| `app/components/top-nav.tsx`                     | Introduced a new type `TopNavProps` with an optional boolean `showTierPill` prop (default `true`) and updated the component to conditionally render the account tier badge based on this prop.                                                                                                                       |
| `app/routeTree.gen.ts`, `app/routes/success.tsx`   | Added a new `/success` route. The route tree interfaces are updated accordingly, and a new route component is defined to manage payment processing states, including loading, success, and error conditions with realtime updates and progress indicators.                                                            |
| `app/server-fns/polar.ts`, `app/trigger/handle-polar-event.ts` | Modified the checkout creation payload by adding a `successUrl` to redirect users after successful checkout, and enhanced the polar event handler with progress tracking updates via the `metadata` object at various execution points.                                                                              |
| `package.json`                                   | Added a new dependency: `"react-confetti-boom": "^1.1.2"`.                                                                                                                                                                                                                                                                |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant U as User
    participant SR as Success Route
    participant SF as Server Function (getPolarEventHandlerForUserServerFn)
    participant RT as Realtime Update Handler
    U->>SR: Navigate to /success
    SR->>SF: Request payment status
    SF-->>SR: Return payment status data
    SR->>RT: Subscribe for realtime progress updates
    RT-->>SR: Send progress updates (0%, 15%, 33%, etc.)
    SR-->>U: Render UI (spinner, success checkmark, or error message)
```

## Poem
> I'm a bouncy rabbit in the code field,  
> Hopping through new routes with a joyful yield.  
> With a tier pill badge so sleek and light,  
> And a success path glowing bright.  
> My progress hops along in a merry tune,  
> Celebrating each update from dawn to moon! 🐇

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE