Skip to content

Commit 21bbfe9

Browse files
committed
use mutation a bunch
1 parent b0773ee commit 21bbfe9

File tree

14 files changed

+407
-406
lines changed

14 files changed

+407
-406
lines changed

.eslintrc.ts

Lines changed: 0 additions & 10 deletions
This file was deleted.

apps/web/app/(org)/dashboard/_components/Notifications/NotificationHeader.tsx

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,34 @@
11
import { faCheckDouble } from "@fortawesome/free-solid-svg-icons";
22
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3-
import { useQueryClient } from "@tanstack/react-query";
3+
import { useMutation, useQueryClient } from "@tanstack/react-query";
44
import clsx from "clsx";
5-
import { useState } from "react";
65
import { toast } from "sonner";
76
import { markAsRead } from "@/actions/notifications/mark-as-read";
87

98
export const NotificationHeader = () => {
10-
const [loading, setLoading] = useState(false);
119
const queryClient = useQueryClient();
12-
13-
const markAsReadHandler = async () => {
14-
try {
15-
setLoading(true);
16-
await markAsRead();
10+
const mutation = useMutation({
11+
mutationFn: () => markAsRead(),
12+
onSuccess: () => {
1713
toast.success("Notifications marked as read");
1814
queryClient.invalidateQueries({
1915
queryKey: ["notifications"],
2016
});
21-
} catch (error) {
17+
},
18+
onError: (error) => {
2219
console.error("Error marking notifications as read:", error);
2320
toast.error("Failed to mark notifications as read");
24-
} finally {
25-
setLoading(false);
26-
}
27-
};
21+
},
22+
});
2823

2924
return (
3025
<div className="flex justify-between items-center px-6 py-3 rounded-t-xl border bg-gray-3 border-gray-4">
3126
<p className="text-md text-gray-12">Notifications</p>
3227
<div
33-
onClick={markAsReadHandler}
28+
onClick={() => mutation.mutate()}
3429
className={clsx(
3530
"flex gap-1 items-center transition-opacity duration-200 cursor-pointer hover:opacity-70",
36-
loading ? "opacity-50 cursor-not-allowed" : "",
31+
mutation.isPending ? "opacity-50 cursor-not-allowed" : "",
3732
)}
3833
>
3934
<FontAwesomeIcon

apps/web/app/(org)/dashboard/caps/Caps.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -244,21 +244,21 @@ export const Caps = ({
244244

245245
return yield* fiber.await.pipe(Effect.flatten);
246246
}),
247-
onSuccess: Effect.fn(function* () {
247+
onSuccess: () => {
248248
setSelectedCaps([]);
249249
router.refresh();
250-
}),
250+
},
251251
});
252252

253253
const deleteCap = useEffectMutation({
254254
mutationFn: (id: Video.VideoId) => withRpc((r) => r.VideoDelete(id)),
255-
onSuccess: Effect.fn(function* () {
255+
onSuccess: () => {
256256
toast.success("Cap deleted successfully");
257257
router.refresh();
258-
}),
259-
onError: Effect.fn(function* () {
258+
},
259+
onError: () => {
260260
toast.error("Failed to delete cap");
261-
}),
261+
},
262262
});
263263

264264
if (count === 0) return <EmptyCapState />;

apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx

Lines changed: 62 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
faVideo,
1818
} from "@fortawesome/free-solid-svg-icons";
1919
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
20+
import { useMutation } from "@tanstack/react-query";
2021
import clsx from "clsx";
2122
import Link from "next/link";
2223
import { useRouter } from "next/navigation";
@@ -27,7 +28,7 @@ import { ConfirmationDialog } from "@/app/(org)/dashboard/_components/Confirmati
2728
import { useDashboardContext } from "@/app/(org)/dashboard/Contexts";
2829
import { Tooltip } from "@/components/Tooltip";
2930
import { VideoThumbnail } from "@/components/VideoThumbnail";
30-
import { EffectRuntime } from "@/lib/EffectRuntime";
31+
import { useEffectMutation } from "@/lib/EffectRuntime";
3132
import { withRpc } from "@/lib/Rpcs";
3233
import { PasswordDialog } from "../PasswordDialog";
3334
import { 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>

apps/web/app/(org)/dashboard/caps/components/Folder.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { Folder } from "@cap/web-domain";
33
import { faTrash } from "@fortawesome/free-solid-svg-icons";
44
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
55
import { Fit, Layout, useRive } from "@rive-app/react-canvas";
6+
import { useMutation } from "@tanstack/react-query";
67
import clsx from "clsx";
78
import { Effect } from "effect";
89
import Link from "next/link";
@@ -45,6 +46,7 @@ const FolderCard = ({
4546
const [isDragOver, setIsDragOver] = useState(false);
4647
const [isMovingVideo, setIsMovingVideo] = useState(false);
4748
const { activeOrganization } = useDashboardContext();
49+
4850
// Use a ref to track drag state to avoid re-renders during animation
4951
const dragStateRef = useRef({
5052
isDragging: false,
@@ -73,16 +75,14 @@ const FolderCard = ({
7375

7476
const deleteFolder = useEffectMutation({
7577
mutationFn: (id: Folder.FolderId) => withRpc((r) => r.FolderDelete(id)),
76-
onSuccess: Effect.fn(function* () {
78+
onSuccess: () => {
7779
router.refresh();
7880
toast.success("Folder deleted successfully");
79-
}),
80-
onError: Effect.fn(function* () {
81-
toast.error("Failed to delete folder");
82-
}),
83-
onSettled: Effect.fn(function* () {
8481
setConfirmDeleteFolderOpen(false);
85-
}),
82+
},
83+
onError: () => {
84+
toast.error("Failed to delete folder");
85+
},
8686
});
8787

8888
useEffect(() => {

apps/web/app/(org)/dashboard/caps/components/SelectedCapsBar.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,6 @@ export const SelectedCapsBar = ({
2525
}: SelectedCapsBarProps) => {
2626
const [confirmOpen, setConfirmOpen] = useState(false);
2727

28-
const handleDeleteClick = () => {
29-
setConfirmOpen(true);
30-
};
31-
3228
const handleConfirmDelete = async () => {
3329
await deleteSelectedCaps();
3430
setConfirmOpen(false);
@@ -71,7 +67,7 @@ export const SelectedCapsBar = ({
7167
</Button>
7268
<Button
7369
variant="destructive"
74-
onClick={handleDeleteClick}
70+
onClick={() => setConfirmOpen(true)}
7571
disabled={isDeleting}
7672
className="size-[40px] min-w-[unset] p-0"
7773
spinner={isDeleting}

0 commit comments

Comments
 (0)