diff --git a/apps/web/app/(org)/dashboard/caps/Caps.tsx b/apps/web/app/(org)/dashboard/caps/Caps.tsx
index 161f451dac..411c4ef503 100644
--- a/apps/web/app/(org)/dashboard/caps/Caps.tsx
+++ b/apps/web/app/(org)/dashboard/caps/Caps.tsx
@@ -8,7 +8,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useQuery } from "@tanstack/react-query";
import { Effect, Exit } from "effect";
import { useRouter, useSearchParams } from "next/navigation";
-import { useEffect, useRef, useState } from "react";
+import { useEffect, useMemo, useRef, useState } from "react";
import { toast } from "sonner";
import { useEffectMutation } from "@/lib/EffectRuntime";
import { Rpc, withRpc } from "@/lib/Rpcs";
@@ -266,6 +266,14 @@ export const Caps = ({
},
});
+ const visibleVideos = useMemo(
+ () =>
+ isUploading && uploadingCapId
+ ? data.filter((video) => video.id !== uploadingCapId)
+ : data,
+ [data, isUploading, uploadingCapId],
+ );
+
if (count === 0) return ;
return (
@@ -312,7 +320,7 @@ export const Caps = ({
>
)}
- {data.length > 0 && (
+ {visibleVideos.length > 0 && (
<>
Videos
@@ -322,31 +330,29 @@ export const Caps = ({
{isUploading && (
)}
- {data
- .filter((cap) => !isUploading || cap.id !== uploadingCapId)
- .map((cap) => {
- return (
- {
- if (selectedCaps.length > 0) {
- deleteCaps(selectedCaps);
- } else {
- deleteCap(cap.id);
- }
- }}
- userId={user?.id}
- customDomain={customDomain}
- isLoadingAnalytics={isLoadingAnalytics}
- domainVerified={domainVerified}
- isSelected={selectedCaps.includes(cap.id)}
- anyCapSelected={anyCapSelected}
- onSelectToggle={() => handleCapSelection(cap.id)}
- />
- );
- })}
+ {visibleVideos.map((video) => {
+ return (
+ {
+ if (selectedCaps.length > 0) {
+ deleteCaps(selectedCaps);
+ } else {
+ deleteCap(video.id);
+ }
+ }}
+ userId={user?.id}
+ customDomain={customDomain}
+ isLoadingAnalytics={isLoadingAnalytics}
+ domainVerified={domainVerified}
+ isSelected={selectedCaps.includes(video.id)}
+ anyCapSelected={anyCapSelected}
+ onSelectToggle={() => handleCapSelection(video.id)}
+ />
+ );
+ })}
>
)}
diff --git a/apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx b/apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx
index 98374c03cd..efb1b4b6f2 100644
--- a/apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx
+++ b/apps/web/app/(org)/dashboard/caps/components/CapCard/CapCard.tsx
@@ -144,6 +144,9 @@ export const CapCard = ({
const duplicateMutation = useEffectMutation({
mutationFn: () => withRpc((r) => r.VideoDuplicate(cap.id)),
+ onSuccess: () => {
+ router.refresh();
+ },
});
const handleSharingUpdated = () => {
@@ -336,13 +339,13 @@ export const CapCard = ({
+ onClick={() => {
toast.promise(duplicateMutation.mutateAsync(), {
loading: "Duplicating cap...",
success: "Cap duplicated successfully",
error: "Failed to duplicate cap",
- })
- }
+ });
+ }}
disabled={duplicateMutation.isPending}
className="flex gap-2 items-center rounded-lg"
>
diff --git a/apps/web/app/(org)/dashboard/folder/[id]/components/FolderVideosSection.tsx b/apps/web/app/(org)/dashboard/folder/[id]/components/FolderVideosSection.tsx
index 2ad39372b6..1228636d20 100644
--- a/apps/web/app/(org)/dashboard/folder/[id]/components/FolderVideosSection.tsx
+++ b/apps/web/app/(org)/dashboard/folder/[id]/components/FolderVideosSection.tsx
@@ -4,11 +4,11 @@ import type { Video } from "@cap/web-domain";
import { useQuery } from "@tanstack/react-query";
import { Effect, Exit } from "effect";
import { useRouter } from "next/navigation";
-import { useRef, useState } from "react";
+import { useMemo, useRef, useState } from "react";
import { toast } from "sonner";
import { useDashboardContext } from "@/app/(org)/dashboard/Contexts";
import { useEffectMutation } from "@/lib/EffectRuntime";
-import { Rpc } from "@/lib/Rpcs";
+import { Rpc, withRpc } from "@/lib/Rpcs";
import type { VideoData } from "../../../caps/Caps";
import { CapCard } from "../../../caps/components/CapCard/CapCard";
import { SelectedCapsBar } from "../../../caps/components/SelectedCapsBar";
@@ -28,12 +28,12 @@ export default function FolderVideosSection({
}: FolderVideosSectionProps) {
const router = useRouter();
const { isUploading, uploadingCapId } = useUploadingContext();
- const { activeOrganization, user } = useDashboardContext();
+ const { user } = useDashboardContext();
const [selectedCaps, setSelectedCaps] = useState([]);
const previousCountRef = useRef(0);
- const deleteCaps = useEffectMutation({
+ const { mutate: deleteCaps, isPending: isDeletingCaps } = useEffectMutation({
mutationFn: Effect.fn(function* (ids: Video.VideoId[]) {
if (ids.length === 0) return;
@@ -63,9 +63,7 @@ export default function FolderVideosSection({
}).pipe(Effect.fork);
toast.promise(Effect.runPromise(fiber.await.pipe(Effect.flatten)), {
- loading: `Deleting ${selectedCaps.length} cap${
- selectedCaps.length === 1 ? "" : "s"
- }...`,
+ loading: `Deleting ${ids.length} cap${ids.length === 1 ? "" : "s"}...`,
success: (data) => {
if (data.error) {
return `Successfully deleted ${data.success} cap${
@@ -90,6 +88,17 @@ export default function FolderVideosSection({
},
});
+ const { mutate: deleteCap, isPending: isDeletingCap } = useEffectMutation({
+ mutationFn: (id: Video.VideoId) => withRpc((r) => r.VideoDelete(id)),
+ onSuccess: () => {
+ toast.success("Cap deleted successfully");
+ router.refresh();
+ },
+ onError: () => {
+ toast.error("Failed to delete cap");
+ },
+ });
+
const handleCapSelection = (capId: Video.VideoId) => {
setSelectedCaps((prev) => {
const newSelection = prev.includes(capId)
@@ -146,6 +155,14 @@ export default function FolderVideosSection({
refetchOnMount: true,
});
+ const visibleVideos = useMemo(
+ () =>
+ isUploading && uploadingCapId
+ ? initialVideos.filter((video) => video.id !== uploadingCapId)
+ : initialVideos,
+ [initialVideos, isUploading, uploadingCapId],
+ );
+
const analytics = analyticsData || {};
return (
@@ -154,7 +171,7 @@ export default function FolderVideosSection({
Videos
- {initialVideos.length === 0 && !isUploading ? (
+ {visibleVideos.length === 0 && !isUploading ? (
No videos in this folder yet. Drag and drop into the folder or
upload.
@@ -164,30 +181,34 @@ export default function FolderVideosSection({
{isUploading && (
)}
- {initialVideos
- .filter((cap) => !isUploading || cap.id !== uploadingCapId)
- .map((video) => (
- 0}
- isDeleting={deleteCaps.isPending}
- onSelectToggle={() => handleCapSelection(video.id)}
- onDelete={() => deleteCaps.mutateAsync(selectedCaps)}
- />
- ))}
+ {visibleVideos.map((video) => (
+ 0}
+ isDeleting={isDeletingCaps || isDeletingCap}
+ onSelectToggle={() => handleCapSelection(video.id)}
+ onDelete={() => {
+ if (selectedCaps.length > 0) {
+ deleteCaps(selectedCaps);
+ } else {
+ deleteCap(video.id);
+ }
+ }}
+ />
+ ))}
>
)}
deleteCaps.mutateAsync(selectedCaps)}
- isDeleting={deleteCaps.isPending}
+ deleteSelectedCaps={() => deleteCaps(selectedCaps)}
+ isDeleting={isDeletingCaps || isDeletingCap}
/>
>
);
diff --git a/packages/database/schema.ts b/packages/database/schema.ts
index 8e6f908dab..41c4df63e4 100644
--- a/packages/database/schema.ts
+++ b/packages/database/schema.ts
@@ -72,8 +72,8 @@ export const users = mysqlTable(
pauseViews: boolean;
pauseReactions: boolean;
};
- // For analytics.
- // Adding in preferences so we don't have to
+ // For analytics.
+ // Adding in preferences so we don't have to
// add a new column and can be dynamic going forward.
trackedEvents?: {
user_signed_up?: boolean;