improved DX

This commit is contained in:
daniel31x13 2023-12-01 17:42:45 -05:00
parent a36769c521
commit 9e4502c015
17 changed files with 744 additions and 1016 deletions

View File

@ -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>
); );
} }

View File

@ -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>
); );
} }

View File

@ -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";

View File

@ -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>

View File

@ -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 &quot;
<span className="font-bold">{collection.name}</span>
&quot; 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 &quot; />
<span className="font-bold">{collection.name}</span> </div>
&quot; 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>
); );
} }

View File

@ -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>
); );
} }

View File

@ -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>
); );
} }

View File

@ -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>
); );
} }

View File

@ -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>
); );
} }

View File

@ -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>
); );
} }

View File

@ -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>
); );
} }

View File

@ -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>
); );
} }

View File

@ -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>

View File

@ -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>
); );
} }

View File

@ -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>
); );
} }

View File

@ -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>
) : ( ) : (
<></> <></>

View File

@ -69,7 +69,7 @@ body {
} }
.slide-up { .slide-up {
animation: slide-up-animation 70ms; animation: slide-up-animation 200ms;
} }
.slide-down { .slide-down {