many visual changes and improvements
This commit is contained in:
parent
6a4f21fc0a
commit
a9d7303359
|
@ -85,7 +85,7 @@ export default function ({ link, count }: Props) {
|
|||
<div className="flex gap-3 items-center flex-wrap my-3">
|
||||
<Link href={`/collections/${link.collection.id}`}>
|
||||
<div className="flex items-center gap-1 cursor-pointer hover:opacity-60 duration-100">
|
||||
<FontAwesomeIcon icon={faFolder} className="w-4 text-sky-300" />
|
||||
<FontAwesomeIcon icon={faFolder} className="w-4 text-sky-500" />
|
||||
<p className="text-sky-900">{link.collection.name}</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
|
|
@ -24,7 +24,7 @@ export default function Dropdown({ onClickOutside, className, items }: Props) {
|
|||
return (
|
||||
<ClickAwayHandler
|
||||
onClickOutside={onClickOutside}
|
||||
className={`${className} border border-sky-100 py-1 shadow-md bg-gray-50 rounded-md flex flex-col z-10`}
|
||||
className={`${className} py-1 shadow-md bg-gray-50 rounded-md flex flex-col z-10`}
|
||||
>
|
||||
{items.map((e, i) => {
|
||||
const inner = (
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
import React, { SetStateAction } from "react";
|
||||
import ClickAwayHandler from "./ClickAwayHandler";
|
||||
import Checkbox from "./Checkbox";
|
||||
import { SearchSettings } from "@/types/global";
|
||||
|
||||
type Props = {
|
||||
setFilterDropdown: (value: SetStateAction<boolean>) => void;
|
||||
toggleCheckbox: (
|
||||
name: "name" | "title" | "url" | "collection" | "tags"
|
||||
) => void;
|
||||
searchSettings: SearchSettings;
|
||||
};
|
||||
|
||||
export default function FilterSearchDropdown({
|
||||
setFilterDropdown,
|
||||
toggleCheckbox,
|
||||
searchSettings,
|
||||
}: Props) {
|
||||
return (
|
||||
<ClickAwayHandler
|
||||
onClickOutside={(e: Event) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
if (target.id !== "filter-dropdown") setFilterDropdown(false);
|
||||
}}
|
||||
className="absolute top-8 right-0 shadow-md bg-gray-50 rounded-md p-2 z-20 w-40"
|
||||
>
|
||||
<p className="mb-2 text-sky-900 text-center font-semibold">Filter by</p>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Checkbox
|
||||
label="Name"
|
||||
state={searchSettings.filter.name}
|
||||
onClick={() => toggleCheckbox("name")}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Link"
|
||||
state={searchSettings.filter.url}
|
||||
onClick={() => toggleCheckbox("url")}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Title"
|
||||
state={searchSettings.filter.title}
|
||||
onClick={() => toggleCheckbox("title")}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Collection"
|
||||
state={searchSettings.filter.collection}
|
||||
onClick={() => toggleCheckbox("collection")}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Tags"
|
||||
state={searchSettings.filter.tags}
|
||||
onClick={() => toggleCheckbox("tags")}
|
||||
/>
|
||||
</div>
|
||||
</ClickAwayHandler>
|
||||
);
|
||||
}
|
|
@ -78,22 +78,26 @@ export default function ({ link, count }: Props) {
|
|||
<div className="flex justify-between gap-5 w-full h-full z-0">
|
||||
<div className="flex flex-col justify-between">
|
||||
<div className="flex items-baseline gap-1">
|
||||
<p className="text-sm text-sky-300 font-bold">{count + 1}.</p>
|
||||
<p className="text-sm text-sky-400 font-bold">{count + 1}.</p>
|
||||
<p className="text-lg text-sky-600 font-bold">{link.name}</p>
|
||||
</div>
|
||||
<p className="text-sky-400 text-sm font-medium">{link.title}</p>
|
||||
<div className="flex gap-3 items-center flex-wrap my-3">
|
||||
<Link href={`/collections/${link.collection.id}`}>
|
||||
<div className="flex items-center gap-1 cursor-pointer hover:opacity-60 duration-100">
|
||||
<FontAwesomeIcon icon={faFolder} className="w-4 text-sky-300" />
|
||||
<FontAwesomeIcon
|
||||
icon={faFolder}
|
||||
className="w-4 h-4 mt-1"
|
||||
style={{ color: link.collection.color }}
|
||||
/>
|
||||
<p className="text-sky-900">{link.collection.name}</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<div className="flex gap-1 items-center flex-wrap">
|
||||
<div className="flex gap-1 items-center flex-wrap mt-1">
|
||||
{link.tags.map((e, i) => (
|
||||
<Link key={i} href={`/tags/${e.id}`}>
|
||||
<p className="px-2 py-1 bg-sky-200 text-sky-700 text-xs rounded-3xl cursor-pointer hover:bg-sky-100 duration-100">
|
||||
<p className="px-2 py-1 bg-sky-200 text-sky-700 text-xs rounded-3xl cursor-pointer hover:opacity-60 duration-100">
|
||||
{e.name}
|
||||
</p>
|
||||
</Link>
|
||||
|
|
|
@ -34,7 +34,7 @@ export default function ChangePassword({
|
|||
<div className="max-w-sm mx-auto flex flex-col gap-3">
|
||||
<p className="text-xl text-sky-500 mb-2 text-center">Change Password</p>
|
||||
|
||||
<p className="text-sm font-bold text-sky-300">Old Password</p>
|
||||
<p className="text-sm text-sky-500">Old Password</p>
|
||||
|
||||
<input
|
||||
value={oldPassword}
|
||||
|
@ -42,7 +42,7 @@ export default function ChangePassword({
|
|||
type="text"
|
||||
className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
|
||||
/>
|
||||
<p className="text-sm font-bold text-sky-300">New Password</p>
|
||||
<p className="text-sm text-sky-500">New Password</p>
|
||||
|
||||
<input
|
||||
value={newPassword1}
|
||||
|
@ -50,7 +50,7 @@ export default function ChangePassword({
|
|||
type="text"
|
||||
className="w-full rounded-md p-3 mx-auto border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
|
||||
/>
|
||||
<p className="text-sm font-bold text-sky-300">Re-enter New Password</p>
|
||||
<p className="text-sm text-sky-500">Re-enter New Password</p>
|
||||
|
||||
<input
|
||||
value={newPassword2}
|
||||
|
@ -60,7 +60,7 @@ export default function ChangePassword({
|
|||
/>
|
||||
|
||||
<div
|
||||
className="mx-auto mt-2 text-white flex items-center gap-2 py-2 px-5 rounded-md select-none font-bold duration-100 bg-sky-500 hover:bg-sky-400 cursor-pointer"
|
||||
className="mx-auto mt-2 text-white flex items-center gap-2 py-2 px-5 rounded-md select-none duration-100 bg-sky-500 hover:bg-sky-400 cursor-pointer"
|
||||
onClick={submit}
|
||||
>
|
||||
Save
|
||||
|
|
|
@ -47,7 +47,7 @@ export default function CollectionInfo({
|
|||
|
||||
<div className="flex flex-col sm:flex-row gap-3">
|
||||
<div className="w-full">
|
||||
<p className="text-sm font-bold text-sky-300 mb-2">
|
||||
<p className="text-sm text-sky-500 mb-2">
|
||||
Name
|
||||
<RequiredBadge />
|
||||
</p>
|
||||
|
@ -63,9 +63,7 @@ export default function CollectionInfo({
|
|||
/>
|
||||
<div className="color-picker flex justify-between">
|
||||
<div className="flex flex-col justify-between items-center w-32">
|
||||
<p className="text-sm w-full font-bold text-sky-300 mb-2">
|
||||
Icon Color
|
||||
</p>
|
||||
<p className="text-sm w-full text-sky-500 mb-2">Icon Color</p>
|
||||
<div style={{ color: collection.color }}>
|
||||
<FontAwesomeIcon
|
||||
icon={faFolder}
|
||||
|
@ -73,9 +71,9 @@ export default function CollectionInfo({
|
|||
/>
|
||||
</div>
|
||||
<div
|
||||
className="py-1 px-2 rounded-md text-xs font-semibold cursor-pointer text-gray-500 hover:bg-slate-200 duration-100"
|
||||
className="py-1 px-2 rounded-md text-xs font-semibold cursor-pointer text-sky-500 hover:bg-slate-200 duration-100"
|
||||
onClick={() =>
|
||||
setCollection({ ...collection, color: "#7dd3fc" })
|
||||
setCollection({ ...collection, color: "#0ea5e9" })
|
||||
}
|
||||
>
|
||||
Reset
|
||||
|
@ -90,7 +88,7 @@ export default function CollectionInfo({
|
|||
</div>
|
||||
|
||||
<div className="w-full">
|
||||
<p className="text-sm font-bold text-sky-300 mb-2">Description</p>
|
||||
<p className="text-sm text-sky-500 mb-2">Description</p>
|
||||
<textarea
|
||||
className="w-full h-[11.4rem] resize-none border rounded-md duration-100 bg-white p-3 outline-none border-sky-100 focus:border-sky-500"
|
||||
placeholder="The purpose of this Collection..."
|
||||
|
|
|
@ -77,7 +77,7 @@ export default function TeamManagement({
|
|||
Sharing & Collaboration
|
||||
</p>
|
||||
|
||||
<p className="text-sm font-bold text-sky-300">Make Public</p>
|
||||
<p className="text-sm text-sky-500">Make Public</p>
|
||||
|
||||
<Checkbox
|
||||
label="Make this a public collection."
|
||||
|
@ -93,7 +93,9 @@ export default function TeamManagement({
|
|||
|
||||
{collection.isPublic ? (
|
||||
<div>
|
||||
<p className="mb-2 text-gray-500">Public Link (Click to copy)</p>
|
||||
<p className="text-sm text-sky-500 mb-2">
|
||||
Public Link (Click to copy)
|
||||
</p>
|
||||
<div
|
||||
onClick={() => {
|
||||
try {
|
||||
|
@ -113,7 +115,7 @@ export default function TeamManagement({
|
|||
|
||||
<hr />
|
||||
|
||||
<p className="text-sm font-bold text-sky-300">Member Management</p>
|
||||
<p className="text-sm text-sky-500">Member Management</p>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
|
@ -202,10 +204,10 @@ export default function TeamManagement({
|
|||
</div>
|
||||
<div className="flex sm:block items-center gap-5">
|
||||
<div>
|
||||
<p className="font-bold text-sm text-gray-500">
|
||||
<p className="font-bold text-sm text-sky-500">
|
||||
Permissions
|
||||
</p>
|
||||
<p className="text-xs text-gray-400 mb-2">
|
||||
<p className="text-xs text-gray-500 mb-2">
|
||||
(Click to toggle.)
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -115,7 +115,7 @@ export default function EditLink({
|
|||
|
||||
<div className="grid sm:grid-cols-2 gap-3">
|
||||
<div>
|
||||
<p className="text-sm font-bold text-sky-300 mb-2">
|
||||
<p className="text-sm text-sky-500 mb-2">
|
||||
Name
|
||||
<RequiredBadge />
|
||||
</p>
|
||||
|
@ -130,7 +130,7 @@ export default function EditLink({
|
|||
|
||||
{method === "CREATE" ? (
|
||||
<div>
|
||||
<p className="text-sm font-bold text-sky-300 mb-2">
|
||||
<p className="text-sm text-sky-500 mb-2">
|
||||
URL
|
||||
<RequiredBadge />
|
||||
</p>
|
||||
|
@ -145,7 +145,7 @@ export default function EditLink({
|
|||
) : null}
|
||||
|
||||
<div>
|
||||
<p className="text-sm font-bold text-sky-300 mb-2">
|
||||
<p className="text-sm text-sky-500 mb-2">
|
||||
Collection
|
||||
<RequiredBadge />
|
||||
</p>
|
||||
|
@ -167,7 +167,7 @@ export default function EditLink({
|
|||
</div>
|
||||
|
||||
<div className={method === "UPDATE" ? "sm:col-span-2" : ""}>
|
||||
<p className="text-sm font-bold text-sky-300 mb-2">Tags</p>
|
||||
<p className="text-sm text-sky-500 mb-2">Tags</p>
|
||||
<TagSelection
|
||||
onChange={setTags}
|
||||
defaultValue={link.tags.map((e) => {
|
||||
|
|
|
@ -95,12 +95,10 @@ export default function UserSettings({ toggleSettingsModal }: Props) {
|
|||
<div className="flex flex-col gap-3 sm:w-[35rem] w-80">
|
||||
<p className="text-xl text-sky-500 mb-2 text-center">Settings</p>
|
||||
|
||||
<p className="text-sky-600">Profile Settings</p>
|
||||
|
||||
<div className="grid sm:grid-cols-2 gap-3 auto-rows-auto">
|
||||
<div className="flex flex-col gap-3">
|
||||
<div>
|
||||
<p className="text-sm font-bold text-sky-300 mb-2">Display Name</p>
|
||||
<p className="text-sm text-sky-500 mb-2">Display Name</p>
|
||||
<input
|
||||
type="text"
|
||||
value={user.name}
|
||||
|
@ -110,7 +108,7 @@ export default function UserSettings({ toggleSettingsModal }: Props) {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-sm font-bold text-sky-300 mb-2">Email</p>
|
||||
<p className="text-sm text-sky-500 mb-2">Email</p>
|
||||
<input
|
||||
type="text"
|
||||
value={user.email}
|
||||
|
@ -120,7 +118,7 @@ export default function UserSettings({ toggleSettingsModal }: Props) {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-sm font-bold text-sky-300 mb-2">Password</p>
|
||||
<p className="text-sm text-sky-500 mb-2">Password</p>
|
||||
|
||||
<div className="w-fit" onClick={togglePasswordFormModal}>
|
||||
<div className="border border-sky-100 rounded-md bg-white px-2 py-1 text-center select-none cursor-pointer text-sky-900 duration-100 hover:border-sky-500">
|
||||
|
@ -139,7 +137,7 @@ export default function UserSettings({ toggleSettingsModal }: Props) {
|
|||
</div>
|
||||
|
||||
<div className="sm:row-span-2 sm:justify-self-center mb-3">
|
||||
<p className="text-sm font-bold text-sky-300 mb-2 sm:text-center">
|
||||
<p className="text-sm text-sky-500 mb-2 sm:text-center">
|
||||
Profile Photo
|
||||
</p>
|
||||
<div className="w-28 h-28 flex items-center justify-center border border-sky-100 rounded-full relative">
|
||||
|
@ -202,7 +200,7 @@ export default function UserSettings({ toggleSettingsModal }: Props) {
|
|||
|
||||
<hr />
|
||||
|
||||
<p className="text-sky-600">Privacy Settings</p>
|
||||
<p className="text-sm text-sky-500 mb-2">Profile Visibility</p>
|
||||
|
||||
<Checkbox
|
||||
label="Make profile private"
|
||||
|
@ -211,19 +209,16 @@ export default function UserSettings({ toggleSettingsModal }: Props) {
|
|||
onClick={() => setUser({ ...user, isPrivate: !user.isPrivate })}
|
||||
/>
|
||||
|
||||
<p className="text-gray-500 text-sm mb-3">
|
||||
<p className="text-gray-500 text-sm">
|
||||
This will limit who can find and add you to other Collections.
|
||||
</p>
|
||||
|
||||
{user.isPrivate ? (
|
||||
<div>
|
||||
<p className="text-sm font-bold text-sky-300 mb-2">
|
||||
Whitelisted Users
|
||||
</p>
|
||||
<p className="text-sm text-sky-500 mb-2">Whitelisted Users</p>
|
||||
<p className="text-gray-500 text-sm mb-3">
|
||||
Please provide the Email addresses of the users you wish to grant
|
||||
visibility to your profile. Separate by comma. Users not included
|
||||
will be unable to view your profile.
|
||||
visibility to your profile. Separated by comma.
|
||||
</p>
|
||||
<textarea
|
||||
className="w-full resize-none border rounded-md duration-100 bg-white p-2 outline-none border-sky-100 focus:border-sky-500"
|
||||
|
|
|
@ -51,7 +51,7 @@ export default function ({ link, count }: Props) {
|
|||
<div className="flex justify-between items-center gap-5 w-full h-full z-0">
|
||||
<div className="flex flex-col justify-between">
|
||||
<div className="flex items-baseline gap-1">
|
||||
<p className="text-sm text-sky-300 font-bold">{count + 1}.</p>
|
||||
<p className="text-sm text-sky-400 font-bold">{count + 1}.</p>
|
||||
<p className="text-lg text-sky-600 font-bold">{link.name}</p>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ import {
|
|||
faBookmark,
|
||||
faChartSimple,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import SidebarItem from "./SidebarItem";
|
||||
import useTagStore from "@/store/tags";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
|
@ -23,13 +22,13 @@ export default function ({ className }: { className?: string }) {
|
|||
|
||||
useEffect(() => {
|
||||
setActive(router.asPath);
|
||||
}, [router]);
|
||||
}, [router, collections]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`bg-gray-100 h-screen w-64 xl:w-80 overflow-y-auto border-solid border-r-sky-100 px-2 border z-20 ${className}`}
|
||||
>
|
||||
<p className="p-3 text-sky-500 font-bold text-2xl my-2 leading-4">
|
||||
<p className="p-2 text-sky-500 font-bold text-2xl my-2 leading-4">
|
||||
Linkwarden
|
||||
</p>
|
||||
|
||||
|
@ -38,45 +37,29 @@ export default function ({ className }: { className?: string }) {
|
|||
<div
|
||||
className={`${
|
||||
active === "/dashboard"
|
||||
? "bg-sky-500"
|
||||
? "bg-sky-200"
|
||||
: "hover:bg-slate-200 bg-gray-100"
|
||||
} outline-sky-100 outline-1 duration-100 py-1 px-2 rounded-md cursor-pointer flex items-center gap-2`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faChartSimple}
|
||||
className={`w-4 h-4 ${
|
||||
active === "/dashboard" ? "text-white" : "text-sky-300"
|
||||
}`}
|
||||
className={`w-6 h-6 drop-shadow text-sky-500`}
|
||||
/>
|
||||
<p
|
||||
className={`${
|
||||
active === "/dashboard" ? "text-white" : "text-sky-900"
|
||||
}`}
|
||||
>
|
||||
Dashboard
|
||||
</p>
|
||||
<p className="text-sky-600">Dashboard</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<Link href="/collections">
|
||||
<div
|
||||
className={`${
|
||||
active === "/collections" ? "bg-sky-500" : "hover:bg-slate-200"
|
||||
active === "/collections" ? "bg-sky-200" : "hover:bg-slate-200"
|
||||
} outline-sky-100 outline-1 duration-100 py-1 px-2 rounded-md cursor-pointer flex items-center gap-2`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faBox}
|
||||
className={`w-4 h-4 ${
|
||||
active === "/collections" ? "text-white" : "text-sky-300"
|
||||
}`}
|
||||
className={`w-6 h-6 drop-shadow text-sky-500`}
|
||||
/>
|
||||
<p
|
||||
className={`${
|
||||
active === "/collections" ? "text-white" : "text-sky-900"
|
||||
}`}
|
||||
>
|
||||
All Collections
|
||||
</p>
|
||||
<p className="text-sky-600">All Collections</p>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
|
@ -84,60 +67,71 @@ export default function ({ className }: { className?: string }) {
|
|||
<div
|
||||
className={`${
|
||||
active === "/links"
|
||||
? "bg-sky-500"
|
||||
? "bg-sky-200"
|
||||
: "hover:bg-slate-200 bg-gray-100"
|
||||
} outline-sky-100 outline-1 duration-100 py-1 px-2 rounded-md cursor-pointer flex items-center gap-2`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faBookmark}
|
||||
className={`w-4 h-4 ${
|
||||
active === "/links" ? "text-white" : "text-sky-300"
|
||||
}`}
|
||||
className={`w-6 h-6 drop-shadow text-sky-500`}
|
||||
/>
|
||||
<p
|
||||
className={`${
|
||||
active === "/links" ? "text-white" : "text-sky-900"
|
||||
}`}
|
||||
>
|
||||
All Links
|
||||
</p>
|
||||
<p className="text-sky-600">All Links</p>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="text-gray-500 mt-5">
|
||||
<p className="text-sm mb-2 pl-3 font-semibold">Collections</p>
|
||||
<p className="text-sm mb-2 pl-2 font-semibold">Collections</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
{collections
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((e, i) => {
|
||||
return (
|
||||
<SidebarItem
|
||||
key={i}
|
||||
text={e.name}
|
||||
icon={<FontAwesomeIcon icon={faFolder} />}
|
||||
iconColor={e.color}
|
||||
path={`/collections/${e.id}`}
|
||||
className="capitalize"
|
||||
/>
|
||||
<Link key={i} href={`/collections/${e.id}`}>
|
||||
<div
|
||||
className={`${
|
||||
active === `/collections/${e.id}`
|
||||
? "bg-sky-200"
|
||||
: "hover:bg-slate-200 bg-gray-100"
|
||||
} duration-100 py-1 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8 capitalize`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faFolder}
|
||||
className="w-6 h-6 drop-shadow"
|
||||
style={{ color: e.color }}
|
||||
/>
|
||||
|
||||
<p className="text-sky-600 truncate w-4/6">{e.name}</p>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="text-gray-500 mt-5">
|
||||
<p className="text-sm mb-2 pl-3 font-semibold">Tags</p>
|
||||
<p className="text-sm mb-2 pl-2 font-semibold">Tags</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
{tags
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map((e, i) => {
|
||||
return (
|
||||
<SidebarItem
|
||||
key={i}
|
||||
text={e.name}
|
||||
icon={<FontAwesomeIcon icon={faHashtag} />}
|
||||
path={`/tags/${e.id}`}
|
||||
/>
|
||||
<Link key={i} href={`/tags/${e.id}`}>
|
||||
<div
|
||||
className={`${
|
||||
active === `/tags/${e.id}`
|
||||
? "bg-sky-200"
|
||||
: "hover:bg-slate-200 bg-gray-100"
|
||||
} duration-100 py-1 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md h-8`}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faHashtag}
|
||||
className="w-4 h-4 text-sky-500 mt-1"
|
||||
/>
|
||||
|
||||
<p className="text-sky-600 truncate w-4/6">{e.name}</p>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
|
@ -1,49 +0,0 @@
|
|||
import Link from "next/link";
|
||||
import React, { ReactElement, useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
interface SidebarItemProps {
|
||||
text: string;
|
||||
icon: ReactElement;
|
||||
path: string;
|
||||
className?: string;
|
||||
iconColor?: string;
|
||||
}
|
||||
|
||||
export default function ({
|
||||
text,
|
||||
icon,
|
||||
path,
|
||||
className,
|
||||
iconColor,
|
||||
}: SidebarItemProps) {
|
||||
const router = useRouter();
|
||||
const [active, setActive] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.asPath === path) setActive(true);
|
||||
else setActive(false);
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
<Link href={path}>
|
||||
<div
|
||||
className={`${
|
||||
active ? "bg-sky-500" : "hover:bg-slate-200 bg-gray-100"
|
||||
} duration-100 py-1 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md ${className}`}
|
||||
>
|
||||
{React.cloneElement(icon, {
|
||||
className: "w-4 h-4",
|
||||
style: {
|
||||
color: active ? "white" : iconColor ? iconColor : "#7dd3fc",
|
||||
},
|
||||
})}
|
||||
<p
|
||||
className={`${active ? "text-white" : "text-sky-900"} truncate w-4/6`}
|
||||
>
|
||||
{text}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
|
@ -19,7 +19,7 @@ export default function SortLinkDropdown({
|
|||
const target = e.target as HTMLInputElement;
|
||||
if (target.id !== "sort-dropdown") toggleSortDropdown();
|
||||
}}
|
||||
className="absolute top-8 right-0 shadow-md bg-gray-50 rounded-md p-2 z-10 border border-sky-100 w-48"
|
||||
className="absolute top-8 right-0 shadow-md bg-gray-50 rounded-md p-2 z-10 w-48"
|
||||
>
|
||||
<p className="mb-2 text-sky-900 text-center font-semibold">Sort by</p>
|
||||
<div className="flex flex-col gap-2">
|
||||
|
|
|
@ -4,6 +4,7 @@ export default async function (userId: number) {
|
|||
// remove empty tags
|
||||
await prisma.tag.deleteMany({
|
||||
where: {
|
||||
ownerId: userId,
|
||||
links: {
|
||||
none: {},
|
||||
},
|
||||
|
|
|
@ -9,3 +9,10 @@ export const prisma =
|
|||
});
|
||||
|
||||
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
|
||||
|
||||
if (process.env.NODE_ENV !== "production")
|
||||
prisma.$on("query" as any, (e: any) => {
|
||||
console.log("Query: " + e.query);
|
||||
console.log("Params: " + e.params);
|
||||
console.log("\x1b[31m", `Duration: ${e.duration}ms`, "\x1b[0m"); // For benchmarking
|
||||
});
|
||||
|
|
|
@ -46,7 +46,6 @@ export default async function (req: NextApiRequest, res: NextApiResponse) {
|
|||
`data/uploads/avatar/${queryId}.jpg`
|
||||
);
|
||||
|
||||
console.log(filePath);
|
||||
const file = fs.existsSync(filePath)
|
||||
? fs.readFileSync(filePath)
|
||||
: "File not found.";
|
||||
|
|
|
@ -85,7 +85,7 @@ export default function () {
|
|||
<div className="flex gap-2">
|
||||
<FontAwesomeIcon
|
||||
icon={faBox}
|
||||
className="sm:w-8 sm:h-8 w-6 h-6 mt-2 text-sky-300"
|
||||
className="sm:w-8 sm:h-8 w-6 h-6 mt-2 text-sky-500"
|
||||
/>
|
||||
<p className="sm:text-4xl text-3xl capitalize bg-gradient-to-tr from-sky-500 to-slate-400 bg-clip-text text-transparent font-bold">
|
||||
All Collections
|
||||
|
@ -218,7 +218,7 @@ export default function () {
|
|||
activeCollection={{
|
||||
name: "",
|
||||
description: "",
|
||||
color: "#7dd3fc",
|
||||
color: "#0ea5e9",
|
||||
isPublic: false,
|
||||
ownerId: session.data?.user.id as number,
|
||||
members: [],
|
||||
|
|
|
@ -39,7 +39,7 @@ export default function () {
|
|||
<div className="flex gap-2">
|
||||
<FontAwesomeIcon
|
||||
icon={faChartSimple}
|
||||
className="sm:w-8 sm:h-8 w-6 h-6 mt-2 text-sky-300"
|
||||
className="sm:w-8 sm:h-8 w-6 h-6 mt-2 text-sky-500"
|
||||
/>
|
||||
<p className="sm:text-4xl text-3xl capitalize bg-gradient-to-tr from-sky-500 to-slate-400 bg-clip-text text-transparent font-bold">
|
||||
Dashboard
|
||||
|
|
|
@ -55,7 +55,7 @@ export default function Links() {
|
|||
<div className="flex gap-2">
|
||||
<FontAwesomeIcon
|
||||
icon={faBookmark}
|
||||
className="sm:w-8 sm:h-8 w-6 h-6 mt-2 text-sky-300"
|
||||
className="sm:w-8 sm:h-8 w-6 h-6 mt-2 text-sky-500"
|
||||
/>
|
||||
<p className="sm:text-4xl text-3xl capitalize bg-gradient-to-tr from-sky-500 to-slate-400 bg-clip-text text-transparent font-bold">
|
||||
All Links
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import Checkbox from "@/components/Checkbox";
|
||||
import ClickAwayHandler from "@/components/ClickAwayHandler";
|
||||
import FilterSearchDropdown from "@/components/FilterSearchDropdown";
|
||||
import LinkList from "@/components/LinkList";
|
||||
import RadioButton from "@/components/RadioButton";
|
||||
import SortLinkDropdown from "@/components/SortLinkDropdown";
|
||||
import MainLayout from "@/layouts/MainLayout";
|
||||
import useLinkStore from "@/store/links";
|
||||
|
@ -17,8 +15,7 @@ export default function Links() {
|
|||
const [sortDropdown, setSortDropdown] = useState(false);
|
||||
const [sortBy, setSortBy] = useState("Name (A-Z)");
|
||||
const [sortedLinks, setSortedLinks] = useState(links);
|
||||
const { searchSettings, toggleCheckbox, setSearchSettings, setSearchQuery } =
|
||||
useSearchSettingsStore();
|
||||
const { searchSettings, toggleCheckbox } = useSearchSettingsStore();
|
||||
|
||||
const handleSortChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setSortBy(event.target.value);
|
||||
|
@ -93,45 +90,11 @@ export default function Links() {
|
|||
</div>
|
||||
|
||||
{filterDropdown ? (
|
||||
<ClickAwayHandler
|
||||
onClickOutside={(e: Event) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
if (target.id !== "filter-dropdown")
|
||||
setFilterDropdown(false);
|
||||
}}
|
||||
className="absolute top-8 right-0 shadow-md bg-gray-50 rounded-md p-2 z-20 border border-sky-100 w-40"
|
||||
>
|
||||
<p className="mb-2 text-sky-900 text-center font-semibold">
|
||||
Filter by
|
||||
</p>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Checkbox
|
||||
label="Name"
|
||||
state={searchSettings.filter.name}
|
||||
onClick={() => toggleCheckbox("name")}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Link"
|
||||
state={searchSettings.filter.url}
|
||||
onClick={() => toggleCheckbox("url")}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Title"
|
||||
state={searchSettings.filter.title}
|
||||
onClick={() => toggleCheckbox("title")}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Collection"
|
||||
state={searchSettings.filter.collection}
|
||||
onClick={() => toggleCheckbox("collection")}
|
||||
/>
|
||||
<Checkbox
|
||||
label="Tags"
|
||||
state={searchSettings.filter.tags}
|
||||
onClick={() => toggleCheckbox("tags")}
|
||||
/>
|
||||
</div>
|
||||
</ClickAwayHandler>
|
||||
<FilterSearchDropdown
|
||||
setFilterDropdown={setFilterDropdown}
|
||||
searchSettings={searchSettings}
|
||||
toggleCheckbox={toggleCheckbox}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
import FilterSearchDropdown from "@/components/FilterSearchDropdown";
|
||||
import LinkList from "@/components/LinkList";
|
||||
import SortLinkDropdown from "@/components/SortLinkDropdown";
|
||||
import MainLayout from "@/layouts/MainLayout";
|
||||
import useLinkStore from "@/store/links";
|
||||
import useSearchSettingsStore from "@/store/search";
|
||||
import { faFilter, faSearch, faSort } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { ChangeEvent, useEffect, useState } from "react";
|
||||
|
||||
export default function Links() {
|
||||
const { links } = useLinkStore();
|
||||
|
||||
const [filterDropdown, setFilterDropdown] = useState(false);
|
||||
const [sortDropdown, setSortDropdown] = useState(false);
|
||||
const [sortBy, setSortBy] = useState("Name (A-Z)");
|
||||
const [sortedLinks, setSortedLinks] = useState(links);
|
||||
const { searchSettings, toggleCheckbox } = useSearchSettingsStore();
|
||||
|
||||
const handleSortChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setSortBy(event.target.value);
|
||||
};
|
||||
|
||||
const { name, url, title, collection, tags } = searchSettings.filter;
|
||||
|
||||
useEffect(() => {
|
||||
const linksArray = [
|
||||
...links.filter((link) => {
|
||||
const query = searchSettings.query.toLowerCase();
|
||||
|
||||
if (
|
||||
(name && link.name.toLowerCase().includes(query)) ||
|
||||
(url && link.url.toLowerCase().includes(query)) ||
|
||||
(title && link.title.toLowerCase().includes(query)) ||
|
||||
(collection && link.collection.name.toLowerCase().includes(query)) ||
|
||||
(tags &&
|
||||
link.tags.some((tag) => tag.name.toLowerCase().includes(query)))
|
||||
)
|
||||
return true;
|
||||
}),
|
||||
];
|
||||
|
||||
if (sortBy === "Name (A-Z)")
|
||||
setSortedLinks(linksArray.sort((a, b) => a.name.localeCompare(b.name)));
|
||||
else if (sortBy === "Title (A-Z)")
|
||||
setSortedLinks(linksArray.sort((a, b) => a.title.localeCompare(b.title)));
|
||||
else if (sortBy === "Name (Z-A)")
|
||||
setSortedLinks(linksArray.sort((a, b) => b.name.localeCompare(a.name)));
|
||||
else if (sortBy === "Title (Z-A)")
|
||||
setSortedLinks(linksArray.sort((a, b) => b.title.localeCompare(a.title)));
|
||||
else if (sortBy === "Date (Newest First)")
|
||||
setSortedLinks(
|
||||
linksArray.sort(
|
||||
(a, b) =>
|
||||
new Date(b.createdAt as string).getTime() -
|
||||
new Date(a.createdAt as string).getTime()
|
||||
)
|
||||
);
|
||||
else if (sortBy === "Date (Oldest First)")
|
||||
setSortedLinks(
|
||||
linksArray.sort(
|
||||
(a, b) =>
|
||||
new Date(a.createdAt as string).getTime() -
|
||||
new Date(b.createdAt as string).getTime()
|
||||
)
|
||||
);
|
||||
}, [searchSettings, links, sortBy]);
|
||||
|
||||
return (
|
||||
<MainLayout>
|
||||
<div className="p-5 flex flex-col gap-5 w-full">
|
||||
<div className="flex gap-3 items-center justify-between">
|
||||
<div className="flex gap-3 items-center mb-5">
|
||||
<div className="flex gap-2">
|
||||
<FontAwesomeIcon
|
||||
icon={faSearch}
|
||||
className="sm:w-8 sm:h-8 w-6 h-6 mt-2 text-sky-500"
|
||||
/>
|
||||
<p className="sm:text-4xl text-3xl capitalize bg-gradient-to-tr from-sky-500 to-slate-400 bg-clip-text text-transparent font-bold">
|
||||
Search Results
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 items-center">
|
||||
<div className="relative">
|
||||
<div
|
||||
onClick={() => setFilterDropdown(!filterDropdown)}
|
||||
id="filter-dropdown"
|
||||
className="inline-flex rounded-md cursor-pointer hover:bg-slate-200 duration-100 p-1"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faFilter}
|
||||
id="filter-dropdown"
|
||||
className="w-5 h-5 text-gray-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{filterDropdown ? (
|
||||
<FilterSearchDropdown
|
||||
setFilterDropdown={setFilterDropdown}
|
||||
searchSettings={searchSettings}
|
||||
toggleCheckbox={toggleCheckbox}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<div
|
||||
onClick={() => setSortDropdown(!sortDropdown)}
|
||||
id="sort-dropdown"
|
||||
className="inline-flex rounded-md cursor-pointer hover:bg-slate-200 duration-100 p-1"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faSort}
|
||||
id="sort-dropdown"
|
||||
className="w-5 h-5 text-gray-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{sortDropdown ? (
|
||||
<SortLinkDropdown
|
||||
handleSortChange={handleSortChange}
|
||||
sortBy={sortBy}
|
||||
toggleSortDropdown={() => setSortDropdown(!sortDropdown)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{sortedLinks[0] ? (
|
||||
sortedLinks.map((e, i) => {
|
||||
return <LinkList key={i} link={e} count={i} />;
|
||||
})
|
||||
) : (
|
||||
<p className="text-sky-900">
|
||||
Nothing found.{" "}
|
||||
<span className="text-sky-500 font-bold text-xl" title="Shruggie">
|
||||
¯\_(ツ)_/¯
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</MainLayout>
|
||||
);
|
||||
}
|
|
@ -71,7 +71,7 @@ export default function () {
|
|||
<div className="flex gap-2">
|
||||
<FontAwesomeIcon
|
||||
icon={faHashtag}
|
||||
className="sm:w-8 sm:h-8 w-6 h-6 mt-2 text-sky-300"
|
||||
className="sm:w-8 sm:h-8 w-6 h-6 mt-2 text-sky-500"
|
||||
/>
|
||||
<p className="sm:text-4xl text-3xl capitalize bg-gradient-to-tr from-sky-500 to-slate-400 bg-clip-text text-transparent font-bold">
|
||||
{activeTag?.name}
|
||||
|
|
|
@ -16,7 +16,7 @@ CREATE TABLE "Collection" (
|
|||
"id" SERIAL NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL DEFAULT '',
|
||||
"color" TEXT NOT NULL DEFAULT '#7dd3fc',
|
||||
"color" TEXT NOT NULL DEFAULT '#0ea5e9',
|
||||
"isPublic" BOOLEAN NOT NULL DEFAULT false,
|
||||
"ownerId" INTEGER NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
@ -24,7 +24,7 @@ model Collection {
|
|||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
description String @default("")
|
||||
color String @default("#7dd3fc")
|
||||
color String @default("#0ea5e9")
|
||||
isPublic Boolean @default(false)
|
||||
owner User @relation(fields: [ownerId], references: [id])
|
||||
ownerId Int
|
||||
|
|
Ŝarĝante…
Reference in New Issue