Skip to content

Conversation

oscartbeaumont
Copy link
Member

@oscartbeaumont oscartbeaumont commented Sep 23, 2025

This literally just breaks out the old flow into a new dedicated function so I can work on the Effect rewrite side by side. This should not change any logic.

Rewriting to Effect means our Loom exporter can share code with the UploadCapButton and it also will make the whole process more reliable and possibly even faster if using Effects concurrency primitives.

Summary by CodeRabbit

  • New Features

    • Visible upload progress with live updates during video and thumbnail uploads.
    • Clear uploading state: disabled button with spinner and “Uploading…” feedback.
  • Improvements

    • More reliable uploads with better error handling and cleanup.
    • Automatic dashboard refresh after successful upload; file input cleared on completion or failure.
    • Thumbnail captured and uploaded as part of the flow.
    • Progress reporting now uses a dedicated, decoupled update path.
  • Refactor

    • Consolidated upload workflow and progress handling for stability and maintainability.

Copy link
Contributor

vercel bot commented Sep 23, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
cap-web Error Error Sep 23, 2025 7:02am

Copy link
Contributor

coderabbitai bot commented Sep 23, 2025

Walkthrough

Delegates the multi-stage upload workflow to a new legacyUploadCap function that returns a boolean, adds a separate sendProgressUpdate path for progress POSTs, uses useUploadingContext for isUploading, simplifies the upload button UI, and refreshes router/clears state on success or logs and resets on failure.

Changes

Cohort / File(s) Summary
Upload button, upload flow, and progress reporting
apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx
Replaces inline multi-stage upload with legacyUploadCap(...) (returns boolean); introduces sendProgressUpdate for decoupled progress POSTs; derives isUploading from useUploadingContext; simplifies button UI (disabled/spinner); refreshes router and clears input on success; logs error and clears upload state on failure; adds typed import UploadStatus.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant B as UploadCapButton
  participant L as legacyUploadCap
  participant X as XHR (video/poster)
  participant P as sendProgressUpdate
  participant R as Router

  U->>B: select file / click upload
  B->>L: legacyUploadCap(file, ctx)
  rect rgb(245,250,255)
    note over L: parse, resize/convert, capture thumbnail
    L->>X: upload video (XHR)
    loop Video progress
      X-->>L: progress %
      L->>P: POST progress (batched)
    end
    L->>X: upload thumbnail (XHR)
    loop Thumbnail progress
      X-->>L: progress %
      L->>P: POST progress
    end
  end
  alt success (true)
    L-->>B: true
    B->>R: refresh()
    B->>B: clear input / reset upload state
  else failure (false/throw)
    L-->>B: false / throw
    B->>B: log error / clear upload state
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

“I’m a rabbit, I press and wait,
Pixels hop through a tiny gate.
Thumbs and videos take their tour,
Progress posts nibble updates pure.
Router refresh—carrot found, upload’s secure.” 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Abstract upload flow" succinctly and accurately reflects the main change described in the PR: extracting/abstracting the upload flow into a dedicated function to enable a future Effects-based rewrite, and it is concise and relevant to the provided file and objective summaries.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch abstract-upload-flow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx (1)

32-43: Don’t block non‑Pro uploads ≤5 min; gate after parsing duration.

Current pre‑click gate forces upgrade for all non‑Pro users, but the server allows ≤300s uploads. Move gating after metadata parsing and only show the Upgrade modal when the duration exceeds 5 minutes.

@@
 const handleClick = () => {
   if (!user) return;
-
-  const isCapPro = userIsPro(user);
-
-  if (!isCapPro) {
-    setUpgradeModalOpen(true);
-    return;
-  }
-
   inputRef.current?.click();
 };
@@
 const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
   const file = e.target.files?.[0];
   if (!file || !user) return;
 
-  const ok = await legacyUploadCap(file, folderId, setUploadStatus);
+  const isCapPro = userIsPro(user);
+  const ok = await legacyUploadCap(
+    file,
+    folderId,
+    setUploadStatus,
+    isCapPro,
+    () => setUpgradeModalOpen(true),
+  );
   if (ok) router.refresh();
   if (inputRef.current) inputRef.current.value = "";
 };
@@
-async function legacyUploadCap(
-  file: File,
-  folderId: string | undefined,
-  setUploadStatus: (state: UploadStatus | undefined) => void,
-) {
+async function legacyUploadCap(
+  file: File,
+  folderId: string | undefined,
+  setUploadStatus: (state: UploadStatus | undefined) => void,
+  isCapPro: boolean,
+  onUpgradeRequired?: () => void,
+) {
@@
   const duration = metadata.durationInSeconds
     ? Math.round(metadata.durationInSeconds)
     : undefined;
 
+  if (!isCapPro && duration && duration > 300) {
+    onUpgradeRequired?.();
+    toast.error("Upgrade required to upload videos longer than 5 minutes.");
+    setUploadStatus(undefined);
+    return false;
+  }
+
   setUploadStatus({ status: "creating" });

Also applies to: 45-52, 84-88, 105-121

🧹 Nitpick comments (6)
apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx (6)

341-341: Remove inline comments to comply with repository rules.

Repo guidelines disallow inline/block/doc comments. These can be dropped; names are self‑explanatory.

-    // Create progress tracking state outside React
@@
-        // Clear any existing pending task
@@
-        // Don't send completion update immediately - let xhr.onload handle it
-        // to avoid double progress updates
@@
-        // Schedule delayed update (after 2 seconds)
@@
-            // Guarantee final 100% progress update

Also applies to: 356-361, 365-369, 419-421


321-325: Revoke thumbnail Object URL to avoid memory leaks.

thumbnailUrl is created via URL.createObjectURL and never revoked.

@@
-  const thumbnailUrl = thumbnailBlob
-    ? URL.createObjectURL(thumbnailBlob)
-    : undefined;
+  const thumbnailUrl = thumbnailBlob
+    ? URL.createObjectURL(thumbnailBlob)
+    : undefined;
@@
-  setUploadStatus(undefined);
-  return true;
+  if (thumbnailUrl) URL.revokeObjectURL(thumbnailUrl);
+  setUploadStatus(undefined);
+  return true;
 } catch (err) {
   console.error("Video upload failed", err);
+  if (thumbnailUrl) URL.revokeObjectURL(thumbnailUrl);
 }
 
 setUploadStatus(undefined);
 return false;

Also applies to: 491-499


343-349: Remove unused lastUpdateTime from progress tracker.

Field is never read; drop it and its assignment.

-      const uploadState = {
+      const uploadState = {
         videoId: uploadId,
         uploaded: 0,
         total: 0,
         pendingTask: undefined as ReturnType<typeof setTimeout> | undefined,
-        lastUpdateTime: Date.now(),
       };
@@
-        uploadState.total = total;
-        uploadState.lastUpdateTime = Date.now();
+        uploadState.total = total;

Also applies to: 354-355


225-225: Remove no‑op loadstart listeners.

They add noise and no behavior.

-        testVideo.addEventListener("loadstart", () => {});
@@
-        video.addEventListener("loadstart", () => {});

Also applies to: 317-317


49-52: Optional: Prefer EffectRuntime mutations and targeted cache updates over router.refresh().

Wrap server action calls with useEffectMutation and update the relevant TanStack Query cache via setQueryData to avoid broad invalidations and improve UX.

If you want, I can sketch the minimal useEffectMutation wrapper and the cache touch points for the caps list.


1-82: Naming: consider kebab‑case filename.

Repo convention suggests module filenames in kebab‑case. Rename to upload-cap-button.tsx if consistent with the rest of the app.

Please confirm whether caps components are standardizing on kebab‑case filenames; I’ll generate a safe rename patch if so.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9de830a and 8b3ab52.

📒 Files selected for processing (1)
  • apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and data fetching in the web app
Web mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData rather than broad invalidations
Client code should use useEffectQuery/useEffectMutation and useRpcClient from apps/web/lib/EffectRuntime.ts; do not create ManagedRuntime inside components

Files:

  • apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx
apps/web/app/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer Server Components for initial data in the Next.js App Router and pass initialData to client components

Files:

  • apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not add inline, block, or docstring comments in any language; code must be self-explanatory

Files:

  • apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript and avoid any; leverage shared types from packages

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Files:

  • apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

On the client, always use useEffectQuery or useEffectMutation from @/lib/EffectRuntime; never call EffectRuntime.run* directly in components.

Files:

  • apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx
🧬 Code graph analysis (1)
apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx (2)
apps/web/app/(org)/dashboard/caps/UploadingContext.tsx (1)
  • UploadStatus (11-33)
apps/web/actions/video/upload.ts (1)
  • createVideoAndGetUploadUrl (149-275)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Analyze (rust)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (10)
apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx (10)

22-24: Remove unused prop type "grey".

It's declared but unused; drop it to keep the signature clean.

-  grey?: boolean;

49-52: Prefer targeted cache updates over router.refresh().

Use useEffectMutation + setQueryData/setQueriesData to update relevant queries instead of a broad refresh.

Which queries should reflect the new upload? If you share their keys, I can sketch the precise cache updates.


490-498: Revoke object URL to prevent memory leak.

thumbnailUrl is never revoked; free it on success/failure.

-    setUploadStatus(undefined);
-    return true;
+    setUploadStatus(undefined);
+    if (thumbnailUrl) URL.revokeObjectURL(thumbnailUrl);
+    return true;
@@
-  } catch (err) {
-    console.error("Video upload failed", err);
-  }
+  } catch (err) {
+    console.error("Video upload failed", err);
+    if (thumbnailUrl) URL.revokeObjectURL(thumbnailUrl);
+  }

