more modals replaced

This commit is contained in:
daniel31x13 2023-12-01 14:00:52 -05:00
parent 2c9541734a
commit a3c6d9b42e
13 changed files with 266 additions and 100 deletions

View File

@ -12,23 +12,17 @@ type Props = {
export default function Checkbox({ label, state, className, onClick }: Props) {
return (
<label
className={`cursor-pointer flex items-center gap-2 ${className || ""}`}
className={`label cursor-pointer flex gap-2 justify-start ${
className || ""
}`}
>
<input
type="checkbox"
checked={state}
onChange={onClick}
className="peer sr-only"
className="checkbox checkbox-primary"
/>
<FontAwesomeIcon
icon={faSquareCheck}
className="w-5 h-5 text-primary peer-checked:block hidden"
/>
<FontAwesomeIcon
icon={faSquare}
className="w-5 h-5 text-primary peer-checked:hidden block"
/>
<span className="rounded select-none">{label}</span>
<span className="label-text">{label}</span>
</label>
);
}

View File

@ -12,6 +12,7 @@ import getPublicUserData from "@/lib/client/getPublicUserData";
import useAccountStore from "@/store/account";
import EditCollectionModal from "./Modals/EditCollectionModal";
import EditCollectionSharingModal from "./Modals/EditCollectionSharingModal";
import DeleteCollectionModal from "./Modals/DeleteCollectionModal";
type Props = {
collection: CollectionIncludingMembersAndLinkCount;
@ -62,6 +63,7 @@ export default function CollectionCard({ collection, className }: Props) {
const [editCollectionModal, setEditCollectionModal] = useState(false);
const [editCollectionSharingModal, setEditCollectionSharingModal] =
useState(false);
const [deleteCollectionModal, setDeleteCollectionModal] = useState(false);
return (
<div className="relative">
@ -109,15 +111,7 @@ export default function CollectionCard({ collection, className }: Props) {
tabIndex={0}
onClick={() => {
(document?.activeElement as HTMLElement)?.blur();
collection &&
setModal({
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: collection,
defaultIndex: permissions === true ? 2 : 1,
});
setDeleteCollectionModal(true);
}}
>
{permissions === true ? "Delete Collection" : "Leave Collection"}
@ -132,7 +126,7 @@ export default function CollectionCard({ collection, className }: Props) {
{collectionOwner.id ? (
<ProfilePhoto
src={collectionOwner.image || undefined}
className="w-7 h-7"
dimensionClass="w-7 h-7"
/>
) : undefined}
{collection.members
@ -212,6 +206,12 @@ export default function CollectionCard({ collection, className }: Props) {
modalId={"edit-collection-sharing-modal" + collection.id}
activeCollection={collection}
/>
<DeleteCollectionModal
isOpen={deleteCollectionModal}
onClose={() => setDeleteCollectionModal(false)}
modalId={"delete-collection-modal" + collection.id}
activeCollection={collection}
/>
</div>
);
}

View File

@ -0,0 +1,180 @@
import React, { useEffect, useState } from "react";
import TextInput from "@/components/TextInput";
import useCollectionStore from "@/store/collections";
import toast, { Toaster } from "react-hot-toast";
import {
faFolder,
faRightFromBracket,
faTrashCan,
} from "@fortawesome/free-solid-svg-icons";
import { HexColorPicker } from "react-colorful";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
import { useRouter } from "next/router";
import usePermissions from "@/hooks/usePermissions";
type Props = {
modalId: string;
isOpen: boolean;
onClose: Function;
activeCollection: CollectionIncludingMembersAndLinkCount;
};
export default function DeleteCollectionModal({
modalId,
isOpen,
onClose,
activeCollection,
}: Props) {
const modal = document.getElementById(modalId);
useEffect(() => {
modal?.addEventListener("close", () => {
onClose();
});
return () => {
modal?.addEventListener("close", () => {
onClose();
});
};
}, [isOpen]);
const [collection, setCollection] =
useState<CollectionIncludingMembersAndLinkCount>(activeCollection);
useEffect(() => {
setCollection(activeCollection);
setInputField("");
}, [isOpen]);
const [submitLoader, setSubmitLoader] = useState(false);
const { removeCollection } = useCollectionStore();
const router = useRouter();
const [inputField, setInputField] = useState("");
const permissions = usePermissions(collection.id as number);
const submit = async () => {
if (permissions === true) if (collection.name !== inputField) return null;
if (!submitLoader) {
setSubmitLoader(true);
if (!collection) return null;
setSubmitLoader(true);
const load = toast.loading("Deleting...");
let response;
response = await removeCollection(collection.id as any);
toast.dismiss(load);
if (response.ok) {
toast.success(`Deleted.`);
(document.getElementById(modalId) as any).close();
router.push("/collections");
} else toast.error(response.data as string);
setSubmitLoader(false);
}
};
return (
<dialog
id={modalId}
className="modal backdrop-blur-sm overflow-y-auto"
open={isOpen}
>
<Toaster
position="top-center"
reverseOrder={false}
toastOptions={{
className:
"border border-sky-100 dark:border-neutral-700 dark:bg-neutral-900 dark:text-white",
}}
/>
<div className="modal-box max-h-full overflow-y-visible border border-neutral-content w-11/12 max-w-2xl">
<form method="dialog">
<button className="btn btn-sm outline-none btn-circle btn-ghost absolute right-3 top-3">
</button>
</form>
<p className="text-xl mb-5 font-thin text-red-500">
{permissions === true ? "Delete" : "Leave"} Collection
</p>
<div className="flex flex-col gap-3">
{permissions === true ? (
<>
<div className="flex flex-col gap-3">
<p>
To confirm, type &quot;
<span className="font-bold">{collection.name}</span>
&quot; in the box below:
</p>
<TextInput
value={inputField}
onChange={(e) => setInputField(e.target.value)}
placeholder={`Type "${collection.name}" Here.`}
className="w-3/4 mx-auto"
/>
</div>
<div role="alert" className="alert alert-warning">
<svg
xmlns="http://www.w3.org/2000/svg"
className="stroke-current shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
<span>
<b>
Warning: Deleting this collection will permanently erase all
its contents
</b>
, and it will become inaccessible to everyone, including
members with previous access.
</span>
</div>
</>
) : (
<p>Click the button below to leave the current collection.</p>
)}
<button
disabled={permissions === true && inputField !== collection.name}
className={`ml-auto btn w-fit text-white flex items-center gap-2 duration-100 ${
permissions === true
? inputField === collection.name
? "bg-red-500 hover:bg-red-400 hover:dark:bg-red-600 cursor-pointer"
: "cursor-not-allowed bg-red-300 dark:bg-red-900"
: "bg-red-500 hover:bg-red-400 hover:dark:bg-red-600 cursor-pointer"
}`}
onClick={submit}
>
<FontAwesomeIcon
icon={permissions === true ? faTrashCan : faRightFromBracket}
className="h-5"
/>
{permissions === true ? "Delete" : "Leave"} Collection
</button>
</div>
</div>
<form method="dialog" className="modal-backdrop">
<button>close</button>
</form>
</dialog>
);
}

View File

@ -4,7 +4,6 @@ import useCollectionStore from "@/store/collections";
import toast, { Toaster } from "react-hot-toast";
import { faFolder } from "@fortawesome/free-solid-svg-icons";
import { HexColorPicker } from "react-colorful";
import { Collection } from "@prisma/client";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";

View File

@ -5,11 +5,8 @@ import toast, { Toaster } from "react-hot-toast";
import {
faClose,
faCrown,
faFolder,
faUserPlus,
} from "@fortawesome/free-solid-svg-icons";
import { HexColorPicker } from "react-colorful";
import { Collection } from "@prisma/client";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CollectionIncludingMembersAndLinkCount, Member } from "@/types/global";
import getPublicUserData from "@/lib/client/getPublicUserData";

View File

@ -110,7 +110,6 @@ export default function Navbar() {
<ProfilePhoto
src={account.image ? account.image : undefined}
priority={true}
className=""
/>
</div>
<ul className="dropdown-content z-[1] menu p-1 shadow bg-base-200 border border-neutral-content rounded-xl w-40 mt-1">

View File

@ -8,6 +8,7 @@ type Props = {
className?: string;
priority?: boolean;
name?: string;
dimensionClass?: string;
};
export default function ProfilePhoto({
@ -15,6 +16,7 @@ export default function ProfilePhoto({
className,
priority,
name,
dimensionClass,
}: Props) {
const [image, setImage] = useState("");
@ -28,7 +30,11 @@ export default function ProfilePhoto({
}, [src]);
return !image ? (
<div className={`avatar w-8 h-8 placeholder ${className || ""}`}>
<div
className={`avatar placeholder ${className || ""} ${
dimensionClass || "w-8 h-8 "
}`}
>
<div className="bg-base-100 text-neutral rounded-full w-full h-full ring-2 ring-neutral-content">
{name ? (
<span className="text-2xl capitalize">{name.slice(0, 1)}</span>
@ -41,7 +47,11 @@ export default function ProfilePhoto({
</div>
</div>
) : (
<div className={`avatar w-8 h-8 drop-shadow-md ${className || ""}`}>
<div
className={`avatar drop-shadow-md ${className || ""} ${
dimensionClass || "w-8 h-8 "
}`}
>
<div className="rounded-full w-full h-full ring-2 ring-neutral-content">
<Image
alt=""

View File

@ -112,10 +112,7 @@ export default function Index() {
onClick={() => setEditCollectionSharingModal(true)}
>
{collectionOwner.id ? (
<ProfilePhoto
src={collectionOwner.image || undefined}
className="w-7 h-7"
/>
<ProfilePhoto src={collectionOwner.image || undefined} />
) : undefined}
{activeCollection.members
.sort((a, b) => (a.userId as number) - (b.userId as number))

View File

@ -153,7 +153,7 @@ export default function PublicCollections() {
{collectionOwner.id ? (
<ProfilePhoto
src={collectionOwner.image || undefined}
className="w-7 h-7"
dimensionClass="w-7 h-7"
/>
) : undefined}
{collection.members

View File

@ -162,6 +162,7 @@ export default function Account() {
<p className="mb-2">Display Name</p>
<TextInput
value={user.name || ""}
className="bg-base-200"
onChange={(e) => setUser({ ...user, name: e.target.value })}
/>
</div>
@ -169,6 +170,7 @@ export default function Account() {
<p className="mb-2">Username</p>
<TextInput
value={user.username || ""}
className="bg-base-200"
onChange={(e) => setUser({ ...user, username: e.target.value })}
/>
</div>
@ -184,6 +186,7 @@ export default function Account() {
) : undefined}
<TextInput
value={user.email || ""}
className="bg-base-200"
onChange={(e) => setUser({ ...user, email: e.target.value })}
/>
</div>
@ -196,7 +199,7 @@ export default function Account() {
<ProfilePhoto
priority={true}
src={user.image ? user.image : undefined}
className="h-auto border-none w-28"
dimensionClass="w-28 h-28"
/>
{user.image && (
<div
@ -206,13 +209,13 @@ export default function Account() {
image: "",
})
}
className="absolute top-1 left-1 w-5 h-5 flex items-center justify-center border p-1 border-slate-200 rounded-full bg-base-200 text-center select-none cursor-pointer duration-100 hover:text-red-500"
className="absolute top-1 left-1 btn btn-xs btn-circle btn-neutral btn-outline bg-base-100"
>
<FontAwesomeIcon icon={faClose} className="w-3 h-3" />
</div>
)}
<div className="absolute -bottom-3 left-0 right-0 mx-auto w-fit text-center">
<label className="border border-slate-200 rounded-md bg-base-200 px-2 text-center select-none cursor-pointer duration-100 hover:border-primary">
<label className="btn btn-xs btn-secondary btn-outline bg-base-100">
Browse...
<input
type="file"
@ -240,33 +243,21 @@ export default function Account() {
<div className="flex gap-3 flex-col">
<div>
<p className="mb-2">Import your data from other platforms.</p>
<div
onClick={() => setImportDropdown(true)}
className="w-fit relative"
<details className="dropdown">
<summary
className="flex gap-2 text-sm btn btn-outline btn-secondary btn-xs"
id="import-dropdown"
>
<div
id="import-dropdown"
className="border border-slate-200 rounded-md bg-base-200 px-2 text-center select-none cursor-pointer duration-100 hover:border-primary"
>
Import From
</div>
{importDropdown ? (
<ClickAwayHandler
onClickOutside={(e: Event) => {
const target = e.target as HTMLInputElement;
if (target.id !== "import-dropdown")
setImportDropdown(false);
}}
className={`absolute top-7 left-0 w-52 py-1 shadow-md border border-neutral-content bg-base-200 rounded-md flex flex-col z-20`}
>
<div className="cursor-pointer rounded-md">
</summary>
<ul className="shadow menu dropdown-content z-[1] p-1 bg-base-200 border border-neutral-content rounded-xl mt-1 w-60">
<li>
<label
className="px-2 py-1 rounded-lg"
htmlFor="import-linkwarden-file"
title="JSON File"
className="flex items-center gap-2 py-1 px-2 hover:bg-neutral-content duration-100 cursor-pointer"
>
Linkwarden File...
From Linkwarden
<input
type="file"
name="photo"
@ -278,12 +269,14 @@ export default function Account() {
}
/>
</label>
</li>
<li>
<label
className="px-2 py-1 rounded-lg"
htmlFor="import-html-file"
title="HTML File"
className="flex items-center gap-2 py-1 px-2 hover:bg-neutral-content duration-100 cursor-pointer"
>
Bookmarks HTML file...
From Bookmarks HTML file
<input
type="file"
name="photo"
@ -295,16 +288,15 @@ export default function Account() {
}
/>
</label>
</div>
</ClickAwayHandler>
) : null}
</div>
</li>
</ul>
</details>
</div>
<div>
<p className="mb-2">Download your data instantly.</p>
<Link className="w-fit" href="/api/v1/migration">
<div className="border w-fit border-slate-200 rounded-md bg-base-200 px-2 text-center select-none cursor-pointer duration-100 hover:border-primary">
<div className="btn btn-outline btn-secondary btn-xs">
Export Data
</div>
</Link>
@ -339,7 +331,7 @@ export default function Account() {
visibility to your profile. Separated by comma.
</p>
<textarea
className="w-full resize-none border rounded-md duration-100 bg-base-200 p-2 outline-none border-neutral-content focus:border-sky-300 dark:focus:border-sky-600"
className="w-full resize-none border rounded-md duration-100 bg-base-200 p-2 outline-none border-neutral-content focus:border-primary"
placeholder="Your profile is hidden from everyone right now..."
value={whitelistedUsersTextbox}
onChange={(e) => setWhiteListedUsersTextbox(e.target.value)}

View File

@ -59,7 +59,7 @@ export default function Api() {
<div className="divider my-3"></div>
<div className="flex flex-col gap-3">
<div className="badge bg-orange-500 rounded-md border border-black w-fit px-2 text-black">
<div className="badge badge-warning rounded-md w-fit p-4">
Status: Under Development
</div>

View File

@ -2,13 +2,9 @@ import SettingsLayout from "@/layouts/SettingsLayout";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSun, faMoon } from "@fortawesome/free-solid-svg-icons";
import { useState, useEffect } from "react";
import { faClose } from "@fortawesome/free-solid-svg-icons";
import useAccountStore from "@/store/account";
import { AccountSettings } from "@/types/global";
import { toast } from "react-hot-toast";
import TextInput from "@/components/TextInput";
import { resizeImage } from "@/lib/client/resizeImage";
import ProfilePhoto from "@/components/ProfilePhoto";
import SubmitButton from "@/components/SubmitButton";
import React from "react";
import Checkbox from "@/components/Checkbox";
@ -16,7 +12,7 @@ import LinkPreview from "@/components/LinkPreview";
import useLocalSettingsStore from "@/store/localSettings";
export default function Appearance() {
const { settings, updateSettings } = useLocalSettingsStore();
const { updateSettings } = useLocalSettingsStore();
const submit = async () => {
setSubmitLoader(true);

View File

@ -58,6 +58,7 @@ export default function Password() {
<TextInput
value={newPassword}
className="bg-base-200"
onChange={(e) => setNewPassword1(e.target.value)}
placeholder="••••••••••••••"
type="password"
@ -67,6 +68,7 @@ export default function Password() {
<TextInput
value={newPassword2}
className="bg-base-200"
onChange={(e) => setNewPassword2(e.target.value)}
placeholder="••••••••••••••"
type="password"