recreated many components

This commit is contained in:
daniel31x13 2023-11-27 16:38:38 -05:00
parent b51b08b0f4
commit 916c69602d
17 changed files with 311 additions and 293 deletions

View File

@ -2,29 +2,24 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faEllipsis, faGlobe, faLink } from "@fortawesome/free-solid-svg-icons"; import { faEllipsis, faGlobe, faLink } from "@fortawesome/free-solid-svg-icons";
import Link from "next/link"; import Link from "next/link";
import { CollectionIncludingMembersAndLinkCount } from "@/types/global"; import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
import Dropdown from "./Dropdown"; import { useEffect, useState } from "react";
import { useState } from "react";
import ProfilePhoto from "./ProfilePhoto"; import ProfilePhoto from "./ProfilePhoto";
import { faCalendarDays } from "@fortawesome/free-regular-svg-icons"; import { faCalendarDays } from "@fortawesome/free-regular-svg-icons";
import useModalStore from "@/store/modals"; import useModalStore from "@/store/modals";
import usePermissions from "@/hooks/usePermissions"; import usePermissions from "@/hooks/usePermissions";
import useLocalSettingsStore from "@/store/localSettings"; import useLocalSettingsStore from "@/store/localSettings";
import getPublicUserData from "@/lib/client/getPublicUserData";
import useAccountStore from "@/store/account";
type Props = { type Props = {
collection: CollectionIncludingMembersAndLinkCount; collection: CollectionIncludingMembersAndLinkCount;
className?: string; className?: string;
}; };
type DropdownTrigger =
| {
x: number;
y: number;
}
| false;
export default function CollectionCard({ collection, className }: Props) { export default function CollectionCard({ collection, className }: Props) {
const { setModal } = useModalStore(); const { setModal } = useModalStore();
const { settings } = useLocalSettingsStore(); const { settings } = useLocalSettingsStore();
const { account } = useAccountStore();
const formattedDate = new Date(collection.createdAt as string).toLocaleString( const formattedDate = new Date(collection.createdAt as string).toLocaleString(
"en-US", "en-US",
@ -35,28 +30,40 @@ export default function CollectionCard({ collection, className }: Props) {
} }
); );
const [expandDropdown, setExpandDropdown] = useState<DropdownTrigger>(false);
const permissions = usePermissions(collection.id as number); const permissions = usePermissions(collection.id as number);
const [collectionOwner, setCollectionOwner] = useState({
id: null as unknown as number,
name: "",
username: "",
image: "",
});
useEffect(() => {
const fetchOwner = async () => {
if (collection && collection.ownerId !== account.id) {
const owner = await getPublicUserData(collection.ownerId as number);
setCollectionOwner(owner);
} else if (collection && collection.ownerId === account.id) {
setCollectionOwner({
id: account.id as number,
name: account.name,
username: account.username as string,
image: account.image as string,
});
}
};
fetchOwner();
}, [collection]);
return ( return (
<> <div className="relative">
<div <div className="dropdown dropdown-bottom dropdown-end absolute top-3 right-3 z-10">
style={{
backgroundImage: `linear-gradient(45deg, ${collection.color}30 10%, ${
settings.theme === "dark" ? "#262626" : "#f3f4f6"
} 50%, ${settings.theme === "dark" ? "#262626" : "#f9fafb"} 100%)`,
}}
className={`border border-solid border-neutral-content self-stretch min-h-[12rem] rounded-2xl shadow duration-100 hover:shadow-none hover:opacity-80 group relative ${
className || ""
}`}
>
<div <div
onClick={(e) => { tabIndex={0}
setExpandDropdown({ x: e.clientX, y: e.clientY }); role="button"
}} className="btn btn-ghost btn-sm btn-square text-neutral"
id={"expand-dropdown" + collection.id}
className="btn btn-ghost btn-sm btn-square absolute right-4 top-4 z-10"
> >
<FontAwesomeIcon <FontAwesomeIcon
icon={faEllipsis} icon={faEllipsis}
@ -65,15 +72,94 @@ export default function CollectionCard({ collection, className }: Props) {
id={"expand-dropdown" + collection.id} id={"expand-dropdown" + collection.id}
/> />
</div> </div>
<Link <ul className="dropdown-content z-[1] menu p-2 shadow bg-base-200 border border-neutral-content rounded-box w-52">
href={`/collections/${collection.id}`} {permissions === true ? (
className="flex flex-col gap-2 justify-between min-h-[12rem] h-full select-none p-5" <li>
> <div
<p className="text-2xl capitalize break-words line-clamp-3 w-4/5"> role="button"
{collection.name} tabIndex={0}
</p> onClick={() =>
collection &&
setModal({
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: collection,
})
}
>
Edit Collection Info
</div>
</li>
) : undefined}
<li>
<div
role="button"
tabIndex={0}
onClick={() =>
collection &&
setModal({
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: collection,
defaultIndex: permissions === true ? 1 : 0,
})
}
>
{permissions === true ? "Share and Collaborate" : "View Team"}
</div>
</li>
<li>
<div
role="button"
tabIndex={0}
onClick={() =>
collection &&
setModal({
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: collection,
defaultIndex: permissions === true ? 2 : 1,
})
}
>
{permissions === true ? "Delete Collection" : "Leave Collection"}
</div>
</li>
</ul>
</div>
<Link
href={`/collections/${collection.id}`}
style={{
backgroundImage: `linear-gradient(45deg, ${collection.color}30 10%, ${
settings.theme === "dark" ? "oklch(var(--b2))" : "oklch(var(--b2))"
} 50%, ${
settings.theme === "dark" ? "oklch(var(--b2))" : "oklch(var(--b2))"
} 100%)`,
}}
className="card card-compact shadow-xl hover:shadow-none duration-200 border border-neutral-content relative"
>
<div className="card-body flex flex-col justify-between min-h-[12rem]">
<div className="flex justify-between">
<p className="card-title break-words line-clamp-2 w-full">
{collection.name}
</p>
<div className="w-8 h-8 ml-10"></div>
</div>
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<div className="flex items-center w-full"> <div className="flex items-center w-full">
{collectionOwner.id ? (
<ProfilePhoto
src={collectionOwner.image || undefined}
className="w-7 h-7 -mr-3"
/>
) : undefined}
{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) => {
@ -81,14 +167,16 @@ export default function CollectionCard({ collection, className }: Props) {
<ProfilePhoto <ProfilePhoto
key={i} key={i}
src={e.user.image ? e.user.image : undefined} src={e.user.image ? e.user.image : undefined}
className="-mr-3 border-[3px]" className="-mr-3"
/> />
); );
}) })
.slice(0, 4)} .slice(0, 3)}
{collection.members.length - 4 > 0 ? ( {collection.members.length - 3 > 0 ? (
<div className="h-10 w-10 text-white flex items-center justify-center rounded-full border-[3px] bg-sky-600 dark:bg-sky-600 border-slate-200 -mr-3"> <div className={`avatar placeholder -mr-3`}>
+{collection.members.length - 4} <div className="bg-base-100 text-base-content rounded-full w-8 h-8 ring-2 ring-base-content">
<span>+{collection.members.length - 3}</span>
</div>
</div> </div>
) : null} ) : null}
</div> </div>
@ -107,75 +195,14 @@ export default function CollectionCard({ collection, className }: Props) {
/> />
{collection._count && collection._count.links} {collection._count && collection._count.links}
</div> </div>
<div className="flex items-center justify-end gap-1 text-neutral"> <div className="flex items-center justify-end gap-1 text-neutral w-full">
<FontAwesomeIcon icon={faCalendarDays} className="w-4 h-4" /> <FontAwesomeIcon icon={faCalendarDays} className="w-4 h-4" />
<p className="font-bold text-xs">{formattedDate}</p> <p className="font-bold text-xs w-full">{formattedDate}</p>
</div> </div>
</div> </div>
</div> </div>
</Link> </div>
</div> </Link>
{expandDropdown ? ( </div>
<Dropdown
points={{ x: expandDropdown.x, y: expandDropdown.y }}
items={[
permissions === true
? {
name: "Edit Collection Info",
onClick: () => {
collection &&
setModal({
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: collection,
});
setExpandDropdown(false);
},
}
: undefined,
{
name: permissions === true ? "Share/Collaborate" : "View Team",
onClick: () => {
collection &&
setModal({
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: collection,
defaultIndex: permissions === true ? 1 : 0,
});
setExpandDropdown(false);
},
},
{
name:
permissions === true ? "Delete Collection" : "Leave Collection",
onClick: () => {
collection &&
setModal({
modal: "COLLECTION",
state: true,
method: "UPDATE",
isOwner: permissions === true,
active: collection,
defaultIndex: permissions === true ? 2 : 1,
});
setExpandDropdown(false);
},
},
]}
onClickOutside={(e: Event) => {
const target = e.target as HTMLInputElement;
if (target.id !== "expand-dropdown" + collection.id)
setExpandDropdown(false);
}}
className="w-fit"
/>
) : null}
</>
); );
} }

