diff --git a/frontend/src/components/ScratchItem.module.scss b/frontend/src/components/ScratchItem.module.scss index dd7f89cd0..1a35d62c9 100644 --- a/frontend/src/components/ScratchItem.module.scss +++ b/frontend/src/components/ScratchItem.module.scss @@ -147,3 +147,15 @@ text-overflow: ellipsis; } } + +.red-on-shift { + --warning-color: #ce3a3a; + color: var(--warning-color) !important; + border-color: var(--warning-color) !important; +} + +.red-on-shift:hover { + --warning-hover-color: #f58d8d; + color: var(--warning-hover-color) !important; + border-color: var(--warning-hover-color) !important; +} \ No newline at end of file diff --git a/frontend/src/components/ScratchItem.tsx b/frontend/src/components/ScratchItem.tsx index bf771139f..29026d344 100644 --- a/frontend/src/components/ScratchItem.tsx +++ b/frontend/src/components/ScratchItem.tsx @@ -1,11 +1,11 @@ "use client"; -import type { ReactNode } from "react"; +import { type ReactNode, useState } from "react"; import Image from "next/image"; import Link from "@/components/Link"; -import { RepoForkedIcon } from "@primer/octicons-react"; +import { RepoForkedIcon, TrashIcon } from "@primer/octicons-react"; import clsx from "clsx"; import TimeAgo from "@/components/TimeAgo"; @@ -15,6 +15,7 @@ import { presetUrl, scratchUrl, userAvatarUrl } from "@/lib/api/urls"; import getTranslation from "@/lib/i18n/translate"; import AnonymousFrogAvatar from "./Frog/AnonymousFrog"; +import Button from "./Button"; import PlatformLink from "./PlatformLink"; import { calculateScorePercent, percentToString } from "./ScoreBadge"; import styles from "./ScratchItem.module.scss"; @@ -144,53 +145,120 @@ function ScratchItemRow({ showOwner = true, showPlatform = true, showPresetOrCompiler = true, + showDeleteButton = false, }: { scratch: api.TerseScratch; children?: ReactNode; showOwner?: boolean; showPlatform?: boolean; showPresetOrCompiler?: boolean; + showDeleteButton?: boolean; }) { + const [warnDelete, setWarnDelete] = useState(false); + const [showElement, setShowElement] = useState(true); + const deleteScratch = async (scratch: api.TerseScratch) => { + if ( + !warnDelete && + !confirm( + "Are you sure you want to delete this scratch? This action cannot be undone.", + ) + ) { + return; + } + + try { + await api.delete_(scratchUrl(scratch), {}); + setShowElement(false); // Hide deleted element to avoid performing a page refresh, and allow deleting more scratches + } catch (error) { + alert("An error occurred trying to deleting this scratch."); + throw error; + } + }; + + document.body.addEventListener("keydown", (evt: KeyboardEvent) => { + setWarnDelete(evt.shiftKey); + }); + + document.body.addEventListener("keyup", (evt: KeyboardEvent) => { + setWarnDelete(evt.shiftKey); + }); + return ( -
  • -
    -
    - - - - -
    -
    - - {(children || showOwner) && ( -
    - {children && ( -
    {children}
    + <> + {" "} + {showElement && ( +
  • +
    +
    + + + + +
    +
    + + {(children || showOwner || showDeleteButton) && ( +
    + {children && ( +
    + {children} +
    + )} + {showOwner && ( + + )} + {showDeleteButton && ( + + )} +
    )} - {showOwner && }
    - )} -
    - -
  • + + + )}{" "} + ); } export function ScratchItem({ scratch, children, -}: { scratch: api.TerseScratch; children?: ReactNode }) { +}: { + scratch: api.TerseScratch; + children?: ReactNode; + showDeleteButton?: boolean; +}) { return {children}; } -export function ScratchItemNoOwner({ scratch }: { scratch: api.TerseScratch }) { - return ; +export function ScratchItemNoOwner({ + scratch, + showDeleteButton, +}: { scratch: api.TerseScratch; showDeleteButton?: boolean }) { + return ( + + ); } export function ScratchItemPlatformList({ diff --git a/frontend/src/components/ScratchList.tsx b/frontend/src/components/ScratchList.tsx index e49aee2a0..6d95202c2 100644 --- a/frontend/src/components/ScratchList.tsx +++ b/frontend/src/components/ScratchList.tsx @@ -23,6 +23,7 @@ export interface Props { emptyButtonLabel?: ReactNode; isSortable?: boolean; isPublic?: boolean; + showDeleteButtons?: boolean; } export default function ScratchList({ @@ -33,6 +34,7 @@ export default function ScratchList({ emptyButtonLabel, isSortable, isPublic, + showDeleteButtons, }: Props) { const [sortMode, setSortMode] = useState(SortMode.NEWEST_FIRST); const { results, isLoading, hasNext, loadNext } = @@ -62,7 +64,11 @@ export default function ScratchList({ )} > {results.map((scratch) => ( - + ))} {results.length === 0 && emptyButtonLabel && (
  • diff --git a/frontend/src/components/user/tabs/ScratchesTab.tsx b/frontend/src/components/user/tabs/ScratchesTab.tsx index 012e2543c..7f57d4bdf 100644 --- a/frontend/src/components/user/tabs/ScratchesTab.tsx +++ b/frontend/src/components/user/tabs/ScratchesTab.tsx @@ -1,16 +1,20 @@ import ScratchList from "@/components/ScratchList"; import { ScratchItemNoOwner } from "@/components/ScratchItem"; -import type { User } from "@/lib/api"; +import { type User, useThisUserIsAdmin, useUserIsYou } from "@/lib/api"; import { userUrl } from "@/lib/api/urls"; export default function ScratchesTab({ user }: { user: User }) { + const userIsYou = useUserIsYou(); + const isAdmin = useThisUserIsAdmin(); + return (
    );