224-225: Remove no-op event listeners.

These listeners do nothing and can be dropped.

-        testVideo.addEventListener("loadstart", () => {});
+        // removed no-op listener
-        video.addEventListener("loadstart", () => {});
+        // removed no-op listener

Also applies to: 316-317


342-355: Drop unused progress tracker field.

lastUpdateTime is unused; remove the field and its assignments.

-      const uploadState = {
+      const uploadState = {
         videoId: uploadId,
         uploaded: 0,
         total: 0,
         pendingTask: undefined as ReturnType<typeof setTimeout> | undefined,
-        lastUpdateTime: Date.now(),
       };
@@
-        uploadState.lastUpdateTime = Date.now();

401-413: Throttle UI progress updates to reduce re-renders.

xhr.upload.onprogress can fire very frequently; consider batching to ~100–200ms or via requestAnimationFrame.


149-166: Throttle conversion progress updates.

Similarly, rate-limit setUploadStatus in onProgress to avoid excessive renders.


340-370: Remove newly added inline comments (repo rule: no comments).

Delete the added comments to match the codebase guideline.

Also applies to: 419-419


84-88: Consider moving legacyUploadCap to a shared module.

Aligns with the PR goal of reuse across Loom exporter and this button.


1-1: File naming: prefer kebab-case for module filenames.

Rename to upload-cap-button.tsx per repo convention.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b3ab52 and 8335d3e.

📒 Files selected for processing (1)
  • apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TanStack Query v5 for all client-side server state and data fetching in the web app
Web mutations should call Server Actions directly and perform targeted cache updates with setQueryData/setQueriesData rather than broad invalidations
Client code should use useEffectQuery/useEffectMutation and useRpcClient from apps/web/lib/EffectRuntime.ts; do not create ManagedRuntime inside components

Files:

  • apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx
apps/web/app/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer Server Components for initial data in the Next.js App Router and pass initialData to client components

Files:

  • apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx
**/*.{ts,tsx,js,jsx,rs}

📄 CodeRabbit inference engine (CLAUDE.md)

Do not add inline, block, or docstring comments in any language; code must be self-explanatory

Files:

  • apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript and avoid any; leverage shared types from packages

**/*.{ts,tsx}: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by running pnpm format.

Files:

  • apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g., user-menu.tsx).
Use PascalCase for React/Solid components.

Files:

  • apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

On the client, always use useEffectQuery or useEffectMutation from @/lib/EffectRuntime; never call EffectRuntime.run* directly in components.

Files:

  • apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx
🧬 Code graph analysis (1)
apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx (2)
apps/web/app/(org)/dashboard/caps/UploadingContext.tsx (1)
  • UploadStatus (11-33)
apps/web/actions/video/upload.ts (1)
  • createVideoAndGetUploadUrl (149-275)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
  • GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
  • GitHub Check: Analyze (rust)
🔇 Additional comments (2)
apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx (2)

56-69: LGTM on the button UX/state.

Disabling + spinner tied to isUploading is clear and consistent.


109-120: No action required — createVideoAndGetUploadUrl is a Server Action.

apps/web/actions/video/upload.ts has "use server" at the top and exports createVideoAndGetUploadUrl, so importing/calling it from the client component apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx is valid.

@Brendonovich Brendonovich merged commit 1e2e94b into main Sep 23, 2025
27 checks passed
@Brendonovich Brendonovich deleted the abstract-upload-flow branch September 23, 2025 16:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants