@@ -17,6 +17,7 @@ import {
1717 faVideo ,
1818} from "@fortawesome/free-solid-svg-icons" ;
1919import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" ;
20+ import { useMutation } from "@tanstack/react-query" ;
2021import clsx from "clsx" ;
2122import Link from "next/link" ;
2223import { useRouter } from "next/navigation" ;
@@ -27,7 +28,7 @@ import { ConfirmationDialog } from "@/app/(org)/dashboard/_components/Confirmati
2728import { useDashboardContext } from "@/app/(org)/dashboard/Contexts" ;
2829import { Tooltip } from "@/components/Tooltip" ;
2930import { VideoThumbnail } from "@/components/VideoThumbnail" ;
30- import { EffectRuntime } from "@/lib/EffectRuntime" ;
31+ import { useEffectMutation } from "@/lib/EffectRuntime" ;
3132import { withRpc } from "@/lib/Rpcs" ;
3233import { PasswordDialog } from "../PasswordDialog" ;
3334import { SharingDialog } from "../SharingDialog" ;
@@ -97,31 +98,50 @@ export const CapCard = ({
9798 ) ;
9899 const [ copyPressed , setCopyPressed ] = useState ( false ) ;
99100 const [ isDragging , setIsDragging ] = useState ( false ) ;
100- const [ isDownloading , setIsDownloading ] = useState ( false ) ;
101101 const { isSubscribed, setUpgradeModalOpen } = useDashboardContext ( ) ;
102102
103103 const [ confirmOpen , setConfirmOpen ] = useState ( false ) ;
104- const [ removing , setRemoving ] = useState ( false ) ;
105104
106105 const router = useRouter ( ) ;
107106
108- const handleDeleteClick = ( e : React . MouseEvent ) => {
109- e . stopPropagation ( ) ;
110- setConfirmOpen ( true ) ;
111- } ;
112-
113- const confirmRemoveCap = async ( ) => {
114- if ( ! onDelete ) return ;
115- try {
116- setRemoving ( true ) ;
117- await onDelete ( ) ;
118- } catch ( error ) {
107+ const downloadMutation = useMutation ( {
108+ mutationFn : async ( ) => {
109+ const response = await downloadVideo ( cap . id ) ;
110+ if ( response . success && response . downloadUrl ) {
111+ const link = document . createElement ( "a" ) ;
112+ link . href = response . downloadUrl ;
113+ link . download = response . filename ;
114+ link . style . display = "none" ;
115+ document . body . appendChild ( link ) ;
116+ link . click ( ) ;
117+ document . body . removeChild ( link ) ;
118+ } else {
119+ throw new Error ( "Failed to get download URL" ) ;
120+ }
121+ } ,
122+ } ) ;
123+
124+ const deleteMutation = useMutation ( {
125+ mutationFn : async ( ) => {
126+ await onDelete ?.( ) ;
127+ } ,
128+ onError : ( error ) => {
119129 console . error ( "Error deleting cap:" , error ) ;
120- } finally {
121- setRemoving ( false ) ;
130+ } ,
131+ onSettled : ( ) => {
122132 setConfirmOpen ( false ) ;
123- }
124- } ;
133+ } ,
134+ } ) ;
135+
136+ const duplicateMutation = useEffectMutation ( {
137+ mutationFn : ( ) => withRpc ( ( r ) => r . VideoDuplicate ( cap . id ) ) ,
138+ onSuccess : ( ) => {
139+ toast . success ( "Cap duplicated successfully" ) ;
140+ } ,
141+ onError : ( ) => {
142+ toast . error ( "Failed to duplicate cap" ) ;
143+ } ,
144+ } ) ;
125145
126146 const handleSharingUpdated = ( ) => {
127147 router . refresh ( ) ;
@@ -197,45 +217,18 @@ export const CapCard = ({
197217 } ;
198218
199219 const handleDownload = async ( ) => {
200- if ( isDownloading ) return ;
201-
202- setIsDownloading ( true ) ;
203-
204- try {
205- toast . promise (
206- downloadVideo ( cap . id ) . then ( async ( response ) => {
207- if ( response . success && response . downloadUrl ) {
208- const fetchResponse = await fetch ( response . downloadUrl ) ;
209- const blob = await fetchResponse . blob ( ) ;
210-
211- const blobUrl = window . URL . createObjectURL ( blob ) ;
212- const link = document . createElement ( "a" ) ;
213- link . href = blobUrl ;
214- link . download = response . filename ;
215- link . style . display = "none" ;
216- document . body . appendChild ( link ) ;
217- link . click ( ) ;
218- document . body . removeChild ( link ) ;
219-
220- window . URL . revokeObjectURL ( blobUrl ) ;
221- }
222- } ) ,
223- {
224- loading : "Preparing download..." ,
225- success : "Download started successfully" ,
226- error : ( error ) => {
227- if ( error instanceof Error ) {
228- return error . message ;
229- }
230- return "Failed to download video - please try again." ;
231- } ,
232- } ,
233- ) ;
234- } catch ( error ) {
235- console . error ( "Download error:" , error ) ;
236- } finally {
237- setIsDownloading ( false ) ;
238- }
220+ if ( downloadMutation . isPending ) return ;
221+
222+ toast . promise ( downloadMutation . mutateAsync ( ) , {
223+ loading : "Preparing download..." ,
224+ success : "Download started successfully" ,
225+ error : ( error ) => {
226+ if ( error instanceof Error ) {
227+ return error . message ;
228+ }
229+ return "Failed to download video - please try again." ;
230+ } ,
231+ } ) ;
239232 } ;
240233
241234 const handleCardClick = ( e : React . MouseEvent ) => {
@@ -308,7 +301,7 @@ export const CapCard = ({
308301 < CapCardButtons
309302 capId = { cap . id }
310303 copyPressed = { copyPressed }
311- isDownloading = { isDownloading }
304+ isDownloading = { downloadMutation . isPending }
312305 customDomain = { customDomain }
313306 domainVerified = { domainVerified }
314307 handleCopy = { handleCopy }
@@ -340,21 +333,16 @@ export const CapCard = ({
340333
341334 < DropdownMenuContent align = "end" sideOffset = { 5 } >
342335 < DropdownMenuItem
343- onClick = { async ( ) => {
344- try {
345- await EffectRuntime . runPromise (
346- withRpc ( ( r ) => r . VideoDuplicate ( cap . id ) ) ,
347- ) ;
348- toast . success ( "Cap duplicated successfully" ) ;
349- router . refresh ( ) ;
350- } catch ( error ) {
351- toast . error ( "Failed to duplicate cap" ) ;
352- }
353- } }
336+ onClick = { ( ) => duplicateMutation . mutate ( ) }
337+ disabled = { duplicateMutation . isPending }
354338 className = "flex gap-2 items-center rounded-lg"
355339 >
356340 < FontAwesomeIcon className = "size-3" icon = { faCopy } />
357- < p className = "text-sm text-gray-12" > Duplicate</ p >
341+ < p className = "text-sm text-gray-12" >
342+ { duplicateMutation . isPending
343+ ? "Duplicating..."
344+ : "Duplicate" }
345+ </ p >
358346 </ DropdownMenuItem >
359347 < DropdownMenuItem
360348 onClick = { ( ) => {
@@ -376,7 +364,8 @@ export const CapCard = ({
376364 </ DropdownMenuItem >
377365 < DropdownMenuItem
378366 onClick = { ( e ) => {
379- handleDeleteClick ( e ) ;
367+ e . stopPropagation ( ) ;
368+ setConfirmOpen ( true ) ;
380369 } }
381370 className = "flex gap-2 items-center rounded-lg"
382371 >
@@ -392,8 +381,8 @@ export const CapCard = ({
392381 description = { `Are you sure you want to delete the cap "${ cap . name } "? This action cannot be undone.` }
393382 confirmLabel = "Delete"
394383 cancelLabel = "Cancel"
395- loading = { removing }
396- onConfirm = { confirmRemoveCap }
384+ loading = { deleteMutation . isPending }
385+ onConfirm = { ( ) => deleteMutation . mutate ( ) }
397386 onCancel = { ( ) => setConfirmOpen ( false ) }
398387 />
399388 </ div >
0 commit comments