Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 33 additions & 27 deletions apps/web/app/(org)/dashboard/caps/Caps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 <EmptyCapState />;

return (
Expand Down Expand Up @@ -312,7 +320,7 @@ export const Caps = ({
</div>
</>
)}
{data.length > 0 && (
{visibleVideos.length > 0 && (
<>
<div className="flex justify-between items-center mb-6 w-full">
<h1 className="text-2xl font-medium text-gray-12">Videos</h1>
Expand All @@ -322,31 +330,29 @@ export const Caps = ({
{isUploading && (
<UploadPlaceholderCard key={"upload-placeholder"} />
)}
{data
.filter((cap) => !isUploading || cap.id !== uploadingCapId)
.map((cap) => {
return (
<CapCard
key={cap.id}
cap={cap}
analytics={analytics[cap.id] || 0}
onDelete={() => {
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 (
<CapCard
key={video.id}
cap={video}
analytics={analytics[video.id] || 0}
onDelete={() => {
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)}
/>
);
})}
</div>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ export const CapCard = ({

const duplicateMutation = useEffectMutation({
mutationFn: () => withRpc((r) => r.VideoDuplicate(cap.id)),
onSuccess: () => {
router.refresh();
},
});

const handleSharingUpdated = () => {
Expand Down Expand Up @@ -336,13 +339,13 @@ export const CapCard = ({

<DropdownMenuContent align="end" sideOffset={5}>
<DropdownMenuItem
onClick={() =>
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"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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<Video.VideoId[]>([]);
const previousCountRef = useRef<number>(0);

const deleteCaps = useEffectMutation({
const { mutate: deleteCaps, isPending: isDeletingCaps } = useEffectMutation({
mutationFn: Effect.fn(function* (ids: Video.VideoId[]) {
if (ids.length === 0) return;

Expand Down Expand Up @@ -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${
Expand All @@ -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)
Expand Down Expand Up @@ -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 (
Expand All @@ -154,7 +171,7 @@ export default function FolderVideosSection({
<h1 className="text-2xl font-medium text-gray-12">Videos</h1>
</div>
<div className="grid grid-cols-1 gap-4 sm:gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5">
{initialVideos.length === 0 && !isUploading ? (
{visibleVideos.length === 0 && !isUploading ? (
<p className="col-span-full text-gray-9">
No videos in this folder yet. Drag and drop into the folder or
upload.
Expand All @@ -164,30 +181,34 @@ export default function FolderVideosSection({
{isUploading && (
<UploadPlaceholderCard key={"upload-placeholder"} />
)}
{initialVideos
.filter((cap) => !isUploading || cap.id !== uploadingCapId)
.map((video) => (
<CapCard
key={video.id}
cap={video}
analytics={analytics[video.id] || 0}
userId={user?.id}
isLoadingAnalytics={isLoadingAnalytics}
isSelected={selectedCaps.includes(video.id)}
anyCapSelected={selectedCaps.length > 0}
isDeleting={deleteCaps.isPending}
onSelectToggle={() => handleCapSelection(video.id)}
onDelete={() => deleteCaps.mutateAsync(selectedCaps)}
/>
))}
{visibleVideos.map((video) => (
<CapCard
key={video.id}
cap={video}
analytics={analytics[video.id] || 0}
userId={user?.id}
isLoadingAnalytics={isLoadingAnalytics}
isSelected={selectedCaps.includes(video.id)}
anyCapSelected={selectedCaps.length > 0}
isDeleting={isDeletingCaps || isDeletingCap}
onSelectToggle={() => handleCapSelection(video.id)}
onDelete={() => {
if (selectedCaps.length > 0) {
deleteCaps(selectedCaps);
} else {
deleteCap(video.id);
}
}}
/>
))}
</>
)}
</div>
<SelectedCapsBar
selectedCaps={selectedCaps}
setSelectedCaps={setSelectedCaps}
deleteSelectedCaps={() => deleteCaps.mutateAsync(selectedCaps)}
isDeleting={deleteCaps.isPending}
deleteSelectedCaps={() => deleteCaps(selectedCaps)}
isDeleting={isDeletingCaps || isDeletingCap}
/>
</>
);
Expand Down
4 changes: 2 additions & 2 deletions packages/database/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading