more more more

This commit is contained in:
Michael Freno
2026-01-06 11:58:12 -05:00
parent 46f1efcd79
commit 1f661f4f89
5 changed files with 234 additions and 176 deletions

View File

@@ -1,7 +1,8 @@
import { createSignal, Show } from "solid-js"; import { createSignal, Show } from "solid-js";
import type { CommentDeletionPromptProps, DeletionType } from "~/types/comment"; import type { CommentDeletionPromptProps, DeletionType } from "~/types/comment";
import UserDefaultImage from "~/components/icons/UserDefaultImage"; import UserDefaultImage from "~/components/icons/UserDefaultImage";
import Xmark from "~/components/icons/Xmark"; import Modal from "~/components/ui/Modal";
import Button from "~/components/ui/Button";
export default function CommentDeletionPrompt( export default function CommentDeletionPrompt(
props: CommentDeletionPromptProps props: CommentDeletionPromptProps
@@ -44,103 +45,90 @@ export default function CommentDeletionPrompt(
normalDeleteChecked() || adminDeleteChecked() || fullDeleteChecked(); normalDeleteChecked() || adminDeleteChecked() || fullDeleteChecked();
return ( return (
<div class="flex justify-center"> <Modal
<div class="fixed top-48 z-100 h-fit"> open={props.isOpen}
<div onClose={props.onClose}
id="delete_prompt" title="Comment Deletion"
class="fade-in bg-red rounded-md px-8 py-4 shadow-lg brightness-110" class="bg-red brightness-110"
> >
<button class="fixed right-4" onClick={() => props.onClose()}> <div class="bg-surface0 mx-auto w-3/4 rounded px-6 py-4">
<Xmark strokeWidth={0.5} color="white" height={50} width={50} /> <div class="flex overflow-x-auto overflow-y-hidden select-text">
</button> {/* Comment body will be passed as prop */}
<div class="py-4 text-center text-3xl tracking-wide"> </div>
Comment Deletion <div class="my-2 flex pl-2">
</div> <Show
<div class="bg-surface0 mx-auto w-3/4 rounded px-6 py-4"> when={props.commenterImage}
<div class="flex overflow-x-auto overflow-y-hidden select-text"> fallback={
{/* Comment body will be passed as prop */} <UserDefaultImage strokeWidth={1} height={24} width={24} />
</div> }
<div class="my-2 flex pl-2"> >
<Show <img
when={props.commenterImage} src={props.commenterImage}
fallback={ height={24}
<UserDefaultImage strokeWidth={1} height={24} width={24} /> width={24}
} alt="user-image"
> class="h-6 w-6 rounded-full object-cover object-center"
<img />
src={props.commenterImage}
height={24}
width={24}
alt="user-image"
class="h-6 w-6 rounded-full object-cover object-center"
/>
</Show>
<div class="px-1">
{props.commenterDisplayName ||
props.commenterEmail ||
"[removed]"}
</div>
</div>
</div>
<div class="flex w-full justify-center">
<div class="flex pt-4">
<input
type="checkbox"
class="my-auto"
checked={normalDeleteChecked()}
onChange={handleNormalDeleteCheckbox}
/>
<div class="my-auto px-2 text-sm font-normal">
{props.privilegeLevel === "admin"
? "Confirm User Delete?"
: "Confirm Delete?"}
</div>
</div>
</div>
<Show when={props.privilegeLevel === "admin"}>
<div class="flex w-full justify-center">
<div class="flex pt-4">
<input
type="checkbox"
class="my-auto"
checked={adminDeleteChecked()}
onChange={handleAdminDeleteCheckbox}
/>
<div class="my-auto px-2 text-sm font-normal">
Confirm Admin Delete?
</div>
</div>
</div>
<div class="flex w-full justify-center">
<div class="flex pt-4">
<input
type="checkbox"
class="my-auto"
checked={fullDeleteChecked()}
onChange={handleFullDeleteCheckbox}
/>
<div class="my-auto px-2 text-sm font-normal">
Confirm Full Delete (removal from database)?
</div>
</div>
</div>
</Show> </Show>
<div class="flex w-full justify-center pt-2"> <div class="px-1">
<button {props.commenterDisplayName || props.commenterEmail || "[removed]"}
type="button"
onClick={deletionWrapper}
disabled={props.commentDeletionLoading || !isDeleteEnabled()}
class={`${
props.commentDeletionLoading || !isDeleteEnabled()
? "bg-surface2 opacity-50"
: "border-red bg-red hover:brightness-125"
} rounded border px-4 py-2 text-base shadow-md transition-all duration-300 ease-in-out active:scale-90`}
>
Delete
</button>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="flex w-full justify-center">
<div class="flex pt-4">
<input
type="checkbox"
class="my-auto"
checked={normalDeleteChecked()}
onChange={handleNormalDeleteCheckbox}
/>
<div class="my-auto px-2 text-sm font-normal">
{props.privilegeLevel === "admin"
? "Confirm User Delete?"
: "Confirm Delete?"}
</div>
</div>
</div>
<Show when={props.privilegeLevel === "admin"}>
<div class="flex w-full justify-center">
<div class="flex pt-4">
<input
type="checkbox"
class="my-auto"
checked={adminDeleteChecked()}
onChange={handleAdminDeleteCheckbox}
/>
<div class="my-auto px-2 text-sm font-normal">
Confirm Admin Delete?
</div>
</div>
</div>
<div class="flex w-full justify-center">
<div class="flex pt-4">
<input
type="checkbox"
class="my-auto"
checked={fullDeleteChecked()}
onChange={handleFullDeleteCheckbox}
/>
<div class="my-auto px-2 text-sm font-normal">
Confirm Full Delete (removal from database)?
</div>
</div>
</div>
</Show>
<div class="flex w-full justify-center pt-2">
<Button
type="button"
onClick={deletionWrapper}
loading={props.commentDeletionLoading}
disabled={!isDeleteEnabled()}
variant="danger"
>
Delete
</Button>
</div>
</Modal>
); );
} }

View File

@@ -634,42 +634,36 @@ export default function CommentSectionWrapper(
commentReaction={commentReaction} commentReaction={commentReaction}
/> />
<Show when={showingDeletionPrompt()}> <CommentDeletionPrompt
<div ref={deletePromptRef}> isOpen={showingDeletionPrompt()}
<CommentDeletionPrompt commentID={commentIDForModification()}
commentID={commentIDForModification()} commenterID={commenterForModification()}
commenterID={commenterForModification()} commenterImage={commenterImageForModification()}
commenterImage={commenterImageForModification()} commenterEmail={commenterEmailForModification()}
commenterEmail={commenterEmailForModification()} commenterDisplayName={commenterDisplayNameForModification()}
commenterDisplayName={commenterDisplayNameForModification()} privilegeLevel={props.privilegeLevel}
privilegeLevel={props.privilegeLevel} commentDeletionLoading={commentDeletionLoading()}
commentDeletionLoading={commentDeletionLoading()} deleteComment={deleteComment}
deleteComment={deleteComment} onClose={() => {
onClose={() => { setShowingDeletionPrompt(false);
setShowingDeletionPrompt(false); clearModificationPrompt();
clearModificationPrompt(); }}
}} />
/>
</div>
</Show>
<Show when={showingCommentEdit()}> <EditCommentModal
<div ref={modificationPromptRef}> isOpen={showingCommentEdit()}
<EditCommentModal commentID={commentIDForModification()}
commentID={commentIDForModification()} commentBody={commentBodyForModification()}
commentBody={commentBodyForModification()} commenterImage={commenterImageForModification()}
commenterImage={commenterImageForModification()} commenterEmail={commenterEmailForModification()}
commenterEmail={commenterEmailForModification()} commenterDisplayName={commenterDisplayNameForModification()}
commenterDisplayName={commenterDisplayNameForModification()} editCommentLoading={editCommentLoading()}
editCommentLoading={editCommentLoading()} editComment={editComment}
editComment={editComment} onClose={() => {
onClose={() => { setShowingCommentEdit(false);
setShowingCommentEdit(false); clearModificationPrompt();
clearModificationPrompt(); }}
}} />
/>
</div>
</Show>
</> </>
); );
} }

View File

@@ -1,6 +1,6 @@
import { createSignal, Show } from "solid-js"; import { createSignal, Show } from "solid-js";
import type { EditCommentModalProps } from "~/types/comment"; import type { EditCommentModalProps } from "~/types/comment";
import Xmark from "~/components/icons/Xmark"; import Modal from "~/components/ui/Modal";
import Button from "~/components/ui/Button"; import Button from "~/components/ui/Button";
export default function EditCommentModal(props: EditCommentModalProps) { export default function EditCommentModal(props: EditCommentModalProps) {
@@ -22,51 +22,38 @@ export default function EditCommentModal(props: EditCommentModalProps) {
}; };
return ( return (
<div class="z-100 flex justify-center"> <Modal
<div class="fixed top-48 h-fit w-11/12 sm:w-4/5 md:w-2/3"> open={props.isOpen}
<div onClose={props.onClose}
id="edit_prompt" title="Edit Comment"
class="fade-in bg-surface1 z-50 rounded-md px-8 py-4 shadow-lg" class="bg-surface1 w-11/12 max-w-none sm:w-4/5 md:w-2/3"
> >
<button class="absolute right-4" onClick={() => props.onClose()}> <form onSubmit={editCommentWrapper}>
<Xmark <div class="textarea-group home">
strokeWidth={0.5} <textarea
color="var(--color-text)" required
height={50} ref={bodyRef}
width={50} placeholder=" "
/> value={props.commentBody}
</button> class="underlinedInput text-blue w-full bg-transparent"
<div class="text-text py-4 text-center text-3xl tracking-wide"> rows={4}
Edit Comment />
</div> <span class="bar" />
<form onSubmit={editCommentWrapper}> <label class="underlinedInputLabel">Edit Comment</label>
<div class="textarea-group home">
<textarea
required
ref={bodyRef}
placeholder=" "
value={props.commentBody}
class="underlinedInput text-blue w-full bg-transparent"
rows={4}
/>
<span class="bar" />
<label class="underlinedInputLabel">Edit Comment</label>
</div>
<div class="flex justify-end pt-2">
<Button
type="submit"
loading={props.editCommentLoading}
variant="primary"
>
Submit
</Button>
</div>
</form>
<Show when={showNoChange()}>
<div class="text-red text-center italic">No change detected</div>
</Show>
</div> </div>
</div> <div class="flex justify-end pt-2">
</div> <Button
type="submit"
loading={props.editCommentLoading}
variant="primary"
>
Submit
</Button>
</div>
</form>
<Show when={showNoChange()}>
<div class="text-red text-center italic">No change detected</div>
</Show>
</Modal>
); );
} }

View File

@@ -0,0 +1,87 @@
import { Show, onMount, onCleanup, type JSX } from "solid-js";
import { Portal } from "solid-js/web";
import Xmark from "~/components/icons/Xmark";
export interface ModalProps {
/** Controls modal visibility */
open: boolean;
/** Callback when modal should close */
onClose: () => void;
/** Modal title (optional) */
title?: string | JSX.Element;
/** Modal content */
children: JSX.Element;
/** Action buttons (optional) */
actions?: JSX.Element;
/** Additional CSS classes for modal container */
class?: string;
}
export default function Modal(props: ModalProps) {
const handleBackdropClick = (e: MouseEvent) => {
if (e.target === e.currentTarget) {
props.onClose();
}
};
const handleEscapeKey = (e: KeyboardEvent) => {
if (e.key === "Escape") {
props.onClose();
}
};
onMount(() => {
if (props.open) {
document.addEventListener("keydown", handleEscapeKey);
}
});
onCleanup(() => {
document.removeEventListener("keydown", handleEscapeKey);
});
return (
<Show when={props.open}>
<Portal>
<div
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
onClick={handleBackdropClick}
role="dialog"
aria-modal="true"
>
<div
class={`bg-base fade-in relative mx-4 max-w-md rounded-md px-8 py-4 shadow-lg ${props.class || ""}`}
onClick={(e) => e.stopPropagation()}
>
<button
class="absolute top-4 right-4"
onClick={props.onClose}
aria-label="Close modal"
>
<Xmark
strokeWidth={0.5}
color="var(--color-text)"
height={50}
width={50}
/>
</button>
<Show when={props.title}>
<div class="py-4 text-center text-3xl tracking-wide">
{props.title}
</div>
</Show>
<div class="modal-content">{props.children}</div>
<Show when={props.actions}>
<div class="modal-actions">{props.actions}</div>
</Show>
</div>
</div>
</Portal>
</Show>
);
}
export { Modal };

View File

@@ -176,6 +176,7 @@ export interface ReactionBarProps {
} }
export interface CommentDeletionPromptProps { export interface CommentDeletionPromptProps {
isOpen: boolean;
privilegeLevel: PrivilegeLevel; privilegeLevel: PrivilegeLevel;
commentID: number; commentID: number;
commenterID: string; commenterID: string;
@@ -192,6 +193,7 @@ export interface CommentDeletionPromptProps {
} }
export interface EditCommentModalProps { export interface EditCommentModalProps {
isOpen: boolean;
commentID: number; commentID: number;
commentBody: string; commentBody: string;
editComment: (body: string, comment_id: number) => Promise<void>; editComment: (body: string, comment_id: number) => Promise<void>;