Skip to content

Commit 2cebeaa

Browse files
authored
improvement: only video owners will be able to perform additional actions (#1068)
* only video owners will be able to perform additional actions on caps * formatting * formatting * unused
1 parent 666d20f commit 2cebeaa

File tree

7 files changed

+150
-244
lines changed

7 files changed

+150
-244
lines changed

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

Lines changed: 111 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { VideoMetadata } from "@cap/database/types";
22
import {
3-
Button,
43
DropdownMenu,
54
DropdownMenuContent,
65
DropdownMenuItem,
@@ -11,7 +10,9 @@ import { HttpClient } from "@effect/platform";
1110
import {
1211
faCheck,
1312
faCopy,
13+
faDownload,
1414
faEllipsis,
15+
faLink,
1516
faLock,
1617
faTrash,
1718
faUnlock,
@@ -25,20 +26,18 @@ import Link from "next/link";
2526
import { useRouter } from "next/navigation";
2627
import { type PropsWithChildren, useState } from "react";
2728
import { toast } from "sonner";
28-
import { downloadVideo } from "@/actions/videos/download";
2929
import { ConfirmationDialog } from "@/app/(org)/dashboard/_components/ConfirmationDialog";
3030
import { useDashboardContext } from "@/app/(org)/dashboard/Contexts";
3131
import ProgressCircle, {
3232
useUploadProgress,
3333
} from "@/app/s/[videoId]/_components/ProgressCircle";
34-
import { Tooltip } from "@/components/Tooltip";
3534
import { VideoThumbnail } from "@/components/VideoThumbnail";
3635
import { useEffectMutation } from "@/lib/EffectRuntime";
3736
import { withRpc } from "@/lib/Rpcs";
3837
import { PasswordDialog } from "../PasswordDialog";
3938
import { SharingDialog } from "../SharingDialog";
4039
import { CapCardAnalytics } from "./CapCardAnalytics";
41-
import { CapCardButtons } from "./CapCardButtons";
40+
import { CapCardButton } from "./CapCardButton";
4241
import { CapCardContent } from "./CapCardContent";
4342

4443
export interface CapCardProps extends PropsWithChildren {
@@ -305,51 +304,102 @@ export const CapCard = ({
305304
{anyCapSelected && !sharedCapCard && (
306305
<div className="absolute inset-0 z-10" onClick={handleCardClick} />
307306
)}
308-
{!sharedCapCard && (
309-
<div
310-
className={clsx(
311-
"flex absolute duration-200",
312-
anyCapSelected
313-
? "opacity-0"
314-
: isDropdownOpen
315-
? "opacity-100"
316-
: "opacity-0 group-hover:opacity-100",
317-
"top-2 right-2 flex-col gap-2 z-[20]",
318-
)}
319-
>
320-
<CapCardButtons
321-
capId={cap.id}
322-
copyPressed={copyPressed}
323-
isDownloading={downloadMutation.isPending}
324-
customDomain={customDomain}
325-
domainVerified={domainVerified}
326-
handleCopy={handleCopy}
327-
handleDownload={handleDownload}
328-
/>
329307

330-
<DropdownMenu modal={false} onOpenChange={setIsDropdownOpen}>
331-
<Tooltip content="More options">
332-
<DropdownMenuTrigger asChild>
333-
<Button
334-
onClick={(e) => {
335-
e.stopPropagation();
336-
}}
337-
className={clsx(
338-
"!size-8 hover:bg-gray-5 hover:border-gray-7 rounded-full min-w-fit !p-0 delay-75",
339-
isDropdownOpen ? "bg-gray-5 border-gray-7" : "",
340-
)}
341-
variant="white"
342-
size="sm"
343-
aria-label="More options"
308+
<div
309+
className={clsx(
310+
"flex absolute duration-200",
311+
anyCapSelected
312+
? "opacity-0"
313+
: isDropdownOpen
314+
? "opacity-100"
315+
: "opacity-0 group-hover:opacity-100",
316+
"top-2 right-2 flex-col gap-2 z-[20]",
317+
)}
318+
>
319+
<CapCardButton
320+
tooltipContent="Copy link"
321+
onClick={(e) => {
322+
e.stopPropagation();
323+
handleCopy(cap.id);
324+
}}
325+
className="delay-0"
326+
icon={() => {
327+
return !copyPressed ? (
328+
<FontAwesomeIcon
329+
className="text-gray-12 size-4"
330+
icon={faLink}
331+
/>
332+
) : (
333+
<svg
334+
xmlns="http://www.w3.org/2000/svg"
335+
width="24"
336+
height="24"
337+
viewBox="0 0 24 24"
338+
fill="none"
339+
stroke="currentColor"
340+
strokeWidth="2"
341+
strokeLinecap="round"
342+
strokeLinejoin="round"
343+
className="text-gray-12 size-5 svgpathanimation"
344+
>
345+
<path d="M20 6 9 17l-5-5" />
346+
</svg>
347+
);
348+
}}
349+
/>
350+
<CapCardButton
351+
tooltipContent="Download Cap"
352+
onClick={(e) => {
353+
e.stopPropagation();
354+
handleDownload();
355+
}}
356+
disabled={downloadMutation.isPending}
357+
className="delay-25"
358+
icon={() => {
359+
return downloadMutation.isPending ? (
360+
<div className="animate-spin size-3">
361+
<svg
362+
className="size-3"
363+
xmlns="http://www.w3.org/2000/svg"
364+
fill="none"
365+
viewBox="0 0 24 24"
366+
aria-hidden="true"
344367
>
345-
<FontAwesomeIcon
346-
className="text-gray-12 size-4"
347-
icon={faEllipsis}
348-
/>
349-
</Button>
350-
</DropdownMenuTrigger>
351-
</Tooltip>
368+
<circle
369+
className="opacity-25"
370+
cx="12"
371+
cy="12"
372+
r="10"
373+
stroke="currentColor"
374+
strokeWidth="4"
375+
></circle>
376+
<path
377+
className="opacity-75"
378+
fill="currentColor"
379+
d="m2 12c0-5.523 4.477-10 10-10v3c-3.866 0-7 3.134-7 7s3.134 7 7 7 7-3.134 7-7c0-1.457-.447-2.808-1.208-3.926l2.4-1.6c1.131 1.671 1.808 3.677 1.808 5.526 0 5.523-4.477 10-10 10s-10-4.477-10-10z"
380+
></path>
381+
</svg>
382+
</div>
383+
) : (
384+
<FontAwesomeIcon
385+
className="text-gray-12 size-3"
386+
icon={faDownload}
387+
/>
388+
);
389+
}}
390+
/>
352391

392+
{isOwner && (
393+
<DropdownMenu modal={false} onOpenChange={setIsDropdownOpen}>
394+
<DropdownMenuTrigger>
395+
<CapCardButton
396+
tooltipContent="More options"
397+
className="delay-75"
398+
icon={() => (
399+
<FontAwesomeIcon className="size-4" icon={faEllipsis} />
400+
)}
401+
/>
402+
</DropdownMenuTrigger>
353403
<DropdownMenuContent align="end" sideOffset={5}>
354404
<DropdownMenuItem
355405
onClick={() => {
@@ -392,19 +442,21 @@ export const CapCard = ({
392442
</DropdownMenuItem>
393443
</DropdownMenuContent>
394444
</DropdownMenu>
395-
<ConfirmationDialog
396-
open={confirmOpen}
397-
icon={<FontAwesomeIcon icon={faVideo} />}
398-
title="Delete Cap"
399-
description={`Are you sure you want to delete the cap "${cap.name}"? This action cannot be undone.`}
400-
confirmLabel={deleteMutation.isPending ? "Deleting..." : "Delete"}
401-
cancelLabel="Cancel"
402-
loading={deleteMutation.isPending}
403-
onConfirm={() => deleteMutation.mutate()}
404-
onCancel={() => setConfirmOpen(false)}
405-
/>
406-
</div>
407-
)}
445+
)}
446+
447+
<ConfirmationDialog
448+
open={confirmOpen}
449+
icon={<FontAwesomeIcon icon={faVideo} />}
450+
title="Delete Cap"
451+
description={`Are you sure you want to delete the cap "${cap.name}"? This action cannot be undone.`}
452+
confirmLabel={deleteMutation.isPending ? "Deleting..." : "Delete"}
453+
cancelLabel="Cancel"
454+
loading={deleteMutation.isPending}
455+
onConfirm={() => deleteMutation.mutate()}
456+
onCancel={() => setConfirmOpen(false)}
457+
/>
458+
</div>
459+
408460
{!sharedCapCard && onSelectToggle && (
409461
<div
410462
className={clsx(
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Button } from "@cap/ui";
2+
import clsx from "clsx";
3+
import type { MouseEvent, ReactNode } from "react";
4+
import { Tooltip } from "@/components/Tooltip";
5+
6+
interface CapCardButtonProps {
7+
tooltipContent: string;
8+
onClick?: (e: MouseEvent) => void;
9+
disabled?: boolean;
10+
className: string;
11+
icon: () => ReactNode;
12+
}
13+
14+
export const CapCardButton = ({
15+
tooltipContent,
16+
onClick = () => {},
17+
disabled,
18+
className,
19+
icon,
20+
}: CapCardButtonProps) => {
21+
return (
22+
<Tooltip key={tooltipContent} content={tooltipContent}>
23+
<Button
24+
onClick={(e) => onClick?.(e)}
25+
disabled={disabled}
26+
className={clsx(
27+
`!size-8 hover:bg-gray-5 hover:border-gray-7 rounded-full min-w-fit !p-0`,
28+
className,
29+
)}
30+
variant="white"
31+
size="sm"
32+
aria-label={tooltipContent}
33+
>
34+
{icon()}
35+
</Button>
36+
</Tooltip>
37+
);
38+
};

0 commit comments

Comments
 (0)