From d910eff35f159f43c910b64001024cf05501c42a Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Tue, 23 Sep 2025 15:35:19 +0800 Subject: [PATCH 1/8] AI slop --- .../(org)/dashboard/caps/UploadingContext.tsx | 47 ++++++++++++++----- .../caps/components/UploadPlaceholderCard.tsx | 4 +- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx b/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx index 18def19ad0..78d21f5a93 100644 --- a/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx +++ b/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx @@ -1,12 +1,9 @@ "use client"; import type React from "react"; -import { createContext, useContext, useEffect, useState } from "react"; - -interface UploadingContextType { - uploadStatus: UploadStatus | undefined; - setUploadStatus: (state: UploadStatus | undefined) => void; -} +import { createContext, useContext, useEffect } from "react"; +import { Store } from "@tanstack/store"; +import { useStore } from "@tanstack/react-store"; export type UploadStatus = | { @@ -32,6 +29,19 @@ export type UploadStatus = thumbnailUrl: string | undefined; }; +interface UploadingStore { + uploadStatus: UploadStatus | undefined; +} + +const uploadingStore = new Store({ + uploadStatus: undefined, +}); + +interface UploadingContextType { + uploadStatus: UploadStatus | undefined; + setUploadStatus: (state: UploadStatus | undefined) => void; +} + const UploadingContext = createContext( undefined, ); @@ -45,13 +55,28 @@ export function useUploadingContext() { return context; } +export function useUploadingStore() { + return useStore(uploadingStore); +} + +export function useUploadStatus() { + return useStore(uploadingStore, (state) => state.uploadStatus); +} + +export function setUploadStatus(status: UploadStatus | undefined) { + uploadingStore.setState((state) => ({ + ...state, + uploadStatus: status, + })); +} + export function UploadingProvider({ children }: { children: React.ReactNode }) { - const [state, setState] = useState(); + const uploadStatus = useUploadStatus(); // Prevent the user closing the tab while uploading useEffect(() => { const handleBeforeUnload = (e: BeforeUnloadEvent) => { - if (state?.status) { + if (uploadStatus?.status) { e.preventDefault(); // Chrome requires returnValue to be set e.returnValue = ""; @@ -61,13 +86,13 @@ export function UploadingProvider({ children }: { children: React.ReactNode }) { window.addEventListener("beforeunload", handleBeforeUnload); return () => window.removeEventListener("beforeunload", handleBeforeUnload); - }, [state]); + }, [uploadStatus]); return ( {children} diff --git a/apps/web/app/(org)/dashboard/caps/components/UploadPlaceholderCard.tsx b/apps/web/app/(org)/dashboard/caps/components/UploadPlaceholderCard.tsx index dd69664905..d071d3d319 100644 --- a/apps/web/app/(org)/dashboard/caps/components/UploadPlaceholderCard.tsx +++ b/apps/web/app/(org)/dashboard/caps/components/UploadPlaceholderCard.tsx @@ -2,12 +2,12 @@ import { LogoSpinner } from "@cap/ui"; import { calculateStrokeDashoffset, getProgressCircleConfig } from "@cap/utils"; -import { type UploadStatus, useUploadingContext } from "../UploadingContext"; +import { type UploadStatus, useUploadStatus } from "../UploadingContext"; const { circumference } = getProgressCircleConfig(); export const UploadPlaceholderCard = () => { - const { uploadStatus } = useUploadingContext(); + const uploadStatus = useUploadStatus(); const strokeDashoffset = calculateStrokeDashoffset( uploadStatus && (uploadStatus.status === "converting" || From 850767733aea6df6401745f281b40bf624bbb2bf Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Tue, 23 Sep 2025 15:49:45 +0800 Subject: [PATCH 2/8] format --- apps/web/app/(org)/dashboard/caps/UploadingContext.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx b/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx index 78d21f5a93..d67ea21797 100644 --- a/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx +++ b/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx @@ -1,9 +1,9 @@ "use client"; +import { useStore } from "@tanstack/react-store"; +import { Store } from "@tanstack/store"; import type React from "react"; import { createContext, useContext, useEffect } from "react"; -import { Store } from "@tanstack/store"; -import { useStore } from "@tanstack/react-store"; export type UploadStatus = | { From 076fb472de9f2847dfe94c993695135d34fb32aa Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Tue, 23 Sep 2025 15:53:27 +0800 Subject: [PATCH 3/8] put context into React lifecycle --- .../(org)/dashboard/caps/UploadingContext.tsx | 34 +++++++------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx b/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx index d67ea21797..4903b255e6 100644 --- a/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx +++ b/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx @@ -3,7 +3,7 @@ import { useStore } from "@tanstack/react-store"; import { Store } from "@tanstack/store"; import type React from "react"; -import { createContext, useContext, useEffect } from "react"; +import { createContext, useContext, useEffect, useRef } from "react"; export type UploadStatus = | { @@ -33,10 +33,6 @@ interface UploadingStore { uploadStatus: UploadStatus | undefined; } -const uploadingStore = new Store({ - uploadStatus: undefined, -}); - interface UploadingContextType { uploadStatus: UploadStatus | undefined; setUploadStatus: (state: UploadStatus | undefined) => void; @@ -55,23 +51,19 @@ export function useUploadingContext() { return context; } -export function useUploadingStore() { - return useStore(uploadingStore); -} - -export function useUploadStatus() { - return useStore(uploadingStore, (state) => state.uploadStatus); -} - -export function setUploadStatus(status: UploadStatus | undefined) { - uploadingStore.setState((state) => ({ - ...state, - uploadStatus: status, - })); -} - export function UploadingProvider({ children }: { children: React.ReactNode }) { - const uploadStatus = useUploadStatus(); + const uploadingStoreRef = useRef(new Store({ + uploadStatus: undefined, + })); + + const uploadStatus = useStore(uploadingStoreRef.current, (state) => state.uploadStatus); + + const setUploadStatus = (status: UploadStatus | undefined) => { + uploadingStoreRef.current.setState((state) => ({ + ...state, + uploadStatus: status, + })); + }; // Prevent the user closing the tab while uploading useEffect(() => { From 2e6e78708603013f3d33a1ae4456387f365d3127 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Wed, 24 Sep 2025 08:44:41 +0800 Subject: [PATCH 4/8] formast --- .../(org)/dashboard/caps/UploadingContext.tsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx b/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx index 4903b255e6..2feeea7470 100644 --- a/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx +++ b/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx @@ -52,12 +52,17 @@ export function useUploadingContext() { } export function UploadingProvider({ children }: { children: React.ReactNode }) { - const uploadingStoreRef = useRef(new Store({ - uploadStatus: undefined, - })); - - const uploadStatus = useStore(uploadingStoreRef.current, (state) => state.uploadStatus); - + const uploadingStoreRef = useRef( + new Store({ + uploadStatus: undefined, + }), + ); + + const uploadStatus = useStore( + uploadingStoreRef.current, + (state) => state.uploadStatus, + ); + const setUploadStatus = (status: UploadStatus | undefined) => { uploadingStoreRef.current.setState((state) => ({ ...state, From 15223ed38a079324465394fee37f00eeda1c7328 Mon Sep 17 00:00:00 2001 From: Oscar Beaumont Date: Wed, 24 Sep 2025 12:53:59 +0800 Subject: [PATCH 5/8] move context to Tanstack Store + fix auth on thumbnail request --- apps/web/app/(org)/dashboard/caps/Caps.tsx | 8 +-- .../(org)/dashboard/caps/UploadingContext.tsx | 70 +++++++++++-------- .../caps/components/UploadCapButton.tsx | 18 +++-- .../caps/components/UploadPlaceholderCard.tsx | 6 +- .../[id]/components/FolderVideosSection.tsx | 12 ++-- apps/web/app/api/thumbnail/route.ts | 29 ++------ apps/web/components/VideoThumbnail.tsx | 41 ++++------- apps/web/package.json | 1 + pnpm-lock.yaml | 27 +++---- 9 files changed, 103 insertions(+), 109 deletions(-) diff --git a/apps/web/app/(org)/dashboard/caps/Caps.tsx b/apps/web/app/(org)/dashboard/caps/Caps.tsx index b294f513a7..e83dd11aa3 100644 --- a/apps/web/app/(org)/dashboard/caps/Caps.tsx +++ b/apps/web/app/(org)/dashboard/caps/Caps.tsx @@ -24,7 +24,7 @@ import { CapPagination } from "./components/CapPagination"; import { EmptyCapState } from "./components/EmptyCapState"; import type { FolderDataType } from "./components/Folder"; import Folder from "./components/Folder"; -import { useUploadingContext } from "./UploadingContext"; +import { useUploadingContext, useUploadingStatus } from "./UploadingContext"; export type VideoData = { id: Video.VideoId; @@ -74,7 +74,6 @@ export const Caps = ({ const previousCountRef = useRef(0); const [selectedCaps, setSelectedCaps] = useState([]); const [isDraggingCap, setIsDraggingCap] = useState(false); - const { uploadStatus } = useUploadingContext(); const anyCapSelected = selectedCaps.length > 0; @@ -258,10 +257,7 @@ export const Caps = ({ onError: () => toast.error("Failed to delete cap"), }); - const isUploading = uploadStatus !== undefined; - const uploadingCapId = - uploadStatus && "capId" in uploadStatus ? uploadStatus.capId : undefined; - + const [isUploading, uploadingCapId] = useUploadingStatus(); const visibleVideos = useMemo( () => isUploading && uploadingCapId diff --git a/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx b/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx index 2feeea7470..a24220c151 100644 --- a/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx +++ b/apps/web/app/(org)/dashboard/caps/UploadingContext.tsx @@ -3,7 +3,7 @@ import { useStore } from "@tanstack/react-store"; import { Store } from "@tanstack/store"; import type React from "react"; -import { createContext, useContext, useEffect, useRef } from "react"; +import { createContext, useContext, useEffect, useState } from "react"; export type UploadStatus = | { @@ -29,12 +29,8 @@ export type UploadStatus = thumbnailUrl: string | undefined; }; -interface UploadingStore { - uploadStatus: UploadStatus | undefined; -} - interface UploadingContextType { - uploadStatus: UploadStatus | undefined; + uploadingStore: Store<{ uploadStatus?: UploadStatus }>; setUploadStatus: (state: UploadStatus | undefined) => void; } @@ -51,26 +47,49 @@ export function useUploadingContext() { return context; } +export function useUploadingStatus() { + const { uploadingStore } = useUploadingContext(); + return useStore( + uploadingStore, + (s) => + [ + s.uploadStatus !== undefined, + s.uploadStatus && "capId" in s.uploadStatus + ? s.uploadStatus.capId + : null, + ] as const, + ); +} + export function UploadingProvider({ children }: { children: React.ReactNode }) { - const uploadingStoreRef = useRef( - new Store({ - uploadStatus: undefined, - }), + const [uploadingStore] = useState>( + () => new Store({}), ); - const uploadStatus = useStore( - uploadingStoreRef.current, - (state) => state.uploadStatus, + return ( + { + uploadingStore.setState((state) => ({ + ...state, + uploadStatus: status, + })); + }, + }} + > + {children} + + + ); +} - const setUploadStatus = (status: UploadStatus | undefined) => { - uploadingStoreRef.current.setState((state) => ({ - ...state, - uploadStatus: status, - })); - }; +// Separated to prevent rerendering whole tree +function ForbidLeaveWhenUploading() { + const { uploadingStore } = useUploadingContext(); + const uploadStatus = useStore(uploadingStore, (state) => state.uploadStatus); - // Prevent the user closing the tab while uploading useEffect(() => { const handleBeforeUnload = (e: BeforeUnloadEvent) => { if (uploadStatus?.status) { @@ -85,14 +104,5 @@ export function UploadingProvider({ children }: { children: React.ReactNode }) { return () => window.removeEventListener("beforeunload", handleBeforeUnload); }, [uploadStatus]); - return ( - - {children} - - ); + return null; } diff --git a/apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx b/apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx index d94a0427ef..3a6a5e9c2c 100644 --- a/apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx +++ b/apps/web/app/(org)/dashboard/caps/components/UploadCapButton.tsx @@ -14,6 +14,9 @@ import { useUploadingContext, } from "@/app/(org)/dashboard/caps/UploadingContext"; import { UpgradeModal } from "@/components/UpgradeModal"; +import { useStore } from "@tanstack/react-store"; +import { QueryClient, useQueryClient } from "@tanstack/react-query"; +import { imageUrlQuery } from "@/components/VideoThumbnail"; export const UploadCapButton = ({ size = "md", @@ -25,9 +28,11 @@ export const UploadCapButton = ({ }) => { const { user } = useDashboardContext(); const inputRef = useRef(null); - const { uploadStatus, setUploadStatus } = useUploadingContext(); + const { uploadingStore, setUploadStatus } = useUploadingContext(); + const isUploading = useStore(uploadingStore, (s) => !!s.uploadStatus); const [upgradeModalOpen, setUpgradeModalOpen] = useState(false); const router = useRouter(); + const queryClient = useQueryClient(); const handleClick = () => { if (!user) return; @@ -46,13 +51,16 @@ export const UploadCapButton = ({ const file = e.target.files?.[0]; if (!file || !user) return; - const ok = await legacyUploadCap(file, folderId, setUploadStatus); + const ok = await legacyUploadCap( + file, + folderId, + setUploadStatus, + queryClient, + ); if (ok) router.refresh(); if (inputRef.current) inputRef.current.value = ""; }; - const isUploading = !!uploadStatus; - return ( <>