recreated modals and many other components

This commit is contained in:
daniel31x13 2023-12-01 12:01:56 -05:00
parent 6d51b6de53
commit 732a5227d3
28 changed files with 1144 additions and 282 deletions

View File

@ -10,6 +10,8 @@ import usePermissions from "@/hooks/usePermissions";
import useLocalSettingsStore from "@/store/localSettings"; import useLocalSettingsStore from "@/store/localSettings";
import getPublicUserData from "@/lib/client/getPublicUserData"; import getPublicUserData from "@/lib/client/getPublicUserData";
import useAccountStore from "@/store/account"; import useAccountStore from "@/store/account";
import EditCollectionModal from "./Modals/EditCollectionModal";
import EditCollectionSharingModal from "./Modals/EditCollectionSharingModal";
type Props = { type Props = {
collection: CollectionIncludingMembersAndLinkCount; collection: CollectionIncludingMembersAndLinkCount;
@ -57,6 +59,10 @@ export default function CollectionCard({ collection, className }: Props) {
fetchOwner(); fetchOwner();
}, [collection]); }, [collection]);
const [editCollectionModal, setEditCollectionModal] = useState(false);
const [editCollectionSharingModal, setEditCollectionSharingModal] =
useState(false);
return ( return (
<div className="relative"> <div className="relative">
<div className="dropdown dropdown-bottom dropdown-end absolute top-3 right-3 z-10"> <div className="dropdown dropdown-bottom dropdown-end absolute top-3 right-3 z-10">
@ -71,19 +77,12 @@ export default function CollectionCard({ collection, className }: Props) {
{permissions === true ? ( {permissions === true ? (
<li> <li>
<div <div
className="px-2 py-1 rounded-lg"
role="button" role="button"
className="px-2 py-1 rounded-lg"
tabIndex={0} tabIndex={0}
onClick={() => { onClick={() => {
(document?.activeElement as HTMLElement)?.blur(); (document?.activeElement as HTMLElement)?.blur();
collection && setEditCollectionModal(true);
setModal({
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: collection,
});
}} }}
> >
Edit Collection Info Edit Collection Info
@ -97,15 +96,7 @@ export default function CollectionCard({ collection, className }: Props) {
tabIndex={0} tabIndex={0}
onClick={() => { onClick={() => {
(document?.activeElement as HTMLElement)?.blur(); (document?.activeElement as HTMLElement)?.blur();
collection && setEditCollectionSharingModal(true);
setModal({
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: collection,
defaultIndex: permissions === true ? 1 : 0,
});
}} }}
> >
{permissions === true ? "Share and Collaborate" : "View Team"} {permissions === true ? "Share and Collaborate" : "View Team"}
@ -136,17 +127,7 @@ export default function CollectionCard({ collection, className }: Props) {
</div> </div>
<div <div
className="flex items-center absolute bottom-3 left-3 z-10 btn px-2 btn-ghost rounded-full" className="flex items-center absolute bottom-3 left-3 z-10 btn px-2 btn-ghost rounded-full"
onClick={() => onClick={() => setEditCollectionSharingModal(true)}
collection &&
setModal({
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: collection,
defaultIndex: permissions === true ? 1 : 0,
})
}
> >
{collectionOwner.id ? ( {collectionOwner.id ? (
<ProfilePhoto <ProfilePhoto
@ -219,6 +200,18 @@ export default function CollectionCard({ collection, className }: Props) {
</div> </div>
</div> </div>
</Link> </Link>
<EditCollectionModal
isOpen={editCollectionModal}
onClose={() => setEditCollectionModal(false)}
modalId={"edit-collection-modal" + collection.id}
activeCollection={collection}
/>
<EditCollectionSharingModal
isOpen={editCollectionSharingModal}
onClose={() => setEditCollectionSharingModal(false)}
modalId={"edit-collection-sharing-modal" + collection.id}
activeCollection={collection}
/>
</div> </div>
); );
} }

View File

@ -24,7 +24,7 @@ export const styles: StylesConfig = {
? "1px solid oklch(var(--p))" ? "1px solid oklch(var(--p))"
: "1px solid oklch(var(--nc))", : "1px solid oklch(var(--nc))",
boxShadow: "none", boxShadow: "none",
height: "2.6rem", minHeight: "2.6rem",
}), }),
container: (styles, state) => ({ container: (styles, state) => ({
...styles, ...styles,

View File

@ -22,6 +22,7 @@ import isValidUrl from "@/lib/client/isValidUrl";
import Link from "next/link"; import Link from "next/link";
import unescapeString from "@/lib/client/unescapeString"; import unescapeString from "@/lib/client/unescapeString";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import EditLinkModal from "./Modals/EditLinkModal";
type Props = { type Props = {
link: LinkIncludingShortenedCollectionAndTags; link: LinkIncludingShortenedCollectionAndTags;
@ -135,147 +136,142 @@ export default function LinkCard({ link, count, className }: Props) {
} }
); );
const [newLinkModal, setNewLinkModal] = useState(false);
return ( return (
<> <div
className={`h-fit border border-solid border-neutral-content bg-base-200 shadow hover:shadow-none duration-100 rounded-2xl relative group ${
className || ""
}`}
>
{permissions === true ||
permissions?.canUpdate ||
permissions?.canDelete ? (
<div className="dropdown dropdown-left dropdown-start absolute top-3 right-3 z-20">
<div
tabIndex={0}
role="button"
className="btn btn-ghost btn-sm btn-square text-neutral"
>
<FontAwesomeIcon
icon={faEllipsis}
title="More"
className="w-5 h-5"
id={"expand-dropdown" + collection.id}
/>
</div>
<ul className="dropdown-content z-[1] menu p-1 shadow bg-base-200 border border-neutral-content rounded-xl w-40 mr-1">
{permissions === true ? (
<li>
<div
role="button"
className="px-2 py-1 rounded-lg"
tabIndex={0}
onClick={() => {
(document?.activeElement as HTMLElement)?.blur();
pinLink();
}}
>
{link?.pinnedBy && link.pinnedBy[0]
? "Unpin"
: "Pin to Dashboard"}
</div>
</li>
) : undefined}
{permissions === true || permissions?.canUpdate ? (
<li>
<div
role="button"
className="px-2 py-1 rounded-lg"
tabIndex={0}
onClick={() => {
(document?.activeElement as HTMLElement)?.blur();
setNewLinkModal(true);
}}
>
Edit
</div>
</li>
) : undefined}
{permissions === true ? (
<li>
<div
role="button"
className="px-2 py-1 rounded-lg"
tabIndex={0}
onClick={() => {
(document?.activeElement as HTMLElement)?.blur();
updateArchive();
}}
>
Refresh Link
</div>
</li>
) : undefined}
{permissions === true || permissions?.canDelete ? (
<li>
<div
role="button"
className="px-2 py-1 rounded-lg"
tabIndex={0}
onClick={() => {
(document?.activeElement as HTMLElement)?.blur();
deleteLink();
}}
>
Delete
</div>
</li>
) : undefined}
</ul>
</div>
) : undefined}
<div <div
className={`h-fit border border-solid border-neutral-content bg-base-200 shadow hover:shadow-none duration-100 rounded-2xl relative group ${ onClick={() => router.push("/links/" + link.id)}
className || "" className="flex items-start cursor-pointer gap-5 sm:gap-10 h-full w-full p-4"
}`}
> >
{permissions === true || {url && account.displayLinkIcons && (
permissions?.canUpdate || <Image
permissions?.canDelete ? ( src={`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url.origin}&size=32`}
<div className="dropdown dropdown-left dropdown-start absolute top-3 right-3 z-20"> width={64}
<div height={64}
tabIndex={0} alt=""
role="button" className={`${
className="btn btn-ghost btn-sm btn-square text-neutral" account.blurredFavicons ? "blur-sm " : ""
}absolute w-16 group-hover:opacity-80 duration-100 rounded-2xl bottom-5 right-5 opacity-60 select-none z-10`}
draggable="false"
onError={(e) => {
const target = e.target as HTMLElement;
target.style.display = "none";
}}
/>
)}
<div className="flex justify-between gap-5 w-full h-full z-0">
<div className="flex flex-col justify-between w-full">
<div className="flex items-baseline gap-1">
<p className="text-sm text-neutral">{count + 1}</p>
<p className="text-lg truncate capitalize w-full pr-8">
{unescapeString(link.name || link.description)}
</p>
</div>
<Link
href={`/collections/${link.collection.id}`}
onClick={(e) => {
e.stopPropagation();
}}
className="flex items-center gap-1 max-w-full w-fit my-1 hover:opacity-70 duration-100"
> >
<FontAwesomeIcon <FontAwesomeIcon
icon={faEllipsis} icon={faFolder}
title="More" className="w-4 h-4 mt-1 drop-shadow"
className="w-5 h-5" style={{ color: collection?.color }}
id={"expand-dropdown" + collection.id}
/> />
</div> <p className="truncate capitalize w-full">{collection?.name}</p>
<ul className="dropdown-content z-[1] menu p-1 shadow bg-base-200 border border-neutral-content rounded-xl w-40 mr-1"> </Link>
{permissions === true ? (
<li>
<div
role="button"
className="px-2 py-1 rounded-lg"
tabIndex={0}
onClick={() => {
(document?.activeElement as HTMLElement)?.blur();
pinLink();
}}
>
{link?.pinnedBy && link.pinnedBy[0]
? "Unpin"
: "Pin to Dashboard"}
</div>
</li>
) : undefined}
{permissions === true || permissions?.canUpdate ? (
<li>
<div
role="button"
className="px-2 py-1 rounded-lg"
tabIndex={0}
onClick={() => {
(document?.activeElement as HTMLElement)?.blur();
collection &&
setModal({
modal: "LINK",
state: true,
method: "UPDATE",
active: link,
});
}}
>
Edit
</div>
</li>
) : undefined}
{permissions === true ? (
<li>
<div
role="button"
className="px-2 py-1 rounded-lg"
tabIndex={0}
onClick={() => {
(document?.activeElement as HTMLElement)?.blur();
updateArchive();
}}
>
Refresh Link
</div>
</li>
) : undefined}
{permissions === true || permissions?.canDelete ? (
<li>
<div
role="button"
className="px-2 py-1 rounded-lg"
tabIndex={0}
onClick={() => {
(document?.activeElement as HTMLElement)?.blur();
deleteLink();
}}
>
Delete
</div>
</li>
) : undefined}
</ul>
</div>
) : undefined}
<div {/* {link.tags[0] ? (
onClick={() => router.push("/links/" + link.id)}
className="flex items-start cursor-pointer gap-5 sm:gap-10 h-full w-full p-4"
>
{url && account.displayLinkIcons && (
<Image
src={`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url.origin}&size=32`}
width={64}
height={64}
alt=""
className={`${
account.blurredFavicons ? "blur-sm " : ""
}absolute w-16 group-hover:opacity-80 duration-100 rounded-2xl bottom-5 right-5 opacity-60 select-none z-10`}
draggable="false"
onError={(e) => {
const target = e.target as HTMLElement;
target.style.display = "none";
}}
/>
)}
<div className="flex justify-between gap-5 w-full h-full z-0">
<div className="flex flex-col justify-between w-full">
<div className="flex items-baseline gap-1">
<p className="text-sm text-neutral">{count + 1}</p>
<p className="text-lg truncate capitalize w-full pr-8">
{unescapeString(link.name || link.description)}
</p>
</div>
<Link
href={`/collections/${link.collection.id}`}
onClick={(e) => {
e.stopPropagation();
}}
className="flex items-center gap-1 max-w-full w-fit my-1 hover:opacity-70 duration-100"
>
<FontAwesomeIcon
icon={faFolder}
className="w-4 h-4 mt-1 drop-shadow"
style={{ color: collection?.color }}
/>
<p className="truncate capitalize w-full">{collection?.name}</p>
</Link>
{/* {link.tags[0] ? (
<div className="flex gap-3 items-center flex-wrap my-2 truncate relative"> <div className="flex gap-3 items-center flex-wrap my-2 truncate relative">
<div className="flex gap-1 items-center flex-nowrap"> <div className="flex gap-1 items-center flex-nowrap">
{link.tags.map((e, i) => ( {link.tags.map((e, i) => (
@ -295,25 +291,30 @@ export default function LinkCard({ link, count, className }: Props) {
</div> </div>
) : undefined} */} ) : undefined} */}
<Link <Link
href={link.url} href={link.url}
target="_blank" target="_blank"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
}} }}
className="flex items-center gap-1 max-w-full w-fit text-neutral hover:opacity-70 duration-100" className="flex items-center gap-1 max-w-full w-fit text-neutral hover:opacity-70 duration-100"
> >
<FontAwesomeIcon icon={faLink} className="mt-1 w-4 h-4" /> <FontAwesomeIcon icon={faLink} className="mt-1 w-4 h-4" />
<p className="truncate w-full">{shortendURL}</p> <p className="truncate w-full">{shortendURL}</p>
</Link> </Link>
<div className="flex items-center gap-1 text-neutral"> <div className="flex items-center gap-1 text-neutral">
<FontAwesomeIcon icon={faCalendarDays} className="w-4 h-4" /> <FontAwesomeIcon icon={faCalendarDays} className="w-4 h-4" />
<p>{formattedDate}</p> <p>{formattedDate}</p>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</> <EditLinkModal
isOpen={newLinkModal}
onClose={() => setNewLinkModal(false)}
modalId={"edit-link-modal" + link.id}
activeLink={link}
/>
</div>
); );
} }

View File

@ -139,7 +139,7 @@ export default function TeamManagement({
) : null} ) : null}
{permissions !== true && collection.isPublic && ( {permissions !== true && collection.isPublic && (
<hr className="mb-3 border border-neutral-content" /> <div className="divider mb-3 mt-0"></div>
)} )}
{permissions === true && ( {permissions === true && (

View File

@ -34,7 +34,7 @@ export default function ViewTeam({ collection }: Props) {
<div <div
className="relative border px-2 rounded-md border-neutral flex min-h-[4rem] gap-2 justify-between" className="relative border px-2 rounded-md border-neutral flex min-h-[4rem] gap-2 justify-between"
title={`'@${collectionOwner.username}' is the owner of this collection.`} title={`@${collectionOwner.username} is the owner of this collection.`}
> >
<div className="flex items-center gap-2 w-full"> <div className="flex items-center gap-2 w-full">
<ProfilePhoto <ProfilePhoto

View File

@ -0,0 +1,164 @@
import React, { useEffect, useState } from "react";
import TextInput from "@/components/TextInput";
import useCollectionStore from "@/store/collections";
import toast, { Toaster } from "react-hot-toast";
import { faFolder } from "@fortawesome/free-solid-svg-icons";
import { HexColorPicker } from "react-colorful";
import { Collection } from "@prisma/client";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
type Props = {
modalId: string;
isOpen: boolean;
onClose: Function;
activeCollection: CollectionIncludingMembersAndLinkCount;
};
export default function EditCollectionModal({
modalId,
isOpen,
onClose,
activeCollection,
}: Props) {
const modal = document.getElementById(modalId);
useEffect(() => {
modal?.addEventListener("close", () => {
onClose();
});
return () => {
modal?.addEventListener("close", () => {
onClose();
});
};
}, [isOpen]);
const [collection, setCollection] =
useState<CollectionIncludingMembersAndLinkCount>(activeCollection);
useEffect(() => {
setCollection(activeCollection);
}, [isOpen]);
const [submitLoader, setSubmitLoader] = useState(false);
const { updateCollection } = useCollectionStore();
const submit = async () => {
if (!submitLoader) {
setSubmitLoader(true);
if (!collection) return null;
setSubmitLoader(true);
const load = toast.loading("Updating...");
let response;
response = await updateCollection(collection as any);
toast.dismiss(load);
if (response.ok) {
toast.success(`Updated!`);
(document.getElementById(modalId) as any).close();
} else toast.error(response.data as string);
setSubmitLoader(false);
}
};
return (
<dialog
id={modalId}
className="modal backdrop-blur-sm overflow-y-auto"
open={isOpen}
>
<Toaster
position="top-center"
reverseOrder={false}
toastOptions={{
className:
"border border-sky-100 dark:border-neutral-700 dark:bg-neutral-900 dark:text-white",
}}
/>
<div className="modal-box max-h-full overflow-y-visible border border-neutral-content w-11/12 max-w-2xl">
<form method="dialog">
<button className="btn btn-sm outline-none btn-circle btn-ghost absolute right-3 top-3">
</button>
</form>
<p className="text-xl mb-5 font-thin">Edit Collection Info</p>
<div className="flex flex-col gap-3">
<div className="flex flex-col sm:flex-row gap-3">
<div className="w-full">
<p className="mb-2">Name</p>
<div className="flex flex-col gap-3">
<TextInput
className="bg-base-200"
value={collection.name}
placeholder="e.g. Example Collection"
onChange={(e) =>
setCollection({ ...collection, name: e.target.value })
}
/>
<div>
<p className="w-full mb-2">Color</p>
<div className="color-picker flex justify-between">
<div className="flex flex-col gap-2 items-center w-32">
<div style={{ color: collection.color }}>
<FontAwesomeIcon
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>
<HexColorPicker
color={collection.color}
onChange={(e) =>
setCollection({ ...collection, color: e })
}
/>
</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>
<button className="btn btn-accent w-fit ml-auto" onClick={submit}>
Save
</button>
</div>
</div>
<form method="dialog" className="modal-backdrop">
<button>close</button>
</form>
</dialog>
);
}

View File

@ -0,0 +1,500 @@
import React, { useEffect, useState } from "react";
import TextInput from "@/components/TextInput";
import useCollectionStore from "@/store/collections";
import toast, { Toaster } from "react-hot-toast";
import {
faClose,
faCrown,
faFolder,
faUserPlus,
} from "@fortawesome/free-solid-svg-icons";
import { HexColorPicker } from "react-colorful";
import { Collection } from "@prisma/client";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { CollectionIncludingMembersAndLinkCount, Member } from "@/types/global";
import getPublicUserData from "@/lib/client/getPublicUserData";
import useAccountStore from "@/store/account";
import usePermissions from "@/hooks/usePermissions";
import ProfilePhoto from "../ProfilePhoto";
import addMemberToCollection from "@/lib/client/addMemberToCollection";
import Checkbox from "../Checkbox";
type Props = {
modalId: string;
isOpen: boolean;
onClose: Function;
activeCollection: CollectionIncludingMembersAndLinkCount;
};
export default function EditCollectionSharingModal({
modalId,
isOpen,
onClose,
activeCollection,
}: Props) {
const modal = document.getElementById(modalId);
useEffect(() => {
modal?.addEventListener("close", () => {
onClose();
});
return () => {
modal?.addEventListener("close", () => {
onClose();
});
};
}, [isOpen]);
const [collection, setCollection] =
useState<CollectionIncludingMembersAndLinkCount>(activeCollection);
useEffect(() => {
setCollection(activeCollection);
}, [isOpen]);
const [submitLoader, setSubmitLoader] = useState(false);
const { updateCollection } = useCollectionStore();
const submit = async () => {
if (!submitLoader) {
setSubmitLoader(true);
if (!collection) return null;
setSubmitLoader(true);
const load = toast.loading("Updating...");
let response;
response = await updateCollection(collection as any);
toast.dismiss(load);
if (response.ok) {
toast.success(`Updated!`);
(document.getElementById(modalId) as any).close();
} else toast.error(response.data as string);
setSubmitLoader(false);
}
};
const { account } = useAccountStore();
const permissions = usePermissions(collection.id as number);
const currentURL = new URL(document.URL);
const publicCollectionURL = `${currentURL.origin}/public/collections/${collection.id}`;
const [memberUsername, setMemberUsername] = useState("");
const [collectionOwner, setCollectionOwner] = useState({
id: null,
name: "",
username: "",
image: "",
});
useEffect(() => {
const fetchOwner = async () => {
const owner = await getPublicUserData(collection.ownerId as number);
setCollectionOwner(owner);
};
fetchOwner();
}, []);
const setMemberState = (newMember: Member) => {
if (!collection) return null;
setCollection({
...collection,
members: [...collection.members, newMember],
});
setMemberUsername("");
};
return (
<dialog
id={modalId}
className="modal backdrop-blur-sm overflow-y-auto"
open={isOpen}
>
<Toaster
position="top-center"
reverseOrder={false}
toastOptions={{
className:
"border border-sky-100 dark:border-neutral-700 dark:bg-neutral-900 dark:text-white",
}}
/>
<div className="modal-box max-h-full overflow-y-visible border border-neutral-content w-11/12 max-w-2xl">
<form method="dialog">
<button className="btn btn-sm outline-none btn-circle btn-ghost absolute right-3 top-3">
</button>
</form>
<p className="text-xl font-thin mb-5">
{permissions === true ? "Share and Collaborate" : "Team"}
</p>
<div className="flex flex-col gap-3">
{permissions === true && (
<div>
<p>Make Public</p>
<label className="label cursor-pointer justify-start gap-2">
<input
type="checkbox"
checked={collection.isPublic}
onChange={() =>
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">
This will let <b>Anyone</b> to view this collection and it's
users.
</p>
</div>
)}
{collection.isPublic ? (
<div className="pl-5">
<p className="mb-2">Public Link (Click to copy)</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>
) : 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
)
}
/>
<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>
</div>
</div>
</div>
{collection.members
.sort((a, b) => (a.userId as number) - (b.userId as number))
.map((e, i) => {
return (
<div
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"
>
{permissions === true && (
<FontAwesomeIcon
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"
title="Remove Member"
onClick={() => {
const updatedMembers = collection.members.filter(
(member) => {
return (
member.user.username !== e.user.username
);
}
);
setCollection({
...collection,
members: updatedMembers,
});
}}
/>
)}
<div className="flex items-center gap-2">
<ProfilePhoto
src={e.user.image ? e.user.image : undefined}
/>
<div>
<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>
</>
)}
{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">
<button>close</button>
</form>
</dialog>
);
}

View File

@ -0,0 +1,205 @@
import React, { useEffect, useState } from "react";
import { Toaster } from "react-hot-toast";
import CollectionSelection from "@/components/InputSelect/CollectionSelection";
import TagSelection from "@/components/InputSelect/TagSelection";
import TextInput from "@/components/TextInput";
import unescapeString from "@/lib/client/unescapeString";
import useLinkStore from "@/store/links";
import { LinkIncludingShortenedCollectionAndTags } from "@/types/global";
import toast from "react-hot-toast";
import Link from "next/link";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faLink } from "@fortawesome/free-solid-svg-icons";
type Props = {
modalId: string;
isOpen: boolean;
onClose: Function;
activeLink: LinkIncludingShortenedCollectionAndTags;
};
export default function EditLinkModal({
modalId,
isOpen,
onClose,
activeLink,
}: Props) {
const modal = document.getElementById(modalId);
const [link, setLink] =
useState<LinkIncludingShortenedCollectionAndTags>(activeLink);
let shortendURL;
try {
shortendURL = new URL(link.url).host.toLowerCase();
} catch (error) {
console.log(error);
}
const { updateLink } = useLinkStore();
const [submitLoader, setSubmitLoader] = useState(false);
const setCollection = (e: any) => {
if (e?.__isNew__) e.value = null;
setLink({
...link,
collection: { id: e?.value, name: e?.label, ownerId: e?.ownerId },
});
};
const setTags = (e: any) => {
const tagNames = e.map((e: any) => {
return { name: e.label };
});
setLink({ ...link, tags: tagNames });
};
useEffect(() => {
setLink(activeLink);
modal?.addEventListener("close", () => {
onClose();
});
return () => {
modal?.addEventListener("close", () => {
onClose();
});
};
}, [isOpen]);
const submit = async () => {
if (!submitLoader) {
setSubmitLoader(true);
let response;
const load = toast.loading("Updating...");
response = await updateLink(link);
toast.dismiss(load);
if (response.ok) {
toast.success(`Updated!`);
(document.getElementById(modalId) as any).close();
} else toast.error(response.data as string);
setSubmitLoader(false);
return response;
}
};
return (
<dialog
id={modalId}
className="modal backdrop-blur-sm overflow-y-auto"
open={isOpen}
>
<Toaster
position="top-center"
reverseOrder={false}
toastOptions={{
className:
"border border-sky-100 dark:border-neutral-700 dark:bg-neutral-900 dark:text-white",
}}
/>
<div className="modal-box max-h-full overflow-y-visible border border-neutral-content w-11/12 max-w-2xl">
<form method="dialog">
<button className="btn btn-sm outline-none btn-circle btn-ghost absolute right-3 top-3">
</button>
</form>
<p className="text-xl mb-5 font-thin">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>
<div className="w-full">
<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 className="mt-5">
{/* <hr className="mb-3 border border-neutral-content" /> */}
<div className="grid sm:grid-cols-2 gap-3">
<div>
<p className="mb-2">Collection</p>
{link.collection.name ? (
<CollectionSelection
onChange={setCollection}
// defaultValue={{
// label: link.collection.name,
// value: link.collection.id,
// }}
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>
</div>
</div>
<div className="flex justify-end items-center mt-5">
<button className="btn btn-accent" onClick={submit}>
Save
</button>
</div>
</div>
<form method="dialog" className="modal-backdrop">
<button>close</button>
</form>
</dialog>
);
}

View File

@ -18,15 +18,15 @@ export default function NewCollectionModal({
isOpen, isOpen,
onClose, onClose,
}: Props) { }: Props) {
const newModal = document.getElementById(modalId); const modal = document.getElementById(modalId);
useEffect(() => { useEffect(() => {
newModal?.addEventListener("close", () => { modal?.addEventListener("close", () => {
onClose(); onClose();
}); });
return () => { return () => {
newModal?.addEventListener("close", () => { modal?.addEventListener("close", () => {
onClose(); onClose();
}); });
}; };
@ -63,7 +63,7 @@ export default function NewCollectionModal({
toast.dismiss(load); toast.dismiss(load);
if (response.ok) { if (response.ok) {
toast.success(`Collection "Created!"`); toast.success("Created!");
(document.getElementById(modalId) as any).close(); (document.getElementById(modalId) as any).close();
} else toast.error(response.data as string); } else toast.error(response.data as string);
@ -107,28 +107,32 @@ export default function NewCollectionModal({
setCollection({ ...collection, name: e.target.value }) setCollection({ ...collection, name: e.target.value })
} }
/> />
<div className="color-picker flex justify-between"> <div>
<div className="flex flex-col justify-between items-center w-32"> <p className="w-full mb-2">Color</p>
<p className="w-full mb-2">Color</p> <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>
<div <HexColorPicker
className="btn btn-ghost btn-xs" color={collection.color}
onClick={() => onChange={(e) =>
setCollection({ ...collection, color: "#0ea5e9" }) setCollection({ ...collection, color: e })
} }
> />
Reset
</div>
</div> </div>
<HexColorPicker
color={collection.color}
onChange={(e) => setCollection({ ...collection, color: e })}
/>
</div> </div>
</div> </div>
</div> </div>
@ -136,7 +140,7 @@ export default function NewCollectionModal({
<div className="w-full"> <div className="w-full">
<p className="mb-2">Description</p> <p className="mb-2">Description</p>
<textarea <textarea
className="w-full h-[11rem] resize-none border rounded-md duration-100 bg-base-200 p-2 outline-none border-neutral-content focus:border-primary" 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..." placeholder="The purpose of this Collection..."
value={collection.description} value={collection.description}
onChange={(e) => onChange={(e) =>

View File

@ -18,7 +18,7 @@ type Props = {
}; };
export default function NewLinkModal({ modalId, isOpen, onClose }: Props) { export default function NewLinkModal({ modalId, isOpen, onClose }: Props) {
const newModal = document.getElementById(modalId); const modal = document.getElementById(modalId);
const { data } = useSession(); const { data } = useSession();
@ -95,12 +95,12 @@ export default function NewLinkModal({ modalId, isOpen, onClose }: Props) {
}, },
}); });
newModal?.addEventListener("close", () => { modal?.addEventListener("close", () => {
onClose(); onClose();
}); });
return () => { return () => {
newModal?.addEventListener("close", () => { modal?.addEventListener("close", () => {
onClose(); onClose();
}); });
}; };

View File

@ -11,7 +11,7 @@ import ProfilePhoto from "@/components/ProfilePhoto";
import useWindowDimensions from "@/hooks/useWindowDimensions"; import useWindowDimensions from "@/hooks/useWindowDimensions";
import ToggleDarkMode from "./ToggleDarkMode"; import ToggleDarkMode from "./ToggleDarkMode";
import useLocalSettingsStore from "@/store/localSettings"; import useLocalSettingsStore from "@/store/localSettings";
import NewLinkModal from "./Modals/NewLink"; import NewLinkModal from "./Modals/NewLinkModal";
import NewCollectionModal from "./Modals/NewCollectionModal"; import NewCollectionModal from "./Modals/NewCollectionModal";
import Link from "next/link"; import Link from "next/link";
@ -46,11 +46,8 @@ export default function Navbar() {
setSidebar(!sidebar); setSidebar(!sidebar);
}; };
const [newLinkModalIsOpen, setNewLinkModalIsOpen] = useState(false); const [newLinkModal, setNewLinkModal] = useState(false);
const closeNewLinkModal = () => setNewLinkModalIsOpen(false); const [newCollectionModal, setNewCollectionModal] = useState(false);
const [newCollectionModalIsOpen, setNewCollectionModalIsOpen] =
useState(false);
const closeNewCollectionModal = () => setNewCollectionModalIsOpen(false);
return ( return (
<div className="flex justify-between gap-2 items-center px-4 py-2 border-solid border-b-neutral-content border-b"> <div className="flex justify-between gap-2 items-center px-4 py-2 border-solid border-b-neutral-content border-b">
@ -72,7 +69,10 @@ export default function Navbar() {
className="flex items-center group btn btn-accent text-white btn-sm px-2" className="flex items-center group btn btn-accent text-white btn-sm px-2"
> >
<FontAwesomeIcon icon={faPlus} className="w-5 h-5" /> <FontAwesomeIcon icon={faPlus} className="w-5 h-5" />
<FontAwesomeIcon icon={faCaretDown} className="w-3 h-3" /> <FontAwesomeIcon
icon={faCaretDown}
className="w-2 h-2 sm:w-3 sm:h-3"
/>
</div> </div>
</div> </div>
<ul className="dropdown-content z-[1] menu p-1 shadow bg-base-200 border border-neutral-content rounded-xl w-32 mt-1"> <ul className="dropdown-content z-[1] menu p-1 shadow bg-base-200 border border-neutral-content rounded-xl w-32 mt-1">
@ -81,7 +81,7 @@ export default function Navbar() {
className="px-2 py-1 rounded-lg" className="px-2 py-1 rounded-lg"
onClick={() => { onClick={() => {
(document?.activeElement as HTMLElement)?.blur(); (document?.activeElement as HTMLElement)?.blur();
setNewLinkModalIsOpen(true); setNewLinkModal(true);
}} }}
tabIndex={0} tabIndex={0}
role="button" role="button"
@ -94,7 +94,7 @@ export default function Navbar() {
className="px-2 py-1 rounded-lg" className="px-2 py-1 rounded-lg"
onClick={() => { onClick={() => {
(document?.activeElement as HTMLElement)?.blur(); (document?.activeElement as HTMLElement)?.blur();
setNewCollectionModalIsOpen(true); setNewCollectionModal(true);
}} }}
tabIndex={0} tabIndex={0}
role="button" role="button"
@ -164,13 +164,13 @@ export default function Navbar() {
</div> </div>
) : null} ) : null}
<NewLinkModal <NewLinkModal
isOpen={newLinkModalIsOpen} isOpen={newLinkModal}
onClose={closeNewLinkModal} onClose={() => setNewLinkModal(false)}
modalId="new-link-modal" modalId="new-link-modal"
/> />
<NewCollectionModal <NewCollectionModal
isOpen={newCollectionModalIsOpen} isOpen={newCollectionModal}
onClose={closeNewCollectionModal} onClose={() => setNewCollectionModal(false)}
modalId="new-collection-modal" modalId="new-collection-modal"
/> />
</div> </div>

View File

@ -46,7 +46,7 @@ export default function ChooseUsername() {
Choose a Username Choose a Username
</p> </p>
<hr className="border-1 border-neutral-content" /> <div className="divider my-0"></div>
<div> <div>
<p className="text-sm w-fit font-semibold mb-1">Username</p> <p className="text-sm w-fit font-semibold mb-1">Username</p>

View File

@ -16,6 +16,8 @@ import NoLinksFound from "@/components/NoLinksFound";
import useLocalSettingsStore from "@/store/localSettings"; import useLocalSettingsStore from "@/store/localSettings";
import useAccountStore from "@/store/account"; import useAccountStore from "@/store/account";
import getPublicUserData from "@/lib/client/getPublicUserData"; import getPublicUserData from "@/lib/client/getPublicUserData";
import EditCollectionModal from "@/components/Modals/EditCollectionModal";
import EditCollectionSharingModal from "@/components/Modals/EditCollectionSharingModal";
export default function Index() { export default function Index() {
const { setModal } = useModalStore(); const { setModal } = useModalStore();
@ -71,13 +73,17 @@ export default function Index() {
fetchOwner(); fetchOwner();
}, [activeCollection]); }, [activeCollection]);
const [editCollectionModal, setEditCollectionModal] = useState(false);
const [editCollectionSharingModal, setEditCollectionSharingModal] =
useState(false);
return ( return (
<MainLayout> <MainLayout>
<div <div
style={{ style={{
backgroundImage: `linear-gradient(${activeCollection?.color}20 10%, ${ backgroundImage: `linear-gradient(${activeCollection?.color}20 10%, ${
settings.theme === "dark" ? "#262626" : "#f3f4f6" settings.theme === "dark" ? "#262626" : "#f3f4f6"
} 50%, ${settings.theme === "dark" ? "#171717" : "#ffffff"} 100%)`, } 14rem, ${settings.theme === "dark" ? "#171717" : "#ffffff"} 100%)`,
}} }}
className="h-full p-5 flex gap-3 flex-col" className="h-full p-5 flex gap-3 flex-col"
> >
@ -103,17 +109,7 @@ export default function Index() {
<div className="flex gap-1 justify-center sm:justify-end items-center w-fit"> <div className="flex gap-1 justify-center sm:justify-end items-center w-fit">
<div <div
className="flex items-center btn px-2 btn-ghost rounded-full w-fit" className="flex items-center btn px-2 btn-ghost rounded-full w-fit"
onClick={() => onClick={() => setEditCollectionSharingModal(true)}
activeCollection &&
setModal({
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: activeCollection,
defaultIndex: permissions === true ? 1 : 0,
})
}
> >
{collectionOwner.id ? ( {collectionOwner.id ? (
<ProfilePhoto <ProfilePhoto
@ -156,7 +152,7 @@ export default function Index() {
<p>{activeCollection?.description}</p> <p>{activeCollection?.description}</p>
) : undefined} ) : undefined}
<hr className="border-1 border-neutral" /> <div className="divider my-0"></div>
<div className="flex justify-between items-end gap-5"> <div className="flex justify-between items-end gap-5">
<p>Showing {activeCollection?._count?.links} results</p> <p>Showing {activeCollection?._count?.links} results</p>
@ -184,14 +180,7 @@ export default function Index() {
tabIndex={0} tabIndex={0}
onClick={() => { onClick={() => {
(document?.activeElement as HTMLElement)?.blur(); (document?.activeElement as HTMLElement)?.blur();
activeCollection && setEditCollectionModal(true);
setModal({
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: activeCollection,
});
}} }}
> >
Edit Collection Info Edit Collection Info
@ -205,15 +194,7 @@ export default function Index() {
tabIndex={0} tabIndex={0}
onClick={() => { onClick={() => {
(document?.activeElement as HTMLElement)?.blur(); (document?.activeElement as HTMLElement)?.blur();
activeCollection && setEditCollectionSharingModal(true);
setModal({
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: activeCollection,
defaultIndex: permissions === true ? 1 : 0,
});
}} }}
> >
{permissions === true {permissions === true
@ -262,6 +243,22 @@ export default function Index() {
<NoLinksFound /> <NoLinksFound />
)} )}
</div> </div>
{activeCollection ? (
<>
<EditCollectionModal
isOpen={editCollectionModal}
onClose={() => setEditCollectionModal(false)}
modalId={"edit-collection-modal" + activeCollection.id}
activeCollection={activeCollection}
/>
<EditCollectionSharingModal
isOpen={editCollectionSharingModal}
onClose={() => setEditCollectionSharingModal(false)}
modalId={"edit-collection-sharing-modal" + activeCollection.id}
activeCollection={activeCollection}
/>
</>
) : undefined}
</MainLayout> </MainLayout>
); );
} }

View File

@ -6,7 +6,6 @@ import {
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import CollectionCard from "@/components/CollectionCard"; import CollectionCard from "@/components/CollectionCard";
import Dropdown from "@/components/Dropdown";
import { useState } from "react"; import { useState } from "react";
import MainLayout from "@/layouts/MainLayout"; import MainLayout from "@/layouts/MainLayout";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
@ -28,8 +27,7 @@ export default function Collections() {
useSort({ sortBy, setData: setSortedCollections, data: collections }); useSort({ sortBy, setData: setSortedCollections, data: collections });
const [newModalIsOpen, setNewModalIsOpen] = useState(false); const [newCollectionModal, setNewCollectionModal] = useState(false);
const closeNewModal = () => setNewModalIsOpen(false);
return ( return (
<MainLayout> <MainLayout>
@ -99,7 +97,7 @@ export default function Collections() {
<div <div
className="card card-compact shadow-md hover:shadow-none duration-200 border border-neutral-content p-5 bg-base-200 self-stretch min-h-[12rem] rounded-2xl cursor-pointer flex flex-col gap-4 justify-center items-center group btn" className="card card-compact shadow-md hover:shadow-none duration-200 border border-neutral-content p-5 bg-base-200 self-stretch min-h-[12rem] rounded-2xl cursor-pointer flex flex-col gap-4 justify-center items-center group btn"
onClick={() => setNewModalIsOpen(true)} onClick={() => setNewCollectionModal(true)}
> >
<p className="group-hover:opacity-0 duration-100">New Collection</p> <p className="group-hover:opacity-0 duration-100">New Collection</p>
<FontAwesomeIcon <FontAwesomeIcon
@ -136,8 +134,8 @@ export default function Collections() {
) : undefined} ) : undefined}
</div> </div>
<NewCollectionModal <NewCollectionModal
isOpen={newModalIsOpen} isOpen={newCollectionModal}
onClose={closeNewModal} onClose={() => setNewCollectionModal(false)}
modalId="new-collection-modal-1" modalId="new-collection-modal-1"
/> />
</MainLayout> </MainLayout>

View File

@ -10,7 +10,7 @@ export default function EmailConfirmaion() {
Please check your Email Please check your Email
</p> </p>
<hr className="border-1 border-neutral my-3" /> <div className="divider my-3"></div>
<p>A sign in link has been sent to your email address.</p> <p>A sign in link has been sent to your email address.</p>

View File

@ -122,8 +122,7 @@ export default function Dashboard() {
icon={faLink} icon={faLink}
/> />
<hr className="border-neutral-content md:hidden my-5" /> <div className="divider md:divider-horizontal"></div>
<div className="h-24 border-1 border-l border-neutral-content hidden md:block"></div>
<DashboardItem <DashboardItem
name={collections.length === 1 ? "Collection" : "Collections"} name={collections.length === 1 ? "Collection" : "Collections"}
@ -131,8 +130,7 @@ export default function Dashboard() {
icon={faFolder} icon={faFolder}
/> />
<hr className="border-neutral-content md:hidden my-5" /> <div className="divider md:divider-horizontal"></div>
<div className="h-24 border-1 border-r border-neutral-content hidden md:block"></div>
<DashboardItem <DashboardItem
name={tags.length === 1 ? "Tag" : "Tags"} name={tags.length === 1 ? "Tag" : "Tags"}

View File

@ -48,7 +48,7 @@ export default function Forgot() {
Password Recovery Password Recovery
</p> </p>
<hr className="border-1 border-neutral-content" /> <div className="divider my-0"></div>
<div> <div>
<p> <p>

View File

@ -68,7 +68,7 @@ export default function Login() {
Enter your credentials Enter your credentials
</p> </p>
<hr className="border-1 border-neutral-content" /> <div className="divider my-0"></div>
<div> <div>
<p className="text-sm w-fit font-semibold mb-1"> <p className="text-sm w-fit font-semibold mb-1">

View File

@ -103,7 +103,7 @@ export default function PublicCollections() {
style={{ style={{
backgroundImage: `linear-gradient(${collection?.color}30 10%, ${ backgroundImage: `linear-gradient(${collection?.color}30 10%, ${
settings.theme === "dark" ? "#262626" : "#f3f4f6" settings.theme === "dark" ? "#262626" : "#f3f4f6"
} 50%, ${settings.theme === "dark" ? "#171717" : "#ffffff"} 100%)`, } 18rem, ${settings.theme === "dark" ? "#171717" : "#ffffff"} 100%)`,
}} }}
> >
<ModalManagement /> <ModalManagement />
@ -195,7 +195,7 @@ export default function PublicCollections() {
<p className="mt-5">{collection.description}</p> <p className="mt-5">{collection.description}</p>
<hr className="mt-5 border-1 border-neutral" /> <div className="divider mt-5 mb-0"></div>
<div className="flex mb-5 mt-10 flex-col gap-5"> <div className="flex mb-5 mt-10 flex-col gap-5">
<div className="flex justify-between"> <div className="flex justify-between">

View File

@ -117,7 +117,7 @@ export default function Register() {
Enter your details Enter your details
</p> </p>
<hr className="border-1 border-neutral-content" /> <div className="divider my-0"></div>
<div> <div>
<p className="text-sm w-fit font-semibold mb-1">Display Name</p> <p className="text-sm w-fit font-semibold mb-1">Display Name</p>

View File

@ -153,7 +153,7 @@ export default function Account() {
<SettingsLayout> <SettingsLayout>
<p className="capitalize text-3xl font-thin inline">Account Settings</p> <p className="capitalize text-3xl font-thin inline">Account Settings</p>
<hr className="my-3 border-1 border-neutral-content" /> <div className="divider my-3"></div>
<div className="flex flex-col gap-10"> <div className="flex flex-col gap-10">
<div className="grid sm:grid-cols-2 gap-3 auto-rows-auto"> <div className="grid sm:grid-cols-2 gap-3 auto-rows-auto">
@ -235,7 +235,7 @@ export default function Account() {
</p> </p>
</div> </div>
<hr className="my-3 border-1 border-neutral-content" /> <div className="divider my-3"></div>
<div className="flex gap-3 flex-col"> <div className="flex gap-3 flex-col">
<div> <div>
@ -319,7 +319,7 @@ export default function Account() {
</p> </p>
</div> </div>
<hr className="my-3 border-1 border-neutral-content" /> <div className="divider my-3"></div>
<Checkbox <Checkbox
label="Make profile private" label="Make profile private"
@ -362,7 +362,7 @@ export default function Account() {
</p> </p>
</div> </div>
<hr className="my-3 border-1 border-neutral-content" /> <div className="divider my-3"></div>
<p> <p>
This will permanently delete ALL the Links, Collections, Tags, and This will permanently delete ALL the Links, Collections, Tags, and

View File

@ -56,7 +56,7 @@ export default function Api() {
<SettingsLayout> <SettingsLayout>
<p className="capitalize text-3xl font-thin inline">API Keys (Soon)</p> <p className="capitalize text-3xl font-thin inline">API Keys (Soon)</p>
<hr className="my-3 border-1 border-neutral-content" /> <div className="divider my-3"></div>
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<div className="badge bg-orange-500 rounded-md border border-black w-fit px-2 text-black"> <div className="badge bg-orange-500 rounded-md border border-black w-fit px-2 text-black">

View File

@ -69,7 +69,7 @@ export default function Appearance() {
<SettingsLayout> <SettingsLayout>
<p className="capitalize text-3xl font-thin inline">Appearance</p> <p className="capitalize text-3xl font-thin inline">Appearance</p>
<hr className="my-3 border-1 border-neutral-content" /> <div className="divider my-3"></div>
<div className="flex flex-col gap-10"> <div className="flex flex-col gap-10">
<div> <div>
@ -107,7 +107,9 @@ export default function Appearance() {
<div className="flex items-center gap-2 w-full rounded-md h-8"> <div className="flex items-center gap-2 w-full rounded-md h-8">
<p className="truncate w-full pr-7 text-3xl font-thin">Link Card</p> <p className="truncate w-full pr-7 text-3xl font-thin">Link Card</p>
</div> </div>
<hr className="my-3 border-1 border-neutral-content" />
<div className="divider my-3"></div>
<Checkbox <Checkbox
label="Display Icons" label="Display Icons"
state={user.displayLinkIcons} state={user.displayLinkIcons}

View File

@ -59,7 +59,7 @@ export default function Archive() {
<SettingsLayout> <SettingsLayout>
<p className="capitalize text-3xl font-thin inline">Archive Settings</p> <p className="capitalize text-3xl font-thin inline">Archive Settings</p>
<hr className="my-3 border-1 border-neutral-content" /> <div className="divider my-3"></div>
<p>Formats to Archive webpages:</p> <p>Formats to Archive webpages:</p>
<div className="p-3"> <div className="p-3">

View File

@ -13,7 +13,7 @@ export default function Billing() {
<SettingsLayout> <SettingsLayout>
<p className="capitalize text-3xl font-thin inline">Billing Settings</p> <p className="capitalize text-3xl font-thin inline">Billing Settings</p>
<hr className="my-3 border-1 border-neutral-content" /> <div className="divider my-3"></div>
<div className="w-full mx-auto flex flex-col gap-3 justify-between"> <div className="w-full mx-auto flex flex-col gap-3 justify-between">
<p className="text-md"> <p className="text-md">

View File

@ -7,7 +7,7 @@ import Link from "next/link";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faChevronLeft } from "@fortawesome/free-solid-svg-icons"; import { faChevronLeft } from "@fortawesome/free-solid-svg-icons";
export default function Password() { export default function Delete() {
const [password, setPassword] = useState(""); const [password, setPassword] = useState("");
const [comment, setComment] = useState<string>(); const [comment, setComment] = useState<string>();
const [feedback, setFeedback] = useState<string>(); const [feedback, setFeedback] = useState<string>();
@ -70,7 +70,7 @@ export default function Password() {
</p> </p>
</div> </div>
<hr className="border-1 border-neutral-content" /> <div className="divider my-0"></div>
<p> <p>
This will permanently delete all the Links, Collections, Tags, and This will permanently delete all the Links, Collections, Tags, and

View File

@ -47,7 +47,7 @@ export default function Password() {
<SettingsLayout> <SettingsLayout>
<p className="capitalize text-3xl font-thin inline">Change Password</p> <p className="capitalize text-3xl font-thin inline">Change Password</p>
<hr className="my-3 border-1 border-neutral-content" /> <div className="divider my-3"></div>
<p className="mb-3"> <p className="mb-3">
To change your password, please fill out the following. Your password To change your password, please fill out the following. Your password

View File

@ -35,7 +35,7 @@ export default function Subscribe() {
Subscribe to Linkwarden! Subscribe to Linkwarden!
</p> </p>
<hr className="border-1 border-neutral-content" /> <div className="divider my-0"></div>
<div> <div>
<p> <p>