View File

@ -10,7 +10,7 @@ type Props = {
export default function dashboardItem({ name, value, icon }: Props) { export default function dashboardItem({ name, value, icon }: Props) {
return ( return (
<div className="flex gap-4 items-end"> <div className="flex gap-4 items-end">
<div className="p-4 bg-primary bg-opacity-20 dark:bg-opacity-10 rounded-xl select-none"> <div className="p-4 bg-secondary/30 rounded-xl select-none">
<FontAwesomeIcon icon={icon} className="w-8 h-8 text-primary" /> <FontAwesomeIcon icon={icon} className="w-8 h-8 text-primary" />
</div> </div>
<div className="flex flex-col justify-center"> <div className="flex flex-col justify-center">

View File

@ -82,7 +82,7 @@ export default function LinkPreview({ link, className, settings }: Props) {
<div className="flex items-center gap-1 max-w-full w-fit my-1 hover:opacity-70 duration-100"> <div className="flex items-center gap-1 max-w-full w-fit my-1 hover:opacity-70 duration-100">
<FontAwesomeIcon <FontAwesomeIcon
icon={faFolder} icon={faFolder}
className="w-4 h-4 mt-1 drop-shadow text-sky-400" className="w-4 h-4 mt-1 drop-shadow text-primary"
/> />
<p className="truncate capitalize w-full">Landing Pages </p> <p className="truncate capitalize w-full">Landing Pages </p>
</div> </div>

View File

@ -93,7 +93,7 @@ export default function PreservedFormats() {
{link?.screenshotPath && link?.screenshotPath !== "pending" ? ( {link?.screenshotPath && link?.screenshotPath !== "pending" ? (
<div className="flex justify-between items-center pr-1 border border-neutral-content rounded-md"> <div className="flex justify-between items-center pr-1 border border-neutral-content rounded-md">
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<div className="text-white bg-sky-300 dark:bg-sky-600 p-2 rounded-l-md"> <div className="text-white bg-secondary p-2 rounded-l-md">
<FontAwesomeIcon icon={faFileImage} className="w-6 h-6" /> <FontAwesomeIcon icon={faFileImage} className="w-6 h-6" />
</div> </div>
@ -128,7 +128,7 @@ export default function PreservedFormats() {
{link?.pdfPath && link.pdfPath !== "pending" ? ( {link?.pdfPath && link.pdfPath !== "pending" ? (
<div className="flex justify-between items-center pr-1 border border-neutral-content rounded-md"> <div className="flex justify-between items-center pr-1 border border-neutral-content rounded-md">
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<div className="text-white bg-sky-300 dark:bg-sky-600 p-2 rounded-l-md"> <div className="text-white bg-secondary p-2 rounded-l-md">
<FontAwesomeIcon icon={faFilePdf} className="w-6 h-6" /> <FontAwesomeIcon icon={faFilePdf} className="w-6 h-6" />
</div> </div>

View File

@ -50,16 +50,18 @@ export default function Navbar() {
}; };
return ( return (
<div className="flex justify-between gap-2 items-center px-5 py-2 border-solid border-b-neutral-content border-b h-16"> <div className="flex justify-between gap-2 items-center px-5 py-2 border-solid border-b-neutral-content border-b">
<div <div
onClick={toggleSidebar} onClick={toggleSidebar}
className="inline-flex lg:hidden gap-1 items-center select-none cursor-pointer p-[0.687rem] text-neutral rounded-md duration-100 hover:bg-neutral-content" className="text-neutral btn btn-square btn-sm btn-ghost lg:hidden"
> >
<FontAwesomeIcon icon={faBars} className="w-5 h-5" /> <FontAwesomeIcon icon={faBars} className="w-5 h-5" />
</div> </div>
<SearchBar /> <SearchBar />
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div <ToggleDarkMode className="sm:inline-grid hidden" />
<button
onClick={() => { onClick={() => {
setModal({ setModal({
modal: "LINK", modal: "LINK",
@ -67,36 +69,28 @@ export default function Navbar() {
method: "CREATE", method: "CREATE",
}); });
}} }}
className="inline-flex gap-1 relative sm:w-[7.2rem] items-center font-semibold select-none cursor-pointer p-[0.687rem] sm:p-2 sm:px-3 rounded-md sm:rounded-full hover:bg-neutral-content dark:hover:bg-sky-800 sm:dark:hover:bg-sky-600 text-primary sm:text-white sm:bg-sky-700 sm:hover:bg-sky-600 duration-100 group" className="inline-flex sm:gap-1 relative sm:w-[5rem] items-center duration-100 group btn btn-accent text-white btn-sm"
> >
<FontAwesomeIcon <FontAwesomeIcon
icon={faPlus} icon={faPlus}
className="w-5 h-5 sm:group-hover:ml-9 sm:absolute duration-100" className="w-5 h-5 sm:group-hover:ml-5 sm:absolute duration-100 left-2"
/> />
<span className="hidden sm:block group-hover:opacity-0 text-right w-full duration-100"> <span className="hidden sm:block group-hover:opacity-0 text-right w-full duration-100">
New Link New
</span> </span>
</div> </button>
<ToggleDarkMode className="sm:flex hidden" />
<div className="relative"> <div className="relative">
<div <div
className="flex gap-1 group sm:hover:bg-neutral-content sm:hover:p-1 sm:hover:pr-2 duration-100 h-10 rounded-full items-center w-fit cursor-pointer" className="btn btn-circle btn-ghost"
onClick={() => setProfileDropdown(!profileDropdown)} onClick={() => setProfileDropdown(!profileDropdown)}
id="profile-dropdown" id="profile-dropdown"
> >
<ProfilePhoto <ProfilePhoto
src={account.image ? account.image : undefined} src={account.image ? account.image : undefined}
priority={true} priority={true}
className="sm:group-hover:h-8 sm:group-hover:w-8 duration-100 border-[3px]" className=""
/> />
<p
id="profile-dropdown"
className="leading-3 hidden sm:block select-none truncate max-w-[8rem] py-1"
>
{account.name}
</p>
</div> </div>
{profileDropdown ? ( {profileDropdown ? (
<Dropdown <Dropdown

View File

@ -6,11 +6,16 @@ import Image from "next/image";
type Props = { type Props = {
src?: string; src?: string;
className?: string; className?: string;
emptyImage?: boolean;
priority?: boolean; priority?: boolean;
name?: string;
}; };
export default function ProfilePhoto({ src, className, priority }: Props) { export default function ProfilePhoto({
src,
className,
priority,
name,
}: Props) {
const [image, setImage] = useState(""); const [image, setImage] = useState("");
useEffect(() => { useEffect(() => {
@ -23,25 +28,32 @@ export default function ProfilePhoto({ src, className, priority }: Props) {
}, [src]); }, [src]);
return !image ? ( return !image ? (
<div <div className={`avatar w-8 h-8 placeholder ${className || ""}`}>
className={`bg-sky-600 dark:bg-sky-600 text-white h-10 w-10 aspect-square shadow rounded-full border border-neutral-content flex items-center justify-center ${ <div className="bg-base-100 text-base-content rounded-full w-full h-full ring-2 ring-base-content">
className || "" {name ? (
}`} <span className="text-2xl capitalize">{name.slice(0, 1)}</span>
> ) : (
<FontAwesomeIcon icon={faUser} className="w-1/2 h-1/2 aspect-square" /> <FontAwesomeIcon
icon={faUser}
className="w-1/2 h-1/2 aspect-square"
/>
)}
</div>
</div> </div>
) : ( ) : (
<Image <div className={`avatar w-8 h-8 drop-shadow-md ${className || ""}`}>
alt="" <div className="rounded-full w-full h-full ring-2 ring-base-content">
src={image} <Image
height={112} alt=""
width={112} src={image}
priority={priority} height={112}
draggable={false} width={112}
onError={() => setImage("")} priority={priority}
className={`h-10 w-10 bg-sky-600 dark:bg-sky-600 shadow rounded-full aspect-square border border-neutral-content ${ draggable={false}
className || "" onError={() => setImage("")}
}`} className="aspect-square rounded-full"
/> />
</div>
</div>
); );
} }

View File

@ -57,7 +57,7 @@ export default function LinkCard({ link, count }: Props) {
<Link <Link
href={"/public/collections/20?q=" + e.name} href={"/public/collections/20?q=" + e.name}
key={i} key={i}
className="px-2 bg-sky-200 dark:bg-sky-900 text-xs rounded-3xl cursor-pointer hover:opacity-60 duration-100 truncate max-w-[19rem]" className="px-2 bg-secondary text-white text-xs rounded-md cursor-pointer hover:opacity-60 duration-100 truncate max-w-[19rem]"
> >
{e.name} {e.name}
</Link> </Link>

View File

@ -52,7 +52,7 @@ export default function PublicSearchBar({ placeHolder }: Props) {
); );
} }
}} }}
className="border text-sm border-neutral-content bg-base-200 focus:border-sky-300 dark:focus:border-sky-600 rounded-md pl-8 py-2 pr-2 w-44 sm:w-60 md:focus:w-80 duration-100 outline-none" className="border text-sm border-neutral-content bg-base-200 focus:border-primary rounded-md pl-8 py-2 pr-2 w-44 sm:w-60 md:focus:w-80 duration-100 outline-none"
/> />
</div> </div>
); );

View File

@ -36,7 +36,7 @@ export default function SearchBar() {
e.key === "Enter" && e.key === "Enter" &&
router.push("/search?q=" + encodeURIComponent(searchQuery)) router.push("/search?q=" + encodeURIComponent(searchQuery))
} }
className="border border-neutral-content bg-base-200 focus:border-primary rounded-md pl-10 py-2 pr-2 w-44 sm:w-60 md:focus:w-80 duration-100 outline-none" className="border border-neutral-content bg-base-200 focus:border-primary py-2 rounded-md pl-10 pr-2 w-44 sm:w-60 md:focus:w-80 duration-100 outline-none"
/> />
</div> </div>
); );

View File

@ -44,9 +44,9 @@ export default function SettingsSidebar({ className }: { className?: string }) {
<div <div
className={`${ className={`${
active === `/settings/account` active === `/settings/account`
? "bg-primary" ? "bg-secondary/30"
: "hover:bg-slate-500" : "hover:bg-neutral/20"
} duration-100 py-2 px-2 hover:bg-opacity-20 bg-opacity-20 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`} } duration-100 py-2 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`}
> >
<FontAwesomeIcon icon={faUser} className="w-6 h-6 text-primary" /> <FontAwesomeIcon icon={faUser} className="w-6 h-6 text-primary" />
@ -58,9 +58,9 @@ export default function SettingsSidebar({ className }: { className?: string }) {
<div <div
className={`${ className={`${
active === `/settings/appearance` active === `/settings/appearance`
? "bg-primary" ? "bg-secondary/30"
: "hover:bg-slate-500" : "hover:bg-neutral/20"
} duration-100 py-2 px-2 hover:bg-opacity-20 bg-opacity-20 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`} } duration-100 py-2 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`}
> >
<FontAwesomeIcon <FontAwesomeIcon
icon={faPalette} icon={faPalette}
@ -75,9 +75,9 @@ export default function SettingsSidebar({ className }: { className?: string }) {
<div <div
className={`${ className={`${
active === `/settings/archive` active === `/settings/archive`
? "bg-primary" ? "bg-secondary/30"
: "hover:bg-slate-500" : "hover:bg-neutral/20"
} duration-100 py-2 px-2 hover:bg-opacity-20 bg-opacity-20 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`} } duration-100 py-2 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`}
> >
<FontAwesomeIcon <FontAwesomeIcon
icon={faBoxArchive} icon={faBoxArchive}
@ -91,8 +91,10 @@ export default function SettingsSidebar({ className }: { className?: string }) {
<Link href="/settings/api"> <Link href="/settings/api">
<div <div
className={`${ className={`${
active === `/settings/api` ? "bg-primary" : "hover:bg-slate-500" active === `/settings/api`
} duration-100 py-2 px-2 hover:bg-opacity-20 bg-opacity-20 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`} ? "bg-secondary/30"
: "hover:bg-neutral/20"
} duration-100 py-2 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`}
> >
<FontAwesomeIcon icon={faKey} className="w-6 h-6 text-primary" /> <FontAwesomeIcon icon={faKey} className="w-6 h-6 text-primary" />
@ -104,9 +106,9 @@ export default function SettingsSidebar({ className }: { className?: string }) {
<div <div
className={`${ className={`${
active === `/settings/password` active === `/settings/password`
? "bg-primary" ? "bg-secondary/30"
: "hover:bg-slate-500" : "hover:bg-neutral/20"
} duration-100 py-2 px-2 hover:bg-opacity-20 bg-opacity-20 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`} } duration-100 py-2 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`}
> >
<FontAwesomeIcon icon={faLock} className="w-6 h-6 text-primary" /> <FontAwesomeIcon icon={faLock} className="w-6 h-6 text-primary" />
@ -119,9 +121,9 @@ export default function SettingsSidebar({ className }: { className?: string }) {
<div <div
className={`${ className={`${
active === `/settings/billing` active === `/settings/billing`
? "bg-primary" ? "bg-secondary/30"
: "hover:bg-slate-500" : "hover:bg-neutral/20"
} duration-100 py-2 px-2 hover:bg-opacity-20 bg-opacity-20 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`} } duration-100 py-2 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`}
> >
<FontAwesomeIcon <FontAwesomeIcon
icon={faCreditCard} icon={faCreditCard}
@ -144,7 +146,7 @@ export default function SettingsSidebar({ className }: { className?: string }) {
</Link> </Link>
<Link href="https://docs.linkwarden.app" target="_blank"> <Link href="https://docs.linkwarden.app" target="_blank">
<div <div
className={`hover:bg-slate-500 duration-100 py-2 px-2 hover:bg-opacity-20 bg-opacity-20 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`} className={`hover:bg-neutral/20 duration-100 py-2 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`}
> >
<FontAwesomeIcon <FontAwesomeIcon
icon={faCircleQuestion as any} icon={faCircleQuestion as any}
@ -157,7 +159,7 @@ export default function SettingsSidebar({ className }: { className?: string }) {
<Link href="https://github.com/linkwarden/linkwarden" target="_blank"> <Link href="https://github.com/linkwarden/linkwarden" target="_blank">
<div <div
className={`hover:bg-slate-500 duration-100 py-2 px-2 hover:bg-opacity-20 bg-opacity-20 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`} className={`hover:bg-neutral/20 duration-100 py-2 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`}
> >
<FontAwesomeIcon <FontAwesomeIcon
icon={faGithub as any} icon={faGithub as any}
@ -170,7 +172,7 @@ export default function SettingsSidebar({ className }: { className?: string }) {
<Link href="https://twitter.com/LinkwardenHQ" target="_blank"> <Link href="https://twitter.com/LinkwardenHQ" target="_blank">
<div <div
className={`hover:bg-slate-500 duration-100 py-2 px-2 hover:bg-opacity-20 bg-opacity-20 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`} className={`hover:bg-neutral/20 duration-100 py-2 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`}
> >
<FontAwesomeIcon <FontAwesomeIcon
icon={faXTwitter as any} icon={faXTwitter as any}
@ -183,7 +185,7 @@ export default function SettingsSidebar({ className }: { className?: string }) {
<Link href="https://fosstodon.org/@linkwarden" target="_blank"> <Link href="https://fosstodon.org/@linkwarden" target="_blank">
<div <div
className={`hover:bg-slate-500 duration-100 py-2 px-2 hover:bg-opacity-20 bg-opacity-20 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`} className={`hover:bg-neutral/20 duration-100 py-2 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`}
> >
<FontAwesomeIcon <FontAwesomeIcon
icon={faMastodon as any} icon={faMastodon as any}

View File

@ -61,8 +61,8 @@ export default function Sidebar({ className }: { className?: string }) {
<div <div
className={`${ className={`${
active === `/dashboard` active === `/dashboard`
? "bg-primary/20" ? "bg-secondary/30"
: "hover:bg-slate-500/20" : "hover:bg-neutral/20"
} duration-100 py-5 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8 capitalize`} } duration-100 py-5 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8 capitalize`}
> >
<FontAwesomeIcon <FontAwesomeIcon
@ -76,7 +76,7 @@ export default function Sidebar({ className }: { className?: string }) {
<Link href={`/links`}> <Link href={`/links`}>
<div <div
className={`${ className={`${
active === `/links` ? "bg-primary/20" : "hover:bg-slate-500/20" active === `/links` ? "bg-secondary/30" : "hover:bg-neutral/20"
} duration-100 py-5 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8 capitalize`} } duration-100 py-5 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8 capitalize`}
> >
<FontAwesomeIcon <FontAwesomeIcon
@ -91,8 +91,8 @@ export default function Sidebar({ className }: { className?: string }) {
<div <div
className={`${ className={`${
active === `/collections` active === `/collections`
? "bg-primary/20" ? "bg-secondary/30"
: "hover:bg-slate-500/20" : "hover:bg-neutral/20"
} duration-100 py-5 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8 capitalize`} } duration-100 py-5 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8 capitalize`}
> >
<FontAwesomeIcon <FontAwesomeIcon
@ -107,8 +107,8 @@ export default function Sidebar({ className }: { className?: string }) {
<div <div
className={`${ className={`${
active === `/links/pinned` active === `/links/pinned`
? "bg-primary/20" ? "bg-secondary/30"
: "hover:bg-slate-500/20" : "hover:bg-neutral/20"
} duration-100 py-5 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8 capitalize`} } duration-100 py-5 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8 capitalize`}
> >
<FontAwesomeIcon <FontAwesomeIcon
@ -154,8 +154,8 @@ export default function Sidebar({ className }: { className?: string }) {
<div <div
className={`${ className={`${
active === `/collections/${e.id}` active === `/collections/${e.id}`
? "bg-primary/20" ? "bg-secondary/30"
: "hover:bg-slate-500/20" : "hover:bg-neutral/20"
} duration-100 py-1 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8 capitalize`} } duration-100 py-1 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8 capitalize`}
> >
<FontAwesomeIcon <FontAwesomeIcon
@ -222,8 +222,8 @@ export default function Sidebar({ className }: { className?: string }) {
<div <div
className={`${ className={`${
active === `/tags/${e.id}` active === `/tags/${e.id}`
? "bg-primary/20" ? "bg-secondary/30"
: "hover:bg-slate-500/20" : "hover:bg-neutral/20"
} duration-100 py-1 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`} } duration-100 py-1 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`}
> >
<FontAwesomeIcon <FontAwesomeIcon

View File

@ -23,7 +23,9 @@ export default function ToggleDarkMode({ className }: Props) {
}, [theme]); }, [theme]);
return ( return (
<label className="swap swap-rotate className"> <label
className={`swap swap-rotate btn-square text-neutral btn btn-ghost btn-sm ${className}`}
>
<input <input
type="checkbox" type="checkbox"
onChange={handleToggle} onChange={handleToggle}
@ -32,21 +34,24 @@ export default function ToggleDarkMode({ className }: Props) {
/> />
{/* sun icon */} {/* sun icon */}
<svg <svg
className="swap-on fill-current w-10 h-10" className="swap-on fill-current w-5 h-5"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" fill="currentColor"
viewBox="0 0 16 16"
> >
<path d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z" /> <path d="M8 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8M8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0m0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13m8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5M3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8m10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0m-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707M4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z" />
</svg> </svg>
{/* moon icon */} {/* moon icon */}
<svg <svg
className="swap-off fill-current w-10 h-10" className="swap-off fill-current w-5 h-5"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" fill="currentColor"
viewBox="0 0 16 16"
> >
<path d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z" /> <path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278" />
</svg> </svg>
</label> </label>
); );

View File

@ -53,14 +53,14 @@ export default function SettingsLayout({ children }: Props) {
<div className="gap-2 inline-flex mr-3"> <div className="gap-2 inline-flex mr-3">
<div <div
onClick={toggleSidebar} onClick={toggleSidebar}
className="text-neutral btn btn-square btn-ghost" className="text-neutral btn btn-square btn-sm btn-ghost lg:hidden"
> >
<FontAwesomeIcon icon={faBars} className="w-5 h-5" /> <FontAwesomeIcon icon={faBars} className="w-5 h-5" />
</div> </div>
<Link <Link
href="/dashboard" href="/dashboard"
className="text-neutral btn btn-square btn-ghost" className="text-neutral btn btn-square btn-sm btn-ghost"
> >
<FontAwesomeIcon icon={faChevronLeft} className="w-5 h-5" /> <FontAwesomeIcon icon={faChevronLeft} className="w-5 h-5" />
</Link> </Link>

View File

@ -23,7 +23,6 @@ import React from "react";
import useModalStore from "@/store/modals"; import useModalStore from "@/store/modals";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { MigrationFormat, MigrationRequest } from "@/types/global"; import { MigrationFormat, MigrationRequest } from "@/types/global";
import ClickAwayHandler from "@/components/ClickAwayHandler";
import DashboardItem from "@/components/DashboardItem"; import DashboardItem from "@/components/DashboardItem";
export default function Dashboard() { export default function Dashboard() {
@ -63,8 +62,6 @@ export default function Dashboard() {
handleNumberOfLinksToShow(); handleNumberOfLinksToShow();
}, [width]); }, [width]);
const [importDropdown, setImportDropdown] = useState(false);
const importBookmarks = async (e: any, format: MigrationFormat) => { const importBookmarks = async (e: any, format: MigrationFormat) => {
const file: File = e.target.files[0]; const file: File = e.target.files[0];
@ -92,8 +89,6 @@ export default function Dashboard() {
toast.success("Imported the Bookmarks! Reloading the page..."); toast.success("Imported the Bookmarks! Reloading the page...");
setImportDropdown(false);
setTimeout(() => { setTimeout(() => {
location.reload(); location.reload();
}, 2000); }, 2000);
@ -200,83 +195,65 @@ export default function Dashboard() {
method: "CREATE", method: "CREATE",
}); });
}} }}
className="inline-flex gap-1 relative w-[11.4rem] items-center font-semibold select-none cursor-pointer p-2 px-3 rounded-md dark:hover:bg-sky-600 text-white bg-sky-700 hover:bg-sky-600 duration-100 group" className="inline-flex gap-1 relative w-[11rem] items-center btn btn-accent text-white group"
> >
<FontAwesomeIcon <FontAwesomeIcon
icon={faPlus} icon={faPlus}
className="w-5 h-5 group-hover:ml-[4.325rem] absolute duration-100" className="w-5 h-5 left-4 group-hover:ml-[4rem] absolute duration-100"
/> />
<span className="group-hover:opacity-0 text-right w-full duration-100"> <span className="group-hover:opacity-0 text-right w-full duration-100">
Create New Link Create New Item
</span> </span>
</div> </div>
<div className="relative"> <details className="dropdown">
<div <summary className="flex gap-2 text-sm btn btn-outline group">
onClick={() => setImportDropdown(!importDropdown)}
id="import-dropdown"
className="flex gap-2 select-none text-sm cursor-pointer p-2 px-3 rounded-md border dark:hover:border-sky-600 text-black border-black dark:text-white dark:border-white hover:border-primary hover:dark:border-primary hover:text-primary duration-100 group"
>
<FontAwesomeIcon <FontAwesomeIcon
icon={faFileImport} icon={faFileImport}
className="w-5 h-5 duration-100" className="w-5 h-5 duration-100"
id="import-dropdown" id="import-dropdown"
/> />
<span <span className="duration-100" id="import-dropdown">
className="text-right w-full duration-100"
id="import-dropdown"
>
Import Your Bookmarks Import Your Bookmarks
</span> </span>
</div> </summary>
{importDropdown ? ( <ul className="shadow menu dropdown-content z-[1] bg-base-200 border border-neutral-content rounded-box w-60">
<ClickAwayHandler <li>
onClickOutside={(e: Event) => { <label htmlFor="import-linkwarden-file" title="JSON File">
const target = e.target as HTMLInputElement; From Linkwarden
if (target.id !== "import-dropdown") <input
setImportDropdown(false); type="file"
}} name="photo"
className={`absolute top-10 left-0 w-52 py-1 shadow-md border border-neutral-content bg-base-200 rounded-md flex flex-col z-20`} id="import-linkwarden-file"
> accept=".json"
<div className="cursor-pointer rounded-md"> className="hidden"
<label onChange={(e) =>
htmlFor="import-linkwarden-file" importBookmarks(e, MigrationFormat.linkwarden)
title="JSON File" }
className="flex items-center gap-2 py-1 px-2 hover:bg-base-100 duration-100 cursor-pointer" />
> </label>
Linkwarden File... </li>
<input <li>
type="file" <label
name="photo" htmlFor="import-html-file"
id="import-linkwarden-file" title="HTML File"
accept=".json" className="w-full"
className="hidden" >
onChange={(e) => From Bookmarks HTML file
importBookmarks(e, MigrationFormat.linkwarden) <input
} type="file"
/> name="photo"
</label> id="import-html-file"
<label accept=".html"
htmlFor="import-html-file" className="hidden"
title="HTML File" onChange={(e) =>
className="flex items-center gap-2 py-1 px-2 hover:bg-base-100 duration-100 cursor-pointer" importBookmarks(e, MigrationFormat.htmlFile)
> }
Bookmarks HTML file... />
<input </label>
type="file" </li>
name="photo" </ul>
id="import-html-file" </details>
accept=".html"
className="hidden"
onChange={(e) =>
importBookmarks(e, MigrationFormat.htmlFile)
}
/>
</label>
</div>
</ClickAwayHandler>
) : null}
</div>
</div> </div>
</div> </div>
)} )}

View File

@ -128,7 +128,7 @@ export default function PublicCollections() {
{collection.name} {collection.name}
</p> </p>
<div className="flex gap-2 items-center mt-8 min-w-fit"> <div className="flex gap-2 items-center mt-8 min-w-fit">
<ToggleDarkMode className="w-8 h-8 flex" /> <ToggleDarkMode />
<Link href="https://linkwarden.app/" target="_blank"> <Link href="https://linkwarden.app/" target="_blank">
<Image <Image
src={`/icon.png`} src={`/icon.png`}
@ -136,33 +136,19 @@ export default function PublicCollections() {
height={551} height={551}
alt="Linkwarden" alt="Linkwarden"
title="Linkwarden" title="Linkwarden"
className="h-8 w-fit mx-auto" className="h-8 w-fit mx-auto rounded"
/> />
</Link> </Link>
</div> </div>
</div> </div>
<div> <div className="mt-3">
<div className={`min-w-[15rem]`}> <div className={`min-w-[15rem]`}>
<div <div className="flex justify-center sm:justify-end items-start w-fit">
onClick={() =>
setModal({
modal: "COLLECTION",
state: true,
method: "VIEW_TEAM",
isOwner: false,
active: collection,
defaultIndex: 0,
})
}
className="hover:opacity-80 duration-100 flex justify-center sm:justify-end items-start w-fit cursor-pointer"
>
{collectionOwner.id ? ( {collectionOwner.id ? (
<ProfilePhoto <ProfilePhoto
src={ src={collectionOwner.image || undefined}
collectionOwner.image ? collectionOwner.image : undefined className="w-7 h-7"
}
className={`w-8 h-8 border-2`}
/> />
) : undefined} ) : undefined}
{collection.members {collection.members
@ -172,24 +158,40 @@ export default function PublicCollections() {
<ProfilePhoto <ProfilePhoto
key={i} key={i}
src={e.user.image ? e.user.image : undefined} src={e.user.image ? e.user.image : undefined}
className={`w-8 h-8 border-2`} className="w-7 h-7"
/> />
); );
}) })
.slice(0, 3)} .slice(0, 4)}
{collection?.members.length && {collection?.members.length &&
collection.members.length - 3 > 0 ? ( collection.members.length - 3 > 0 ? (
<div className="w-8 h-8 min-w-[2rem] text-white text-sm flex items-center justify-center rounded-full border-2 bg-sky-600 dark:bg-sky-600 border-slate-200"> <div className={`avatar placeholder`}>
+{collection?.members?.length - 3} <div className="bg-base-100 text-base-content rounded-full w-8 h-8 ring-2 ring-base-content">
<span>+{collection.members.length - 3}</span>
</div>
</div> </div>
) : null} ) : null}
<p className="ml-2 mt-1 text-neutral"> <p className="ml-3 mt-1 text-neutral text-xs">
By {collectionOwner.name} By
{collection.members.length > 0 <span
? ` and ${collection.members.length} others` className="btn btn-ghost btn-xs p-1"
: undefined} onClick={() =>
. setModal({
modal: "COLLECTION",
state: true,
method: "VIEW_TEAM",
isOwner: false,
active: collection,
defaultIndex: 0,
})
}
>
{collectionOwner.name}
{collection.members.length > 0
? ` and ${collection.members.length} others`
: undefined}
</span>
</p> </p>
</div> </div>
</div> </div>

View File

@ -155,7 +155,6 @@ body {
/* Theme */ /* Theme */
/* react-select */
@layer components { @layer components {
.react-select-container .react-select__control { .react-select-container .react-select__control {
@apply bg-base-200 dark:hover:border-neutral-500 border-red-500 border; @apply bg-base-200 dark:hover:border-neutral-500 border-red-500 border;

View File

@ -7,8 +7,8 @@ module.exports = {
{ {
light: { light: {
primary: "#0ea5e9", primary: "#0ea5e9",
secondary: "#22d3ee", secondary: "#06b6d4",
accent: "#4f46e5", accent: "#6366f1",
neutral: "#6b7280", neutral: "#6b7280",
"neutral-content": "#d1d5db", "neutral-content": "#d1d5db",
"base-100": "#ffffff", "base-100": "#ffffff",
@ -22,9 +22,9 @@ module.exports = {
}, },
{ {
dark: { dark: {
primary: "#38bdf8", primary: "#0ea5e9",
secondary: "#0284c7", secondary: "#0e7490",
accent: "#818cf8", accent: "#6366f1",
neutral: "#9ca3af", neutral: "#9ca3af",
"neutral-content": "#404040", "neutral-content": "#404040",
"base-100": "#171717", "base-100": "#171717",