improved DX
This commit is contained in:
parent
a36769c521
commit
9e4502c015
|
@ -192,24 +192,24 @@ export default function CollectionCard({ collection, className }: Props) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<EditCollectionModal
|
{editCollectionModal ? (
|
||||||
isOpen={editCollectionModal}
|
<EditCollectionModal
|
||||||
onClose={() => setEditCollectionModal(false)}
|
onClose={() => setEditCollectionModal(false)}
|
||||||
modalId={"edit-collection-modal" + collection.id}
|
activeCollection={collection}
|
||||||
activeCollection={collection}
|
/>
|
||||||
/>
|
) : undefined}
|
||||||
<EditCollectionSharingModal
|
{editCollectionSharingModal ? (
|
||||||
isOpen={editCollectionSharingModal}
|
<EditCollectionSharingModal
|
||||||
onClose={() => setEditCollectionSharingModal(false)}
|
onClose={() => setEditCollectionSharingModal(false)}
|
||||||
modalId={"edit-collection-sharing-modal" + collection.id}
|
activeCollection={collection}
|
||||||
activeCollection={collection}
|
/>
|
||||||
/>
|
) : undefined}
|
||||||
<DeleteCollectionModal
|
{deleteCollectionModal ? (
|
||||||
isOpen={deleteCollectionModal}
|
<DeleteCollectionModal
|
||||||
onClose={() => setDeleteCollectionModal(false)}
|
onClose={() => setDeleteCollectionModal(false)}
|
||||||
modalId={"delete-collection-modal" + collection.id}
|
activeCollection={collection}
|
||||||
activeCollection={collection}
|
/>
|
||||||
/>
|
) : undefined}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,7 +118,7 @@ export default function LinkCard({ link, count, className }: Props) {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const [newLinkModal, setNewLinkModal] = useState(false);
|
const [editLinkModal, setEditLinkModal] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -168,7 +168,7 @@ export default function LinkCard({ link, count, className }: Props) {
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
(document?.activeElement as HTMLElement)?.blur();
|
(document?.activeElement as HTMLElement)?.blur();
|
||||||
setNewLinkModal(true);
|
setEditLinkModal(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
|
@ -291,12 +291,12 @@ export default function LinkCard({ link, count, className }: Props) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<EditLinkModal
|
{editLinkModal ? (
|
||||||
isOpen={newLinkModal}
|
<EditLinkModal
|
||||||
onClose={() => setNewLinkModal(false)}
|
onClose={() => setEditLinkModal(false)}
|
||||||
modalId={"edit-link-modal" + link.id}
|
activeLink={link}
|
||||||
activeLink={link}
|
/>
|
||||||
/>
|
) : undefined}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Tab } from "@headlessui/react";
|
||||||
import CollectionInfo from "./CollectionInfo";
|
import CollectionInfo from "./CollectionInfo";
|
||||||
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
|
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
|
||||||
import TeamManagement from "./TeamManagement";
|
import TeamManagement from "./TeamManagement";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import DeleteCollection from "./DeleteCollection";
|
import DeleteCollection from "./DeleteCollection";
|
||||||
import ViewTeam from "./ViewTeam";
|
import ViewTeam from "./ViewTeam";
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { MouseEventHandler, ReactNode } from "react";
|
import { MouseEventHandler, ReactNode, useEffect } from "react";
|
||||||
import ClickAwayHandler from "@/components/ClickAwayHandler";
|
import ClickAwayHandler from "@/components/ClickAwayHandler";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
|
import { faClose } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
toggleModal: Function;
|
toggleModal: Function;
|
||||||
|
@ -14,17 +14,14 @@ export default function Modal({ toggleModal, className, children }: Props) {
|
||||||
<div className="overflow-y-auto py-2 fixed top-0 bottom-0 right-0 left-0 bg-black bg-opacity-10 backdrop-blur-sm flex justify-center items-center fade-in z-30">
|
<div className="overflow-y-auto py-2 fixed top-0 bottom-0 right-0 left-0 bg-black bg-opacity-10 backdrop-blur-sm flex justify-center items-center fade-in z-30">
|
||||||
<ClickAwayHandler
|
<ClickAwayHandler
|
||||||
onClickOutside={toggleModal}
|
onClickOutside={toggleModal}
|
||||||
className={`m-auto ${className || ""}`}
|
className={`m-auto w-11/12 max-w-2xl ${className || ""}`}
|
||||||
>
|
>
|
||||||
<div className="slide-up relative border-neutral-content rounded-2xl border-solid border shadow-lg p-5 bg-base-100">
|
<div className="slide-up m-auto relative border-neutral-content rounded-2xl border-solid border shadow-2xl p-5 bg-base-100">
|
||||||
<div
|
<div
|
||||||
onClick={toggleModal as MouseEventHandler<HTMLDivElement>}
|
onClick={toggleModal as MouseEventHandler<HTMLDivElement>}
|
||||||
className="absolute top-5 left-5 inline-flex rounded-md cursor-pointer hover:bg-slate-200 hover:dark:bg-neutral-700 duration-100 z-20 p-2"
|
className="absolute top-3 right-3 btn btn-sm outline-none btn-circle btn-ghost"
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon icon={faClose} className="w-4 h-4 text-neutral" />
|
||||||
icon={faChevronLeft}
|
|
||||||
className="w-4 h-4 text-neutral"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,52 +1,32 @@
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import TextInput from "@/components/TextInput";
|
import TextInput from "@/components/TextInput";
|
||||||
import useCollectionStore from "@/store/collections";
|
import useCollectionStore from "@/store/collections";
|
||||||
import toast, { Toaster } from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import {
|
import {
|
||||||
faFolder,
|
|
||||||
faRightFromBracket,
|
faRightFromBracket,
|
||||||
faTrashCan,
|
faTrashCan,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { HexColorPicker } from "react-colorful";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
|
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import usePermissions from "@/hooks/usePermissions";
|
import usePermissions from "@/hooks/usePermissions";
|
||||||
|
import Modal from "../Modal";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
modalId: string;
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: Function;
|
onClose: Function;
|
||||||
activeCollection: CollectionIncludingMembersAndLinkCount;
|
activeCollection: CollectionIncludingMembersAndLinkCount;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function DeleteCollectionModal({
|
export default function DeleteCollectionModal({
|
||||||
modalId,
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
onClose,
|
||||||
activeCollection,
|
activeCollection,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const modal = document.getElementById(modalId);
|
|
||||||
|
|
||||||
const [collection, setCollection] =
|
const [collection, setCollection] =
|
||||||
useState<CollectionIncludingMembersAndLinkCount>(activeCollection);
|
useState<CollectionIncludingMembersAndLinkCount>(activeCollection);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
modal?.scrollTo(0, 0);
|
|
||||||
|
|
||||||
setCollection(activeCollection);
|
setCollection(activeCollection);
|
||||||
setInputField("");
|
}, []);
|
||||||
|
|
||||||
modal?.addEventListener("close", () => {
|
|
||||||
onClose();
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
modal?.addEventListener("close", () => {
|
|
||||||
onClose();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
const [submitLoader, setSubmitLoader] = useState(false);
|
const [submitLoader, setSubmitLoader] = useState(false);
|
||||||
const { removeCollection } = useCollectionStore();
|
const { removeCollection } = useCollectionStore();
|
||||||
|
@ -74,7 +54,7 @@ export default function DeleteCollectionModal({
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
toast.success(`Deleted.`);
|
toast.success(`Deleted.`);
|
||||||
(document.getElementById(modalId) as any).close();
|
onClose();
|
||||||
router.push("/collections");
|
router.push("/collections");
|
||||||
} else toast.error(response.data as string);
|
} else toast.error(response.data as string);
|
||||||
|
|
||||||
|
@ -83,98 +63,75 @@ export default function DeleteCollectionModal({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dialog
|
<Modal toggleModal={onClose}>
|
||||||
id={modalId}
|
<p className="text-xl mb-5 font-thin text-red-500">
|
||||||
className="modal backdrop-blur-sm overflow-y-auto p-5"
|
{permissions === true ? "Delete" : "Leave"} Collection
|
||||||
open={isOpen}
|
</p>
|
||||||
>
|
|
||||||
<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">
|
<div className="flex flex-col gap-3">
|
||||||
{permissions === true ? "Delete" : "Leave"} Collection
|
{permissions === true ? (
|
||||||
</p>
|
<>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<p>
|
||||||
|
To confirm, type "
|
||||||
|
<span className="font-bold">{collection.name}</span>
|
||||||
|
" in the box below:
|
||||||
|
</p>
|
||||||
|
|
||||||
<div className="flex flex-col gap-3">
|
<TextInput
|
||||||
{permissions === true ? (
|
value={inputField}
|
||||||
<>
|
onChange={(e) => setInputField(e.target.value)}
|
||||||
<div className="flex flex-col gap-3">
|
placeholder={`Type "${collection.name}" Here.`}
|
||||||
<p>
|
className="w-3/4 mx-auto"
|
||||||
To confirm, type "
|
/>
|
||||||
<span className="font-bold">{collection.name}</span>
|
</div>
|
||||||
" in the box below:
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<TextInput
|
<div role="alert" className="alert alert-warning">
|
||||||
value={inputField}
|
<svg
|
||||||
onChange={(e) => setInputField(e.target.value)}
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
placeholder={`Type "${collection.name}" Here.`}
|
className="stroke-current shrink-0 h-6 w-6"
|
||||||
className="w-3/4 mx-auto"
|
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"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
||||||
|
)}
|
||||||
|
|
||||||
<div role="alert" className="alert alert-warning">
|
<button
|
||||||
<svg
|
disabled={permissions === true && inputField !== collection.name}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
className={`ml-auto btn w-fit text-white flex items-center gap-2 duration-100 ${
|
||||||
className="stroke-current shrink-0 h-6 w-6"
|
permissions === true
|
||||||
fill="none"
|
? inputField === collection.name
|
||||||
viewBox="0 0 24 24"
|
? "bg-red-500 hover:bg-red-400 hover:dark:bg-red-600 cursor-pointer"
|
||||||
>
|
: "cursor-not-allowed bg-red-300 dark:bg-red-900"
|
||||||
<path
|
: "bg-red-500 hover:bg-red-400 hover:dark:bg-red-600 cursor-pointer"
|
||||||
strokeLinecap="round"
|
}`}
|
||||||
strokeLinejoin="round"
|
onClick={submit}
|
||||||
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"
|
<FontAwesomeIcon
|
||||||
/>
|
icon={permissions === true ? faTrashCan : faRightFromBracket}
|
||||||
</svg>
|
className="h-5"
|
||||||
<span>
|
/>
|
||||||
<b>
|
{permissions === true ? "Delete" : "Leave"} Collection
|
||||||
Warning: Deleting this collection will permanently erase all
|
</button>
|
||||||
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>
|
</div>
|
||||||
<form method="dialog" className="modal-backdrop">
|
</Modal>
|
||||||
<button>close</button>
|
|
||||||
</form>
|
|
||||||
</dialog>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,37 +6,17 @@ import { faFolder } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { HexColorPicker } from "react-colorful";
|
import { HexColorPicker } from "react-colorful";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
|
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
|
||||||
|
import Modal from "../Modal";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
modalId: string;
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: Function;
|
onClose: Function;
|
||||||
activeCollection: CollectionIncludingMembersAndLinkCount;
|
activeCollection: CollectionIncludingMembersAndLinkCount;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function EditCollectionModal({
|
export default function EditCollectionModal({
|
||||||
modalId,
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
onClose,
|
||||||
activeCollection,
|
activeCollection,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const modal = document.getElementById(modalId);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
modal?.scrollTo(0, 0);
|
|
||||||
setCollection(activeCollection);
|
|
||||||
|
|
||||||
modal?.addEventListener("close", () => {
|
|
||||||
onClose();
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
modal?.addEventListener("close", () => {
|
|
||||||
onClose();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
const [collection, setCollection] =
|
const [collection, setCollection] =
|
||||||
useState<CollectionIncludingMembersAndLinkCount>(activeCollection);
|
useState<CollectionIncludingMembersAndLinkCount>(activeCollection);
|
||||||
|
|
||||||
|
@ -60,7 +40,7 @@ export default function EditCollectionModal({
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
toast.success(`Updated!`);
|
toast.success(`Updated!`);
|
||||||
(document.getElementById(modalId) as any).close();
|
onClose();
|
||||||
} else toast.error(response.data as string);
|
} else toast.error(response.data as string);
|
||||||
|
|
||||||
setSubmitLoader(false);
|
setSubmitLoader(false);
|
||||||
|
@ -68,95 +48,70 @@ export default function EditCollectionModal({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dialog
|
<Modal toggleModal={onClose}>
|
||||||
id={modalId}
|
<p className="text-xl mb-5 font-thin">Edit Collection Info</p>
|
||||||
className="modal backdrop-blur-sm overflow-y-auto p-5"
|
|
||||||
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">Edit Collection Info</p>
|
<div className="flex flex-col gap-3">
|
||||||
|
<div className="flex flex-col sm:flex-row gap-3">
|
||||||
<div className="flex flex-col gap-3">
|
<div className="w-full">
|
||||||
<div className="flex flex-col sm:flex-row gap-3">
|
<p className="mb-2">Name</p>
|
||||||
<div className="w-full">
|
<div className="flex flex-col gap-3">
|
||||||
<p className="mb-2">Name</p>
|
<TextInput
|
||||||
<div className="flex flex-col gap-3">
|
className="bg-base-200"
|
||||||
<TextInput
|
value={collection.name}
|
||||||
className="bg-base-200"
|
placeholder="e.g. Example Collection"
|
||||||
value={collection.name}
|
onChange={(e) =>
|
||||||
placeholder="e.g. Example Collection"
|
setCollection({ ...collection, name: e.target.value })
|
||||||
onChange={(e) =>
|
}
|
||||||
setCollection({ ...collection, name: e.target.value })
|
/>
|
||||||
}
|
<div>
|
||||||
/>
|
<p className="w-full mb-2">Color</p>
|
||||||
<div>
|
<div className="color-picker flex justify-between">
|
||||||
<p className="w-full mb-2">Color</p>
|
<div className="flex flex-col gap-2 items-center w-32">
|
||||||
<div className="color-picker flex justify-between">
|
<div style={{ color: collection.color }}>
|
||||||
<div className="flex flex-col gap-2 items-center w-32">
|
<FontAwesomeIcon
|
||||||
<div style={{ color: collection.color }}>
|
icon={faFolder}
|
||||||
<FontAwesomeIcon
|
className="w-12 h-12 drop-shadow"
|
||||||
icon={faFolder}
|
/>
|
||||||
className="w-12 h-12 drop-shadow"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="btn btn-ghost btn-xs"
|
|
||||||
onClick={() =>
|
|
||||||
setCollection({ ...collection, color: "#0ea5e9" })
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Reset
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<HexColorPicker
|
<div
|
||||||
color={collection.color}
|
className="btn btn-ghost btn-xs"
|
||||||
onChange={(e) =>
|
onClick={() =>
|
||||||
setCollection({ ...collection, color: e })
|
setCollection({ ...collection, color: "#0ea5e9" })
|
||||||
}
|
}
|
||||||
/>
|
>
|
||||||
|
Reset
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<HexColorPicker
|
||||||
|
color={collection.color}
|
||||||
|
onChange={(e) => setCollection({ ...collection, color: e })}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full">
|
|
||||||
<p className="mb-2">Description</p>
|
|
||||||
<textarea
|
|
||||||
className="w-full h-[13rem] resize-none border rounded-md duration-100 bg-base-200 p-2 outline-none border-neutral-content focus:border-primary"
|
|
||||||
placeholder="The purpose of this Collection..."
|
|
||||||
value={collection.description}
|
|
||||||
onChange={(e) =>
|
|
||||||
setCollection({
|
|
||||||
...collection,
|
|
||||||
description: e.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button className="btn btn-accent w-fit ml-auto" onClick={submit}>
|
<div className="w-full">
|
||||||
Save
|
<p className="mb-2">Description</p>
|
||||||
</button>
|
<textarea
|
||||||
|
className="w-full h-[13rem] resize-none border rounded-md duration-100 bg-base-200 p-2 outline-none border-neutral-content focus:border-primary"
|
||||||
|
placeholder="The purpose of this Collection..."
|
||||||
|
value={collection.description}
|
||||||
|
onChange={(e) =>
|
||||||
|
setCollection({
|
||||||
|
...collection,
|
||||||
|
description: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button className="btn btn-accent w-fit ml-auto" onClick={submit}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form method="dialog" className="modal-backdrop">
|
</Modal>
|
||||||
<button>close</button>
|
|
||||||
</form>
|
|
||||||
</dialog>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,22 +14,17 @@ import useAccountStore from "@/store/account";
|
||||||
import usePermissions from "@/hooks/usePermissions";
|
import usePermissions from "@/hooks/usePermissions";
|
||||||
import ProfilePhoto from "../ProfilePhoto";
|
import ProfilePhoto from "../ProfilePhoto";
|
||||||
import addMemberToCollection from "@/lib/client/addMemberToCollection";
|
import addMemberToCollection from "@/lib/client/addMemberToCollection";
|
||||||
|
import Modal from "../Modal";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
modalId: string;
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: Function;
|
onClose: Function;
|
||||||
activeCollection: CollectionIncludingMembersAndLinkCount;
|
activeCollection: CollectionIncludingMembersAndLinkCount;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function EditCollectionSharingModal({
|
export default function EditCollectionSharingModal({
|
||||||
modalId,
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
onClose,
|
||||||
activeCollection,
|
activeCollection,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const modal = document.getElementById(modalId);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchOwner = async () => {
|
const fetchOwner = async () => {
|
||||||
const owner = await getPublicUserData(collection.ownerId as number);
|
const owner = await getPublicUserData(collection.ownerId as number);
|
||||||
|
@ -38,19 +33,8 @@ export default function EditCollectionSharingModal({
|
||||||
|
|
||||||
fetchOwner();
|
fetchOwner();
|
||||||
|
|
||||||
modal?.scrollTo(0, 0);
|
|
||||||
setCollection(activeCollection);
|
setCollection(activeCollection);
|
||||||
|
}, []);
|
||||||
modal?.addEventListener("close", () => {
|
|
||||||
onClose();
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
modal?.addEventListener("close", () => {
|
|
||||||
onClose();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
const [collection, setCollection] =
|
const [collection, setCollection] =
|
||||||
useState<CollectionIncludingMembersAndLinkCount>(activeCollection);
|
useState<CollectionIncludingMembersAndLinkCount>(activeCollection);
|
||||||
|
@ -75,7 +59,7 @@ export default function EditCollectionSharingModal({
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
toast.success(`Updated!`);
|
toast.success(`Updated!`);
|
||||||
(document.getElementById(modalId) as any).close();
|
onClose();
|
||||||
} else toast.error(response.data as string);
|
} else toast.error(response.data as string);
|
||||||
|
|
||||||
setSubmitLoader(false);
|
setSubmitLoader(false);
|
||||||
|
@ -110,384 +94,353 @@ export default function EditCollectionSharingModal({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dialog
|
<Modal toggleModal={onClose}>
|
||||||
id={modalId}
|
<p className="text-xl font-thin mb-5">
|
||||||
className="modal backdrop-blur-sm overflow-y-auto p-5"
|
{permissions === true ? "Share and Collaborate" : "Team"}
|
||||||
open={isOpen}
|
</p>
|
||||||
>
|
|
||||||
<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 font-thin mb-5">
|
<div className="flex flex-col gap-3">
|
||||||
{permissions === true ? "Share and Collaborate" : "Team"}
|
{permissions === true && (
|
||||||
</p>
|
<div>
|
||||||
|
<p>Make Public</p>
|
||||||
|
|
||||||
<div className="flex flex-col gap-3">
|
<label className="label cursor-pointer justify-start gap-2">
|
||||||
{permissions === true && (
|
<input
|
||||||
<div>
|
type="checkbox"
|
||||||
<p>Make Public</p>
|
checked={collection.isPublic}
|
||||||
|
onChange={() =>
|
||||||
|
setCollection({
|
||||||
|
...collection,
|
||||||
|
isPublic: !collection.isPublic,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="checkbox checkbox-primary"
|
||||||
|
/>
|
||||||
|
<span className="label-text">Make this a public collection</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
<label className="label cursor-pointer justify-start gap-2">
|
<p className="text-neutral text-sm">
|
||||||
<input
|
This will let <b>Anyone</b> to view this collection and it's
|
||||||
type="checkbox"
|
users.
|
||||||
checked={collection.isPublic}
|
</p>
|
||||||
onChange={() =>
|
</div>
|
||||||
setCollection({
|
)}
|
||||||
...collection,
|
|
||||||
isPublic: !collection.isPublic,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
className="checkbox checkbox-primary"
|
|
||||||
/>
|
|
||||||
<span className="label-text">
|
|
||||||
Make this a public collection
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<p className="text-neutral text-sm">
|
{collection.isPublic ? (
|
||||||
This will let <b>Anyone</b> to view this collection and it's
|
<div className={permissions === true ? "pl-5" : ""}>
|
||||||
users.
|
<p className="mb-2">Sharable Link (Click to copy)</p>
|
||||||
</p>
|
<div
|
||||||
|
onClick={() => {
|
||||||
|
try {
|
||||||
|
navigator.clipboard
|
||||||
|
.writeText(publicCollectionURL)
|
||||||
|
.then(() => toast.success("Copied!"));
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="w-full hide-scrollbar overflow-x-auto whitespace-nowrap rounded-md p-2 bg-base-200 border-neutral-content border-solid border outline-none hover:border-primary dark:hover:border-primary duration-100 cursor-text"
|
||||||
|
>
|
||||||
|
{publicCollectionURL}
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{permissions === true && <div className="divider my-3"></div>}
|
||||||
|
|
||||||
|
{permissions === true && (
|
||||||
|
<>
|
||||||
|
<p>Member Management</p>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<TextInput
|
||||||
|
value={memberUsername || ""}
|
||||||
|
className="bg-base-200"
|
||||||
|
placeholder="Username (without the '@')"
|
||||||
|
onChange={(e) => setMemberUsername(e.target.value)}
|
||||||
|
onKeyDown={(e) =>
|
||||||
|
e.key === "Enter" &&
|
||||||
|
addMemberToCollection(
|
||||||
|
account.username as string,
|
||||||
|
memberUsername || "",
|
||||||
|
collection,
|
||||||
|
setMemberState
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
{collection.isPublic ? (
|
|
||||||
<div className={permissions === true ? "pl-5" : ""}>
|
|
||||||
<p className="mb-2">Sharable Link (Click to copy)</p>
|
|
||||||
<div
|
<div
|
||||||
onClick={() => {
|
onClick={() =>
|
||||||
try {
|
addMemberToCollection(
|
||||||
navigator.clipboard
|
account.username as string,
|
||||||
.writeText(publicCollectionURL)
|
memberUsername || "",
|
||||||
.then(() => toast.success("Copied!"));
|
collection,
|
||||||
} catch (err) {
|
setMemberState
|
||||||
console.log(err);
|
)
|
||||||
}
|
}
|
||||||
}}
|
className="btn btn-primary text-white btn-square"
|
||||||
className="w-full hide-scrollbar overflow-x-auto whitespace-nowrap rounded-md p-2 bg-base-200 border-neutral-content border-solid border outline-none hover:border-primary dark:hover:border-primary duration-100 cursor-text"
|
|
||||||
>
|
>
|
||||||
{publicCollectionURL}
|
<FontAwesomeIcon icon={faUserPlus} className="w-5 h-5" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{permissions === true && <div className="divider my-3"></div>}
|
{collection?.members[0]?.user && (
|
||||||
|
<>
|
||||||
|
{permissions === true ? (
|
||||||
|
<p className="text-center text-neutral text-xs sm:text-sm">
|
||||||
|
(All Members have <b>Read</b> access to this collection.)
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
<p>
|
||||||
|
Here are all the members who are collaborating on this
|
||||||
|
collection.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
{permissions === true && (
|
<div className="flex flex-col gap-3 rounded-md">
|
||||||
<>
|
<div
|
||||||
<p>Member Management</p>
|
className="relative border px-2 rounded-xl border-neutral-content bg-base-200 flex min-h-[7rem] sm:min-h-[5rem] gap-2 justify-between"
|
||||||
|
title={`@${collectionOwner.username} is the owner of this collection.`}
|
||||||
<div className="flex items-center gap-2">
|
>
|
||||||
<TextInput
|
<div className="flex items-center gap-2 w-full">
|
||||||
value={memberUsername || ""}
|
<ProfilePhoto
|
||||||
className="bg-base-200"
|
src={
|
||||||
placeholder="Username (without the '@')"
|
collectionOwner.image ? collectionOwner.image : undefined
|
||||||
onChange={(e) => setMemberUsername(e.target.value)}
|
}
|
||||||
onKeyDown={(e) =>
|
/>
|
||||||
e.key === "Enter" &&
|
<div className="w-full">
|
||||||
addMemberToCollection(
|
<div className="flex items-center gap-1 w-full justify-between">
|
||||||
account.username as string,
|
<p className="text-sm font-bold">
|
||||||
memberUsername || "",
|
{collectionOwner.name}
|
||||||
collection,
|
|
||||||
setMemberState
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
|
||||||
onClick={() =>
|
|
||||||
addMemberToCollection(
|
|
||||||
account.username as string,
|
|
||||||
memberUsername || "",
|
|
||||||
collection,
|
|
||||||
setMemberState
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className="btn btn-primary text-white btn-square"
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faUserPlus} className="w-5 h-5" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{collection?.members[0]?.user && (
|
|
||||||
<>
|
|
||||||
{permissions === true ? (
|
|
||||||
<p className="text-center text-neutral text-xs sm:text-sm">
|
|
||||||
(All Members have <b>Read</b> access to this collection.)
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<p>
|
|
||||||
Here are all the members who are collaborating on this
|
|
||||||
collection.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-3 rounded-md">
|
|
||||||
<div
|
|
||||||
className="relative border px-2 rounded-xl border-neutral-content bg-base-200 flex min-h-[7rem] sm:min-h-[5rem] gap-2 justify-between"
|
|
||||||
title={`@${collectionOwner.username} is the owner of this collection.`}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2 w-full">
|
|
||||||
<ProfilePhoto
|
|
||||||
src={
|
|
||||||
collectionOwner.image
|
|
||||||
? collectionOwner.image
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div className="w-full">
|
|
||||||
<div className="flex items-center gap-1 w-full justify-between">
|
|
||||||
<p className="text-sm font-bold">
|
|
||||||
{collectionOwner.name}
|
|
||||||
</p>
|
|
||||||
<div className="flex text-xs gap-1 items-center">
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={faCrown}
|
|
||||||
className="w-3 h-3 text-yellow-500"
|
|
||||||
/>
|
|
||||||
Admin
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p className="text-neutral">
|
|
||||||
@{collectionOwner.username}
|
|
||||||
</p>
|
</p>
|
||||||
|
<div className="flex text-xs gap-1 items-center">
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faCrown}
|
||||||
|
className="w-3 h-3 text-yellow-500"
|
||||||
|
/>
|
||||||
|
Admin
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-neutral">@{collectionOwner.username}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{collection.members
|
{collection.members
|
||||||
.sort((a, b) => (a.userId as number) - (b.userId as number))
|
.sort((a, b) => (a.userId as number) - (b.userId as number))
|
||||||
.map((e, i) => {
|
.map((e, i) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className="relative border p-2 rounded-xl border-neutral-content bg-base-200 flex flex-col sm:flex-row sm:items-center gap-2 justify-between"
|
className="relative border p-2 rounded-xl border-neutral-content bg-base-200 flex flex-col sm:flex-row sm:items-center gap-2 justify-between"
|
||||||
>
|
>
|
||||||
{permissions === true && (
|
{permissions === true && (
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faClose}
|
icon={faClose}
|
||||||
className="absolute right-2 top-2 text-neutral h-4 hover:text-red-500 dark:hover:text-red-500 duration-100 cursor-pointer"
|
className="absolute right-2 top-2 text-neutral h-4 hover:text-red-500 dark:hover:text-red-500 duration-100 cursor-pointer"
|
||||||
title="Remove Member"
|
title="Remove Member"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const updatedMembers = collection.members.filter(
|
const updatedMembers = collection.members.filter(
|
||||||
(member) => {
|
(member) => {
|
||||||
return (
|
return member.user.username !== e.user.username;
|
||||||
member.user.username !== e.user.username
|
}
|
||||||
);
|
);
|
||||||
}
|
setCollection({
|
||||||
);
|
...collection,
|
||||||
setCollection({
|
members: updatedMembers,
|
||||||
...collection,
|
});
|
||||||
members: updatedMembers,
|
}}
|
||||||
});
|
/>
|
||||||
}}
|
)}
|
||||||
/>
|
<div className="flex items-center gap-2">
|
||||||
)}
|
<ProfilePhoto
|
||||||
<div className="flex items-center gap-2">
|
src={e.user.image ? e.user.image : undefined}
|
||||||
<ProfilePhoto
|
/>
|
||||||
src={e.user.image ? e.user.image : undefined}
|
<div>
|
||||||
/>
|
<p className="text-sm font-bold">{e.user.name}</p>
|
||||||
<div>
|
<p className="text-neutral">@{e.user.username}</p>
|
||||||
<p className="text-sm font-bold">{e.user.name}</p>
|
|
||||||
<p className="text-neutral">@{e.user.username}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex sm:block items-center justify-between gap-5 min-w-[10rem]">
|
|
||||||
<div>
|
|
||||||
<p
|
|
||||||
className={`font-bold text-sm ${
|
|
||||||
permissions === true ? "" : "mb-2"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Permissions
|
|
||||||
</p>
|
|
||||||
{permissions === true && (
|
|
||||||
<p className="text-xs text-neutral mb-2">
|
|
||||||
(Click to toggle.)
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{permissions !== true &&
|
|
||||||
!e.canCreate &&
|
|
||||||
!e.canUpdate &&
|
|
||||||
!e.canDelete ? (
|
|
||||||
<p className="text-sm text-neutral">
|
|
||||||
Has no permissions.
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<div>
|
|
||||||
<label
|
|
||||||
className={
|
|
||||||
permissions === true
|
|
||||||
? "cursor-pointer mr-1"
|
|
||||||
: "mr-1"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="canCreate"
|
|
||||||
className="peer sr-only"
|
|
||||||
checked={e.canCreate}
|
|
||||||
onChange={() => {
|
|
||||||
if (permissions === true) {
|
|
||||||
const updatedMembers =
|
|
||||||
collection.members.map((member) => {
|
|
||||||
if (
|
|
||||||
member.user.username ===
|
|
||||||
e.user.username
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
...member,
|
|
||||||
canCreate: !e.canCreate,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return member;
|
|
||||||
});
|
|
||||||
setCollection({
|
|
||||||
...collection,
|
|
||||||
members: updatedMembers,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className={`peer-checked:bg-primary text-sm ${
|
|
||||||
permissions === true
|
|
||||||
? "hover:bg-neutral-content duration-100"
|
|
||||||
: ""
|
|
||||||
} rounded p-1 select-none`}
|
|
||||||
>
|
|
||||||
Create
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label
|
|
||||||
className={
|
|
||||||
permissions === true
|
|
||||||
? "cursor-pointer mr-1"
|
|
||||||
: "mr-1"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="canUpdate"
|
|
||||||
className="peer sr-only"
|
|
||||||
checked={e.canUpdate}
|
|
||||||
onChange={() => {
|
|
||||||
if (permissions === true) {
|
|
||||||
const updatedMembers =
|
|
||||||
collection.members.map((member) => {
|
|
||||||
if (
|
|
||||||
member.user.username ===
|
|
||||||
e.user.username
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
...member,
|
|
||||||
canUpdate: !e.canUpdate,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return member;
|
|
||||||
});
|
|
||||||
setCollection({
|
|
||||||
...collection,
|
|
||||||
members: updatedMembers,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className={`peer-checked:bg-primary text-sm ${
|
|
||||||
permissions === true
|
|
||||||
? "hover:bg-neutral-content duration-100"
|
|
||||||
: ""
|
|
||||||
} rounded p-1 select-none`}
|
|
||||||
>
|
|
||||||
Update
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label
|
|
||||||
className={
|
|
||||||
permissions === true
|
|
||||||
? "cursor-pointer mr-1"
|
|
||||||
: "mr-1"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="canDelete"
|
|
||||||
className="peer sr-only"
|
|
||||||
checked={e.canDelete}
|
|
||||||
onChange={() => {
|
|
||||||
if (permissions === true) {
|
|
||||||
const updatedMembers =
|
|
||||||
collection.members.map((member) => {
|
|
||||||
if (
|
|
||||||
member.user.username ===
|
|
||||||
e.user.username
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
...member,
|
|
||||||
canDelete: !e.canDelete,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return member;
|
|
||||||
});
|
|
||||||
setCollection({
|
|
||||||
...collection,
|
|
||||||
members: updatedMembers,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
className={`peer-checked:bg-primary text-sm ${
|
|
||||||
permissions === true
|
|
||||||
? "hover:bg-neutral-content duration-100"
|
|
||||||
: ""
|
|
||||||
} rounded p-1 select-none`}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
<div className="flex sm:block items-center justify-between gap-5 min-w-[10rem]">
|
||||||
})}
|
<div>
|
||||||
</div>
|
<p
|
||||||
</>
|
className={`font-bold text-sm ${
|
||||||
)}
|
permissions === true ? "" : "mb-2"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Permissions
|
||||||
|
</p>
|
||||||
|
{permissions === true && (
|
||||||
|
<p className="text-xs text-neutral mb-2">
|
||||||
|
(Click to toggle.)
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{permissions === true && (
|
{permissions !== true &&
|
||||||
<button
|
!e.canCreate &&
|
||||||
className="btn btn-accent w-fit ml-auto mt-3"
|
!e.canUpdate &&
|
||||||
onClick={submit}
|
!e.canDelete ? (
|
||||||
>
|
<p className="text-sm text-neutral">
|
||||||
Save
|
Has no permissions.
|
||||||
</button>
|
</p>
|
||||||
)}
|
) : (
|
||||||
</div>
|
<div>
|
||||||
|
<label
|
||||||
|
className={
|
||||||
|
permissions === true
|
||||||
|
? "cursor-pointer mr-1"
|
||||||
|
: "mr-1"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="canCreate"
|
||||||
|
className="peer sr-only"
|
||||||
|
checked={e.canCreate}
|
||||||
|
onChange={() => {
|
||||||
|
if (permissions === true) {
|
||||||
|
const updatedMembers =
|
||||||
|
collection.members.map((member) => {
|
||||||
|
if (
|
||||||
|
member.user.username ===
|
||||||
|
e.user.username
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...member,
|
||||||
|
canCreate: !e.canCreate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return member;
|
||||||
|
});
|
||||||
|
setCollection({
|
||||||
|
...collection,
|
||||||
|
members: updatedMembers,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={`peer-checked:bg-primary text-sm ${
|
||||||
|
permissions === true
|
||||||
|
? "hover:bg-neutral-content duration-100"
|
||||||
|
: ""
|
||||||
|
} rounded p-1 select-none`}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label
|
||||||
|
className={
|
||||||
|
permissions === true
|
||||||
|
? "cursor-pointer mr-1"
|
||||||
|
: "mr-1"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="canUpdate"
|
||||||
|
className="peer sr-only"
|
||||||
|
checked={e.canUpdate}
|
||||||
|
onChange={() => {
|
||||||
|
if (permissions === true) {
|
||||||
|
const updatedMembers =
|
||||||
|
collection.members.map((member) => {
|
||||||
|
if (
|
||||||
|
member.user.username ===
|
||||||
|
e.user.username
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...member,
|
||||||
|
canUpdate: !e.canUpdate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return member;
|
||||||
|
});
|
||||||
|
setCollection({
|
||||||
|
...collection,
|
||||||
|
members: updatedMembers,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={`peer-checked:bg-primary text-sm ${
|
||||||
|
permissions === true
|
||||||
|
? "hover:bg-neutral-content duration-100"
|
||||||
|
: ""
|
||||||
|
} rounded p-1 select-none`}
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label
|
||||||
|
className={
|
||||||
|
permissions === true
|
||||||
|
? "cursor-pointer mr-1"
|
||||||
|
: "mr-1"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="canDelete"
|
||||||
|
className="peer sr-only"
|
||||||
|
checked={e.canDelete}
|
||||||
|
onChange={() => {
|
||||||
|
if (permissions === true) {
|
||||||
|
const updatedMembers =
|
||||||
|
collection.members.map((member) => {
|
||||||
|
if (
|
||||||
|
member.user.username ===
|
||||||
|
e.user.username
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...member,
|
||||||
|
canDelete: !e.canDelete,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return member;
|
||||||
|
});
|
||||||
|
setCollection({
|
||||||
|
...collection,
|
||||||
|
members: updatedMembers,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
className={`peer-checked:bg-primary text-sm ${
|
||||||
|
permissions === true
|
||||||
|
? "hover:bg-neutral-content duration-100"
|
||||||
|
: ""
|
||||||
|
} rounded p-1 select-none`}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{permissions === true && (
|
||||||
|
<button
|
||||||
|
className="btn btn-accent w-fit ml-auto mt-3"
|
||||||
|
onClick={submit}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<form method="dialog" className="modal-backdrop">
|
</Modal>
|
||||||
<button>close</button>
|
|
||||||
</form>
|
|
||||||
</dialog>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,22 +10,14 @@ import toast from "react-hot-toast";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faLink } from "@fortawesome/free-solid-svg-icons";
|
import { faLink } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import Modal from "../Modal";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
modalId: string;
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: Function;
|
onClose: Function;
|
||||||
activeLink: LinkIncludingShortenedCollectionAndTags;
|
activeLink: LinkIncludingShortenedCollectionAndTags;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function EditLinkModal({
|
export default function EditLinkModal({ onClose, activeLink }: Props) {
|
||||||
modalId,
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
activeLink,
|
|
||||||
}: Props) {
|
|
||||||
const modal = document.getElementById(modalId);
|
|
||||||
|
|
||||||
const [link, setLink] =
|
const [link, setLink] =
|
||||||
useState<LinkIncludingShortenedCollectionAndTags>(activeLink);
|
useState<LinkIncludingShortenedCollectionAndTags>(activeLink);
|
||||||
|
|
||||||
|
@ -58,20 +50,8 @@ export default function EditLinkModal({
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
modal?.scrollTo(0, 0);
|
|
||||||
|
|
||||||
setLink(activeLink);
|
setLink(activeLink);
|
||||||
|
}, []);
|
||||||
modal?.addEventListener("close", () => {
|
|
||||||
onClose();
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
modal?.addEventListener("close", () => {
|
|
||||||
onClose();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
if (!submitLoader) {
|
if (!submitLoader) {
|
||||||
|
@ -87,7 +67,7 @@ export default function EditLinkModal({
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
toast.success(`Updated!`);
|
toast.success(`Updated!`);
|
||||||
(document.getElementById(modalId) as any).close();
|
onClose();
|
||||||
} else toast.error(response.data as string);
|
} else toast.error(response.data as string);
|
||||||
|
|
||||||
setSubmitLoader(false);
|
setSubmitLoader(false);
|
||||||
|
@ -97,111 +77,88 @@ export default function EditLinkModal({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dialog
|
<Modal toggleModal={onClose}>
|
||||||
id={modalId}
|
<p className="text-xl mb-5 font-thin">Edit Link</p>
|
||||||
className="modal backdrop-blur-sm overflow-y-auto p-5"
|
|
||||||
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">Edit Link</p>
|
<Link
|
||||||
|
href={link.url}
|
||||||
|
className="truncate text-neutral flex gap-2 mb-5 w-fit max-w-full"
|
||||||
|
title={link.url}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faLink}
|
||||||
|
className="mt-1 w-5 h-5 min-w-[1.25rem]"
|
||||||
|
/>
|
||||||
|
<p>{shortendURL}</p>
|
||||||
|
</Link>
|
||||||
|
|
||||||
<Link
|
<div className="w-full">
|
||||||
href={link.url}
|
<p className="mb-2">Name</p>
|
||||||
className="truncate text-neutral flex gap-2 mb-5 w-fit max-w-full"
|
<TextInput
|
||||||
title={link.url}
|
value={link.name}
|
||||||
target="_blank"
|
onChange={(e) => setLink({ ...link, name: e.target.value })}
|
||||||
>
|
placeholder="e.g. Example Link"
|
||||||
<FontAwesomeIcon
|
className="bg-base-200"
|
||||||
icon={faLink}
|
/>
|
||||||
className="mt-1 w-5 h-5 min-w-[1.25rem]"
|
</div>
|
||||||
/>
|
|
||||||
<p>{shortendURL}</p>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="mt-5">
|
||||||
<p className="mb-2">Name</p>
|
{/* <hr className="mb-3 border border-neutral-content" /> */}
|
||||||
<TextInput
|
<div className="grid sm:grid-cols-2 gap-3">
|
||||||
value={link.name}
|
<div>
|
||||||
onChange={(e) => setLink({ ...link, name: e.target.value })}
|
<p className="mb-2">Collection</p>
|
||||||
placeholder="e.g. Example Link"
|
{link.collection.name ? (
|
||||||
className="bg-base-200"
|
<CollectionSelection
|
||||||
/>
|
onChange={setCollection}
|
||||||
</div>
|
// defaultValue={{
|
||||||
|
// label: link.collection.name,
|
||||||
<div className="mt-5">
|
// value: link.collection.id,
|
||||||
{/* <hr className="mb-3 border border-neutral-content" /> */}
|
// }}
|
||||||
<div className="grid sm:grid-cols-2 gap-3">
|
defaultValue={
|
||||||
<div>
|
link.collection.id
|
||||||
<p className="mb-2">Collection</p>
|
? {
|
||||||
{link.collection.name ? (
|
value: link.collection.id,
|
||||||
<CollectionSelection
|
label: link.collection.name,
|
||||||
onChange={setCollection}
|
}
|
||||||
// defaultValue={{
|
: {
|
||||||
// label: link.collection.name,
|
value: null as unknown as number,
|
||||||
// value: link.collection.id,
|
label: "Unorganized",
|
||||||
// }}
|
}
|
||||||
defaultValue={
|
|
||||||
link.collection.id
|
|
||||||
? {
|
|
||||||
value: link.collection.id,
|
|
||||||
label: link.collection.name,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
value: null as unknown as number,
|
|
||||||
label: "Unorganized",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p className="mb-2">Tags</p>
|
|
||||||
<TagSelection
|
|
||||||
onChange={setTags}
|
|
||||||
defaultValue={link.tags.map((e) => {
|
|
||||||
return { label: e.name, value: e.id };
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="sm:col-span-2">
|
|
||||||
<p className="mb-2">Description</p>
|
|
||||||
<textarea
|
|
||||||
value={unescapeString(link.description) as string}
|
|
||||||
onChange={(e) =>
|
|
||||||
setLink({ ...link, description: e.target.value })
|
|
||||||
}
|
}
|
||||||
placeholder="Will be auto generated if nothing is provided."
|
|
||||||
className="resize-none w-full rounded-md p-2 border-neutral-content bg-base-200 focus:border-sky-300 dark:focus:border-sky-600 border-solid border outline-none duration-100"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
) : null}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="mb-2">Tags</p>
|
||||||
|
<TagSelection
|
||||||
|
onChange={setTags}
|
||||||
|
defaultValue={link.tags.map((e) => {
|
||||||
|
return { label: e.name, value: e.id };
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="sm:col-span-2">
|
||||||
|
<p className="mb-2">Description</p>
|
||||||
|
<textarea
|
||||||
|
value={unescapeString(link.description) as string}
|
||||||
|
onChange={(e) =>
|
||||||
|
setLink({ ...link, description: e.target.value })
|
||||||
|
}
|
||||||
|
placeholder="Will be auto generated if nothing is provided."
|
||||||
|
className="resize-none w-full rounded-md p-2 border-neutral-content bg-base-200 focus:border-sky-300 dark:focus:border-sky-600 border-solid border outline-none duration-100"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end items-center mt-5">
|
|
||||||
<button className="btn btn-accent" onClick={submit}>
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<form method="dialog" className="modal-backdrop">
|
|
||||||
<button>close</button>
|
<div className="flex justify-end items-center mt-5">
|
||||||
</form>
|
<button className="btn btn-accent" onClick={submit}>
|
||||||
</dialog>
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,20 +6,13 @@ import { faFolder } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { HexColorPicker } from "react-colorful";
|
import { HexColorPicker } from "react-colorful";
|
||||||
import { Collection } from "@prisma/client";
|
import { Collection } from "@prisma/client";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import Modal from "../Modal";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
modalId: string;
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: Function;
|
onClose: Function;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function NewCollectionModal({
|
export default function NewCollectionModal({ onClose }: Props) {
|
||||||
modalId,
|
|
||||||
isOpen,
|
|
||||||
onClose,
|
|
||||||
}: Props) {
|
|
||||||
const modal = document.getElementById(modalId);
|
|
||||||
|
|
||||||
const initial = {
|
const initial = {
|
||||||
name: "",
|
name: "",
|
||||||
description: "",
|
description: "",
|
||||||
|
@ -29,20 +22,8 @@ export default function NewCollectionModal({
|
||||||
const [collection, setCollection] = useState<Partial<Collection>>(initial);
|
const [collection, setCollection] = useState<Partial<Collection>>(initial);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
modal?.scrollTo(0, 0);
|
|
||||||
|
|
||||||
modal?.addEventListener("close", () => {
|
|
||||||
onClose();
|
|
||||||
});
|
|
||||||
|
|
||||||
setCollection(initial);
|
setCollection(initial);
|
||||||
|
}, []);
|
||||||
return () => {
|
|
||||||
modal?.addEventListener("close", () => {
|
|
||||||
onClose();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
const [submitLoader, setSubmitLoader] = useState(false);
|
const [submitLoader, setSubmitLoader] = useState(false);
|
||||||
const { addCollection } = useCollectionStore();
|
const { addCollection } = useCollectionStore();
|
||||||
|
@ -64,7 +45,7 @@ export default function NewCollectionModal({
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
toast.success("Created!");
|
toast.success("Created!");
|
||||||
(document.getElementById(modalId) as any).close();
|
onClose();
|
||||||
} else toast.error(response.data as string);
|
} else toast.error(response.data as string);
|
||||||
|
|
||||||
setSubmitLoader(false);
|
setSubmitLoader(false);
|
||||||
|
@ -72,95 +53,70 @@ export default function NewCollectionModal({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dialog
|
<Modal toggleModal={onClose}>
|
||||||
id={modalId}
|
<p className="text-xl mb-5 font-thin">Create a New Collection</p>
|
||||||
className="modal backdrop-blur-sm overflow-y-auto p-5"
|
|
||||||
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">Create a New Collection</p>
|
<div className="flex flex-col gap-3">
|
||||||
|
<div className="flex flex-col sm:flex-row gap-3">
|
||||||
<div className="flex flex-col gap-3">
|
<div className="w-full">
|
||||||
<div className="flex flex-col sm:flex-row gap-3">
|
<p className="mb-2">Name</p>
|
||||||
<div className="w-full">
|
<div className="flex flex-col gap-3">
|
||||||
<p className="mb-2">Name</p>
|
<TextInput
|
||||||
<div className="flex flex-col gap-3">
|
className="bg-base-200"
|
||||||
<TextInput
|
value={collection.name}
|
||||||
className="bg-base-200"
|
placeholder="e.g. Example Collection"
|
||||||
value={collection.name}
|
onChange={(e) =>
|
||||||
placeholder="e.g. Example Collection"
|
setCollection({ ...collection, name: e.target.value })
|
||||||
onChange={(e) =>
|
}
|
||||||
setCollection({ ...collection, name: e.target.value })
|
/>
|
||||||
}
|
<div>
|
||||||
/>
|
<p className="w-full mb-2">Color</p>
|
||||||
<div>
|
<div className="color-picker flex justify-between">
|
||||||
<p className="w-full mb-2">Color</p>
|
<div className="flex flex-col gap-2 items-center w-32">
|
||||||
<div className="color-picker flex justify-between">
|
<div style={{ color: collection.color }}>
|
||||||
<div className="flex flex-col gap-2 items-center w-32">
|
<FontAwesomeIcon
|
||||||
<div style={{ color: collection.color }}>
|
icon={faFolder}
|
||||||
<FontAwesomeIcon
|
className="w-12 h-12 drop-shadow"
|
||||||
icon={faFolder}
|
/>
|
||||||
className="w-12 h-12 drop-shadow"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="btn btn-ghost btn-xs"
|
|
||||||
onClick={() =>
|
|
||||||
setCollection({ ...collection, color: "#0ea5e9" })
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Reset
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<HexColorPicker
|
<div
|
||||||
color={collection.color}
|
className="btn btn-ghost btn-xs"
|
||||||
onChange={(e) =>
|
onClick={() =>
|
||||||
setCollection({ ...collection, color: e })
|
setCollection({ ...collection, color: "#0ea5e9" })
|
||||||
}
|
}
|
||||||
/>
|
>
|
||||||
|
Reset
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<HexColorPicker
|
||||||
|
color={collection.color}
|
||||||
|
onChange={(e) => setCollection({ ...collection, color: e })}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full">
|
|
||||||
<p className="mb-2">Description</p>
|
|
||||||
<textarea
|
|
||||||
className="w-full h-[13rem] resize-none border rounded-md duration-100 bg-base-200 p-2 outline-none border-neutral-content focus:border-primary"
|
|
||||||
placeholder="The purpose of this Collection..."
|
|
||||||
value={collection.description}
|
|
||||||
onChange={(e) =>
|
|
||||||
setCollection({
|
|
||||||
...collection,
|
|
||||||
description: e.target.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button className="btn btn-accent w-fit ml-auto" onClick={submit}>
|
<div className="w-full">
|
||||||
Create Collection
|
<p className="mb-2">Description</p>
|
||||||
</button>
|
<textarea
|
||||||
|
className="w-full h-[13rem] resize-none border rounded-md duration-100 bg-base-200 p-2 outline-none border-neutral-content focus:border-primary"
|
||||||
|
placeholder="The purpose of this Collection..."
|
||||||
|
value={collection.description}
|
||||||
|
onChange={(e) =>
|
||||||
|
setCollection({
|
||||||
|
...collection,
|
||||||
|
description: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button className="btn btn-accent w-fit ml-auto" onClick={submit}>
|
||||||
|
Create Collection
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form method="dialog" className="modal-backdrop">
|
</Modal>
|
||||||
<button>close</button>
|
|
||||||
</form>
|
|
||||||
</dialog>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,16 +10,13 @@ import { LinkIncludingShortenedCollectionAndTags } from "@/types/global";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import Modal from "../Modal";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
modalId: string;
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: Function;
|
onClose: Function;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function NewLinkModal({ modalId, isOpen, onClose }: Props) {
|
export default function NewLinkModal({ onClose }: Props) {
|
||||||
const modal = document.getElementById(modalId);
|
|
||||||
|
|
||||||
const { data } = useSession();
|
const { data } = useSession();
|
||||||
|
|
||||||
const initial = {
|
const initial = {
|
||||||
|
@ -71,7 +68,6 @@ export default function NewLinkModal({ modalId, isOpen, onClose }: Props) {
|
||||||
setResetCollectionSelection(Date.now().toString());
|
setResetCollectionSelection(Date.now().toString());
|
||||||
console.log(link);
|
console.log(link);
|
||||||
|
|
||||||
modal?.scrollTo(0, 0);
|
|
||||||
setOptionsExpanded(false);
|
setOptionsExpanded(false);
|
||||||
if (router.query.id) {
|
if (router.query.id) {
|
||||||
const currentCollection = collections.find(
|
const currentCollection = collections.find(
|
||||||
|
@ -99,17 +95,7 @@ export default function NewLinkModal({ modalId, isOpen, onClose }: Props) {
|
||||||
ownerId: data?.user.id as number,
|
ownerId: data?.user.id as number,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}, []);
|
||||||
modal?.addEventListener("close", () => {
|
|
||||||
onClose();
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
modal?.addEventListener("close", () => {
|
|
||||||
onClose();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
if (!submitLoader) {
|
if (!submitLoader) {
|
||||||
|
@ -125,7 +111,7 @@ export default function NewLinkModal({ modalId, isOpen, onClose }: Props) {
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
toast.success(`Created!`);
|
toast.success(`Created!`);
|
||||||
(document?.getElementById(modalId) as any)?.close();
|
onClose();
|
||||||
} else toast.error(response.data as string);
|
} else toast.error(response.data as string);
|
||||||
|
|
||||||
setSubmitLoader(false);
|
setSubmitLoader(false);
|
||||||
|
@ -135,107 +121,84 @@ export default function NewLinkModal({ modalId, isOpen, onClose }: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dialog
|
<Modal toggleModal={onClose}>
|
||||||
id={modalId}
|
<p className="text-xl mb-5 font-thin">Create a New Link</p>
|
||||||
className="modal backdrop-blur-sm overflow-y-auto p-5"
|
<div className="grid grid-flow-row-dense sm:grid-cols-5 gap-3">
|
||||||
open={isOpen}
|
<div className="sm:col-span-3 col-span-5">
|
||||||
>
|
<p className="mb-2">Link</p>
|
||||||
<Toaster
|
<TextInput
|
||||||
position="top-center"
|
value={link.url}
|
||||||
reverseOrder={false}
|
onChange={(e) => setLink({ ...link, url: e.target.value })}
|
||||||
toastOptions={{
|
placeholder="e.g. http://example.com/"
|
||||||
className:
|
className="bg-base-200"
|
||||||
"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">Create a New Link</p>
|
|
||||||
<div className="grid grid-flow-row-dense sm:grid-cols-5 gap-3">
|
|
||||||
<div className="sm:col-span-3 col-span-5">
|
|
||||||
<p className="mb-2">Link</p>
|
|
||||||
<TextInput
|
|
||||||
value={link.url}
|
|
||||||
onChange={(e) => setLink({ ...link, url: e.target.value })}
|
|
||||||
placeholder="e.g. http://example.com/"
|
|
||||||
className="bg-base-200"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="sm:col-span-2 col-span-5">
|
|
||||||
<p className="mb-2">Collection</p>
|
|
||||||
{link.collection.name ? (
|
|
||||||
<CollectionSelection
|
|
||||||
onChange={setCollection}
|
|
||||||
defaultValue={{
|
|
||||||
label: link.collection.name,
|
|
||||||
value: link.collection.id,
|
|
||||||
}}
|
|
||||||
id={resetCollectionSelection}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="sm:col-span-2 col-span-5">
|
||||||
{optionsExpanded ? (
|
<p className="mb-2">Collection</p>
|
||||||
<div className="mt-5">
|
{link.collection.name ? (
|
||||||
{/* <hr className="mb-3 border border-neutral-content" /> */}
|
<CollectionSelection
|
||||||
<div className="grid sm:grid-cols-2 gap-3">
|
onChange={setCollection}
|
||||||
<div>
|
defaultValue={{
|
||||||
<p className="mb-2">Name</p>
|
label: link.collection.name,
|
||||||
<TextInput
|
value: link.collection.id,
|
||||||
value={link.name}
|
}}
|
||||||
onChange={(e) => setLink({ ...link, name: e.target.value })}
|
id={resetCollectionSelection}
|
||||||
placeholder="e.g. Example Link"
|
/>
|
||||||
className="bg-base-200"
|
) : null}
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p className="mb-2">Tags</p>
|
|
||||||
<TagSelection
|
|
||||||
onChange={setTags}
|
|
||||||
defaultValue={link.tags.map((e) => {
|
|
||||||
return { label: e.name, value: e.id };
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="sm:col-span-2">
|
|
||||||
<p className="mb-2">Description</p>
|
|
||||||
<textarea
|
|
||||||
value={unescapeString(link.description) as string}
|
|
||||||
onChange={(e) =>
|
|
||||||
setLink({ ...link, description: e.target.value })
|
|
||||||
}
|
|
||||||
placeholder="Will be auto generated if nothing is provided."
|
|
||||||
className="resize-none w-full rounded-md p-2 border-neutral-content bg-base-200 focus:border-sky-300 dark:focus:border-sky-600 border-solid border outline-none duration-100"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : undefined}
|
|
||||||
|
|
||||||
<div className="flex justify-between items-center mt-5">
|
|
||||||
<div
|
|
||||||
onClick={() => setOptionsExpanded(!optionsExpanded)}
|
|
||||||
className={`rounded-md cursor-pointer btn btn-sm btn-ghost duration-100 flex items-center px-2 w-fit text-sm`}
|
|
||||||
>
|
|
||||||
<p>{optionsExpanded ? "Hide" : "More"} Options</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button className="btn btn-accent" onClick={submit}>
|
|
||||||
Create Link
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<form method="dialog" className="modal-backdrop">
|
|
||||||
<button>close</button>
|
{optionsExpanded ? (
|
||||||
</form>
|
<div className="mt-5">
|
||||||
</dialog>
|
{/* <hr className="mb-3 border border-neutral-content" /> */}
|
||||||
|
<div className="grid sm:grid-cols-2 gap-3">
|
||||||
|
<div>
|
||||||
|
<p className="mb-2">Name</p>
|
||||||
|
<TextInput
|
||||||
|
value={link.name}
|
||||||
|
onChange={(e) => setLink({ ...link, name: e.target.value })}
|
||||||
|
placeholder="e.g. Example Link"
|
||||||
|
className="bg-base-200"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="mb-2">Tags</p>
|
||||||
|
<TagSelection
|
||||||
|
onChange={setTags}
|
||||||
|
defaultValue={link.tags.map((e) => {
|
||||||
|
return { label: e.name, value: e.id };
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="sm:col-span-2">
|
||||||
|
<p className="mb-2">Description</p>
|
||||||
|
<textarea
|
||||||
|
value={unescapeString(link.description) as string}
|
||||||
|
onChange={(e) =>
|
||||||
|
setLink({ ...link, description: e.target.value })
|
||||||
|
}
|
||||||
|
placeholder="Will be auto generated if nothing is provided."
|
||||||
|
className="resize-none w-full rounded-md p-2 border-neutral-content bg-base-200 focus:border-sky-300 dark:focus:border-sky-600 border-solid border outline-none duration-100"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : undefined}
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center mt-5">
|
||||||
|
<div
|
||||||
|
onClick={() => setOptionsExpanded(!optionsExpanded)}
|
||||||
|
className={`rounded-md cursor-pointer btn btn-sm btn-ghost duration-100 flex items-center px-2 w-fit text-sm`}
|
||||||
|
>
|
||||||
|
<p>{optionsExpanded ? "Hide" : "More"} Options</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button className="btn btn-accent" onClick={submit}>
|
||||||
|
Create Link
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -162,16 +162,12 @@ export default function Navbar() {
|
||||||
</ClickAwayHandler>
|
</ClickAwayHandler>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<NewLinkModal
|
{newLinkModal ? (
|
||||||
isOpen={newLinkModal}
|
<NewLinkModal onClose={() => setNewLinkModal(false)} />
|
||||||
onClose={() => setNewLinkModal(false)}
|
) : undefined}
|
||||||
modalId="new-link-modal-nav"
|
{newCollectionModal ? (
|
||||||
/>
|
<NewCollectionModal onClose={() => setNewCollectionModal(false)} />
|
||||||
<NewCollectionModal
|
) : undefined}
|
||||||
isOpen={newCollectionModal}
|
|
||||||
onClose={() => setNewCollectionModal(false)}
|
|
||||||
modalId="new-collection-modal-nav"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,11 +31,9 @@ export default function NoLinksFound({ text }: Props) {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<NewLinkModal
|
{newLinkModal ? (
|
||||||
isOpen={newLinkModal}
|
<NewLinkModal onClose={() => setNewLinkModal(false)} />
|
||||||
onClose={() => setNewLinkModal(false)}
|
) : undefined}
|
||||||
modalId="new-link-modal"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -233,24 +233,24 @@ export default function Index() {
|
||||||
</div>
|
</div>
|
||||||
{activeCollection ? (
|
{activeCollection ? (
|
||||||
<>
|
<>
|
||||||
<EditCollectionModal
|
{editCollectionModal ? (
|
||||||
isOpen={editCollectionModal}
|
<EditCollectionModal
|
||||||
onClose={() => setEditCollectionModal(false)}
|
onClose={() => setEditCollectionModal(false)}
|
||||||
modalId={"edit-collection-modal" + activeCollection.id}
|
activeCollection={activeCollection}
|
||||||
activeCollection={activeCollection}
|
/>
|
||||||
/>
|
) : undefined}
|
||||||
<EditCollectionSharingModal
|
{editCollectionSharingModal ? (
|
||||||
isOpen={editCollectionSharingModal}
|
<EditCollectionSharingModal
|
||||||
onClose={() => setEditCollectionSharingModal(false)}
|
onClose={() => setEditCollectionSharingModal(false)}
|
||||||
modalId={"edit-collection-sharing-modal" + activeCollection.id}
|
activeCollection={activeCollection}
|
||||||
activeCollection={activeCollection}
|
/>
|
||||||
/>
|
) : undefined}
|
||||||
<DeleteCollectionModal
|
{deleteCollectionModal ? (
|
||||||
isOpen={deleteCollectionModal}
|
<DeleteCollectionModal
|
||||||
onClose={() => setDeleteCollectionModal(false)}
|
onClose={() => setDeleteCollectionModal(false)}
|
||||||
modalId={"delete-collection-modal" + activeCollection.id}
|
activeCollection={activeCollection}
|
||||||
activeCollection={activeCollection}
|
/>
|
||||||
/>
|
) : undefined}
|
||||||
</>
|
</>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
|
|
|
@ -126,11 +126,9 @@ export default function Collections() {
|
||||||
</>
|
</>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
</div>
|
</div>
|
||||||
<NewCollectionModal
|
{newCollectionModal ? (
|
||||||
isOpen={newCollectionModal}
|
<NewCollectionModal onClose={() => setNewCollectionModal(false)} />
|
||||||
onClose={() => setNewCollectionModal(false)}
|
) : undefined}
|
||||||
modalId="new-collection-modal-1"
|
|
||||||
/>
|
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -315,11 +315,9 @@ export default function Dashboard() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<NewLinkModal
|
{newLinkModal ? (
|
||||||
isOpen={newLinkModal}
|
<NewLinkModal onClose={() => setNewLinkModal(false)} />
|
||||||
onClose={() => setNewLinkModal(false)}
|
) : undefined}
|
||||||
modalId="new-link-modal"
|
|
||||||
/>
|
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -228,12 +228,12 @@ export default function PublicCollections() {
|
||||||
</p> */}
|
</p> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<EditCollectionSharingModal
|
{editCollectionSharingModal ? (
|
||||||
isOpen={editCollectionSharingModal}
|
<EditCollectionSharingModal
|
||||||
onClose={() => setEditCollectionSharingModal(false)}
|
onClose={() => setEditCollectionSharingModal(false)}
|
||||||
modalId={"edit-collection-sharing-modal" + collection.id}
|
activeCollection={collection}
|
||||||
activeCollection={collection}
|
/>
|
||||||
/>
|
) : undefined}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
|
|
|
@ -69,7 +69,7 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.slide-up {
|
.slide-up {
|
||||||
animation: slide-up-animation 70ms;
|
animation: slide-up-animation 200ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slide-down {
|
.slide-down {
|
||||||
|
|
Ŝarĝante…
Reference in New Issue