many breaking changes/fixes

This commit is contained in:
Daniel
2023-03-28 11:01:50 +03:30
parent 3a5ae28f86
commit b9567ca3c2
43 changed files with 1180 additions and 466 deletions
+4 -1
View File
@@ -15,7 +15,10 @@ export default function ({ collection }: { collection: Collection }) {
<div className="p-5 bg-gray-100 h-40 w-60 rounded-md border-sky-100 border-solid border flex flex-col justify-between cursor-pointer hover:bg-gray-50 duration-100">
<div className="flex justify-between text-sky-900 items-center">
<p className="text-lg w-max">{collection.name}</p>
<FontAwesomeIcon icon={faChevronRight} className="w-3" />
<FontAwesomeIcon
icon={faChevronRight}
className="w-3 h-3 text-gray-500"
/>
</div>
<p className="text-sm text-sky-300 font-bold">{formattedDate}</p>
</div>
+15 -7
View File
@@ -5,7 +5,17 @@ import CreatableSelect from "react-select/creatable";
import { styles } from "./styles";
import { Options } from "./types";
export default function ({ onChange }: any) {
type Props = {
onChange: any;
defaultValue:
| {
value: number;
label: string;
}
| undefined;
};
export default function ({ onChange, defaultValue }: Props) {
const { collections } = useCollectionStore();
const router = useRouter();
@@ -17,10 +27,8 @@ export default function ({ onChange }: any) {
return e.id === collectionId;
});
let defaultCollection = null;
if (activeCollection) {
defaultCollection = {
if (activeCollection && !defaultValue) {
defaultValue = {
value: activeCollection?.id,
label: activeCollection?.name,
};
@@ -28,7 +36,7 @@ export default function ({ onChange }: any) {
useEffect(() => {
const formatedCollections = collections.map((e) => {
return { value: e.id, label: e.name };
return { value: e.id, label: e.name, ownerId: e.ownerId };
});
setOptions(formatedCollections);
@@ -40,7 +48,7 @@ export default function ({ onChange }: any) {
onChange={onChange}
options={options}
styles={styles}
defaultValue={defaultCollection}
defaultValue={defaultValue}
menuPosition="fixed"
/>
);
+10 -1
View File
@@ -4,7 +4,15 @@ import CreatableSelect from "react-select/creatable";
import { styles } from "./styles";
import { Options } from "./types";
export default function ({ onChange }: any) {
type Props = {
onChange: any;
defaultValue?: {
value: number;
label: string;
}[];
};
export default function ({ onChange, defaultValue }: Props) {
const { tags } = useTagStore();
const [options, setOptions] = useState<Options[]>([]);
@@ -23,6 +31,7 @@ export default function ({ onChange }: any) {
onChange={onChange}
options={options}
styles={styles}
defaultValue={defaultValue}
menuPosition="fixed"
isMulti
/>
+36 -20
View File
@@ -3,17 +3,17 @@ import {
faFolder,
faArrowUpRightFromSquare,
faEllipsis,
faStar,
faPenToSquare,
faTrashCan,
} from "@fortawesome/free-solid-svg-icons";
import { faFileImage, faFilePdf } from "@fortawesome/free-regular-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useState } from "react";
import Image from "next/image";
import Dropdown from "./Dropdown";
import useLinkStore from "@/store/links";
import Modal from "./Modal";
import EditLink from "./Modal/EditLink";
export default function ({
link,
@@ -23,6 +23,7 @@ export default function ({
count: number;
}) {
const [editDropdown, setEditDropdown] = useState(false);
const [editModal, setEditModal] = useState(false);
const [archiveLabel, setArchiveLabel] = useState("Archived Formats");
const { removeLink } = useLinkStore();
@@ -34,14 +35,24 @@ export default function ({
day: "numeric",
});
const toggleEditModal = () => {
setEditModal(!editModal);
};
return (
<div className="mx-auto border border-sky-100 mb-5 bg-gray-100 p-2 rounded-md flex items-start relative gap-5 group/item">
<div className="border border-sky-100 bg-gray-100 p-5 rounded-md flex items-start relative gap-5 sm:gap-14 group/item">
{editModal ? (
<Modal toggleModal={toggleEditModal}>
<EditLink toggleLinkModal={toggleEditModal} link={link} />
</Modal>
) : null}
<Image
src={`http://icons.duckduckgo.com/ip3/${shortendURL}.ico`}
width={32}
height={32}
alt=""
className="opacity-100 duration-100 select-none mt-3"
className="select-none mt-3 z-10 rounded-md"
draggable="false"
onError={(e) => {
const target = e.target as HTMLElement;
@@ -53,7 +64,7 @@ export default function ({
width={80}
height={80}
alt=""
className="blur-sm absolute left-0 opacity-50 select-none"
className="blur-sm absolute left-2 opacity-50 select-none hidden sm:block"
draggable="false"
onError={(e) => {
const target = e.target as HTMLElement;
@@ -65,9 +76,6 @@ export default function ({
<div className="flex items-baseline gap-1">
<p className="text-sm text-sky-300 font-bold">{count + 1}.</p>
<p className="text-lg text-sky-600">{link.name}</p>
{link.starred ? (
<FontAwesomeIcon icon={faStar} className="w-3 text-amber-400" />
) : null}
</div>
<p className="text-sky-400 text-sm font-medium">{link.title}</p>
<div className="flex gap-3 items-center flex-wrap my-3">
@@ -101,20 +109,25 @@ export default function ({
</div>
<div className="flex flex-col justify-between items-end relative">
<FontAwesomeIcon
icon={faEllipsis}
title="More"
className="w-6 h-6 text-gray-500 rounded-md cursor-pointer hover:bg-white hover:outline outline-sky-100 outline-1 duration-100 p-1"
<div
onClick={() => setEditDropdown(!editDropdown)}
id="edit-dropdown"
/>
className="text-gray-500 inline-flex rounded-md cursor-pointer hover:bg-white hover:outline outline-sky-100 outline-1 duration-100 p-1"
>
<FontAwesomeIcon
icon={faEllipsis}
title="More"
className="w-6 h-6"
id="edit-dropdown"
/>
</div>
<div>
<p className="text-center text-sky-400 text-sm font-bold">
{archiveLabel}
</p>
<div
className="flex justify-between mt-3 gap-3"
className="flex justify-center mt-3 gap-3"
onMouseLeave={() => setArchiveLabel("Archived Formats")}
>
<a
@@ -152,25 +165,28 @@ export default function ({
{editDropdown ? (
<Dropdown
items={[
{
name: "Star",
icon: <FontAwesomeIcon icon={faStar} />,
},
{
name: "Edit",
icon: <FontAwesomeIcon icon={faPenToSquare} />,
onClick: () => {
setEditModal(true);
setEditDropdown(false);
},
},
{
name: "Delete",
icon: <FontAwesomeIcon icon={faTrashCan} />,
onClick: () => removeLink(link),
onClick: () => {
removeLink(link);
setEditDropdown(false);
},
},
]}
onClickOutside={(e: Event) => {
const target = e.target as HTMLInputElement;
if (target.id !== "edit-dropdown") setEditDropdown(false);
}}
className="absolute top-8 right-0"
className="absolute top-9 right-0"
/>
) : null}
</div>
@@ -1,46 +1,76 @@
import React, { useState } from "react";
import CollectionSelection from "./InputSelect/CollectionSelection";
import TagSelection from "./InputSelect/TagSelection";
import React, { useEffect, useState } from "react";
import CollectionSelection from "@/components/InputSelect/CollectionSelection";
import TagSelection from "@/components/InputSelect/TagSelection";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { useRouter } from "next/router";
import { NewLink } from "@/types/global";
import { ExtendedLink, NewLink } from "@/types/global";
import useLinkStore from "@/store/links";
import { useRouter } from "next/router";
import useCollectionStore from "@/store/collections";
export default function ({ toggleLinkModal }: { toggleLinkModal: Function }) {
type Props = {
toggleLinkModal: Function;
};
export default function AddLink({ toggleLinkModal }: Props) {
const router = useRouter();
const [newLink, setNewLink] = useState<NewLink>({
name: "",
url: "",
tags: [],
collection: { id: Number(router.query.id) },
collection: {
id: undefined,
name: "",
ownerId: undefined,
},
});
const { addLink } = useLinkStore();
const { collections } = useCollectionStore();
useEffect(() => {
if (router.query.id) {
const currentCollection = collections.find(
(e) => e.id == Number(router.query.id)
);
setNewLink({
...newLink,
collection: {
id: currentCollection?.id,
name: currentCollection?.name,
ownerId: currentCollection?.ownerId,
},
});
}
}, []);
const setTags = (e: any) => {
const tagNames = e.map((e: any) => {
return e.label;
return { name: e.label };
});
setNewLink({ ...newLink, tags: tagNames });
};
const setCollection = (e: any) => {
const collection = { id: e?.value, isNew: e?.__isNew__ };
if (e?.__isNew__) e.value = null;
setNewLink({ ...newLink, collection: collection });
setNewLink({
...newLink,
collection: { id: e?.value, name: e?.label, ownerId: e?.ownerId },
});
};
const submitLink = async () => {
const response = await addLink(newLink);
console.log(newLink);
const response = await addLink(newLink as ExtendedLink);
if (response) toggleLinkModal();
};
return (
<div className="slide-up border-sky-100 rounded-md border-solid border rounded-md-md shadow-lg p-5 bg-white flex flex-col gap-3">
<div className="flex flex-col gap-3">
<p className="font-bold text-sky-300 mb-2 text-center">New Link</p>
<div className="flex gap-5 items-center justify-between">
@@ -72,7 +102,14 @@ export default function ({ toggleLinkModal }: { toggleLinkModal: Function }) {
<div className="flex gap-5 items-center justify-between">
<p className="text-sm font-bold text-sky-300">Collection</p>
<CollectionSelection onChange={setCollection} />
<CollectionSelection
defaultValue={
newLink.collection.name && newLink.collection.id
? { value: newLink.collection.id, label: newLink.collection.name }
: undefined
}
onChange={setCollection}
/>
</div>
<div
+101
View File
@@ -0,0 +1,101 @@
import React, { useState } from "react";
import CollectionSelection from "@/components/InputSelect/CollectionSelection";
import TagSelection from "@/components/InputSelect/TagSelection";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ExtendedLink } from "@/types/global";
import { faPenToSquare } from "@fortawesome/free-regular-svg-icons";
import useLinkStore from "@/store/links";
type Props = {
toggleLinkModal: Function;
link: ExtendedLink;
};
export default function EditLink({ toggleLinkModal, link }: Props) {
const [currentLink, setCurrentLink] = useState<ExtendedLink>(link);
const { updateLink } = useLinkStore();
const setTags = (e: any) => {
const tagNames = e.map((e: any) => {
return { name: e.label };
});
setCurrentLink({ ...currentLink, tags: tagNames });
};
const setCollection = (e: any) => {
if (e?.__isNew__) e.value = null;
setCurrentLink({
...currentLink,
collection: { id: e?.value, name: e?.label, ownerId: e?.ownerId } as any,
});
};
const submitLink = async () => {
updateLink(currentLink);
toggleLinkModal();
};
return (
<div className="flex flex-col gap-3">
<p className="font-bold text-sky-300 mb-2 text-center">New Link</p>
<div className="flex gap-5 items-center justify-between">
<p className="text-sm font-bold text-sky-300">Name</p>
<input
value={currentLink.name}
onChange={(e) =>
setCurrentLink({ ...currentLink, name: e.target.value })
}
type="text"
placeholder="e.g. Example Link"
className="w-60 rounded-md p-3 border-sky-100 border-solid border text-sm outline-none focus:border-sky-500 duration-100"
/>
</div>
<div className="flex gap-5 items-center justify-between">
<p className="text-sm font-bold text-sky-300">URL</p>
<input
value={currentLink.url}
onChange={(e) =>
setCurrentLink({ ...currentLink, url: e.target.value })
}
type="text"
placeholder="e.g. http://example.com/"
className="w-60 rounded-md p-3 border-sky-100 border-solid border text-sm outline-none focus:border-sky-500 duration-100"
/>
</div>
<div className="flex gap-5 items-center justify-between">
<p className="text-sm font-bold text-sky-300">Tags</p>
<TagSelection
onChange={setTags}
defaultValue={link.tags.map((e) => {
return { label: e.name, value: e.id };
})}
/>
</div>
<div className="flex gap-5 items-center justify-between">
<p className="text-sm font-bold text-sky-300">Collection</p>
<CollectionSelection
onChange={setCollection}
defaultValue={{
label: link.collection.name,
value: link.collection.id,
}}
/>
</div>
<div
className="mx-auto mt-2 bg-sky-500 text-white flex items-center gap-2 py-2 px-5 rounded-md select-none font-bold cursor-pointer duration-100 hover:bg-sky-400"
onClick={submitLink}
>
<FontAwesomeIcon icon={faPenToSquare} className="h-5" />
Edit Link
</div>
</div>
);
}
+19
View File
@@ -0,0 +1,19 @@
import { ReactNode } from "react";
import ClickAwayHandler from "@/components/ClickAwayHandler";
type Props = {
toggleModal: Function;
children: ReactNode;
};
export default function ({ toggleModal, children }: Props) {
return (
<div className="fixed top-0 bottom-0 right-0 left-0 bg-gray-500 bg-opacity-10 flex items-center fade-in z-30">
<ClickAwayHandler onClickOutside={toggleModal} className="w-fit mx-auto">
<div className="slide-up border-sky-100 rounded-md border-solid border shadow-lg p-5 bg-white">
{children}
</div>
</ClickAwayHandler>
</div>
);
}
+76 -88
View File
@@ -1,62 +1,25 @@
import { useRouter } from "next/router";
import useCollectionStore from "@/store/collections";
import { Collection, Tag } from "@prisma/client";
import ClickAwayHandler from "./ClickAwayHandler";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { signOut } from "next-auth/react";
import { useSession } from "next-auth/react";
import {
faPlus,
faFolder,
faBox,
faHashtag,
faBookmark,
faMagnifyingGlass,
IconDefinition,
faCircleUser,
faSliders,
faArrowRightFromBracket,
faChevronDown,
} from "@fortawesome/free-solid-svg-icons";
import { useEffect, useState } from "react";
import AddLinkModal from "./AddLinkModal";
import useTagStore from "@/store/tags";
import { useState } from "react";
import Dropdown from "@/components/Dropdown";
import Modal from "./Modal";
import AddLink from "./Modal/AddLink";
export default function () {
const router = useRouter();
const [pageName, setPageName] = useState<string | null>("");
const [pageIcon, setPageIcon] = useState<IconDefinition | null>(null);
const { data: session } = useSession();
const { collections } = useCollectionStore();
const { tags } = useTagStore();
const [profileDropdown, setProfileDropdown] = useState(false);
useEffect(() => {
if (router.route === "/collections/[id]") {
const collectionId = router.query.id;
const activeCollection: Collection | undefined = collections.find(
(e) => e.id.toString() == collectionId
);
if (activeCollection) {
setPageName(activeCollection?.name);
}
setPageIcon(faFolder);
} else if (router.route === "/tags/[id]") {
const tagId = router.query.id;
const activeTag: Tag | undefined = tags.find(
(e) => e.id.toString() == tagId
);
if (activeTag) {
setPageName(activeTag?.name);
}
setPageIcon(faHashtag);
} else if (router.route === "/collections") {
setPageName("All Collections");
setPageIcon(faBox);
} else if (router.route === "/links") {
setPageName("All Links");
setPageIcon(faBookmark);
}
}, [router, collections, tags]);
const user = session?.user;
const [linkModal, setLinkModal] = useState(false);
@@ -65,50 +28,75 @@ export default function () {
};
return (
<div className="flex justify-between items-center p-2 border-solid border-b-sky-100 border-b">
<div className="text-sky-900 rounded-md my-1 flex items-center gap-2 font-bold">
{pageIcon ? (
<FontAwesomeIcon icon={pageIcon} className="w-4 text-sky-300" />
) : null}
<p>{pageName}</p>
<div className="flex justify-between gap-2 items-center px-5 py-2 border-solid border-b-sky-100 border-b">
<div className="flex items-center relative">
<label
htmlFor="search-box"
className="inline-flex w-fit absolute right-0 cursor-pointer select-none rounded-md p-1 text-sky-500"
>
<FontAwesomeIcon icon={faMagnifyingGlass} className="w-5 h-5" />
</label>
<input
id="search-box"
type="text"
placeholder="Search for Links"
className="border border-sky-100 rounded-md pr-6 w-60 focus:border-sky-500 sm:focus:w-80 hover:border-sky-500 duration-100 outline-none p-1 text-sm"
/>
</div>
<div className="flex items-center gap-2 justify-between">
<div className="flex items-center relative">
<label
htmlFor="search-box"
className="inline-flex w-fit absolute right-0 cursor-pointer"
title="Search"
>
<FontAwesomeIcon
icon={faMagnifyingGlass}
className="select-none w-5 h-5 rounded-md p-1 text-sky-500 "
/>
</label>
<input
id="search-box"
type="text"
placeholder="Search for Links"
className="border border-sky-100 rounded-md pr-6 w-6 focus:border-sky-500 focus:w-60 hover:border-sky-500 duration-100 outline-none p-1 text-sm"
/>
</div>
<FontAwesomeIcon
icon={faPlus}
<div className="flex items-center gap-2">
<div
onClick={toggleLinkModal}
title="New Link"
className="select-none cursor-pointer w-5 h-5 text-sky-500 p-1 rounded-md hover:outline-sky-500 outline duration-100 hover:bg-white outline-sky-100 outline-1"
/>
className="inline-flex gap-1 items-center select-none cursor-pointer p-1 text-sky-500 rounded-md hover:outline-sky-500 outline duration-100 bg-white outline-sky-100 outline-1"
>
<FontAwesomeIcon icon={faPlus} className="w-5 h-5" />
</div>
{linkModal ? (
<div className="fixed top-0 bottom-0 right-0 left-0 bg-gray-500 bg-opacity-10 flex items-center fade-in z-10">
<ClickAwayHandler
onClickOutside={toggleLinkModal}
className="w-fit mx-auto"
>
<AddLinkModal toggleLinkModal={toggleLinkModal} />
</ClickAwayHandler>
</div>
<Modal toggleModal={toggleLinkModal}>
<AddLink toggleLinkModal={toggleLinkModal} />
</Modal>
) : null}
<div className="relative">
<div
className="flex gap-2 items-center p-1 w-fit bg-white text-gray-600 cursor-pointer border border-sky-100 hover:border-sky-500 rounded-md duration-100"
onClick={() => setProfileDropdown(!profileDropdown)}
id="profile-dropdown"
>
<FontAwesomeIcon
icon={faCircleUser}
className="h-5 w-5 pointer-events-none"
/>
<div className="flex items-center gap-1 pointer-events-none">
<p className="font-bold leading-3">{user?.name}</p>
<FontAwesomeIcon icon={faChevronDown} className="h-3 w-3" />
</div>
</div>
{profileDropdown ? (
<Dropdown
items={[
{
name: "Settings",
icon: <FontAwesomeIcon icon={faSliders} />,
},
{
name: "Logout",
icon: <FontAwesomeIcon icon={faArrowRightFromBracket} />,
onClick: () => {
signOut();
setProfileDropdown(!profileDropdown);
},
},
]}
onClickOutside={(e: Event) => {
const target = e.target as HTMLInputElement;
if (target.id !== "profile-dropdown") setProfileDropdown(false);
}}
className="absolute top-8 right-0 z-20"
/>
) : null}
</div>
</div>
</div>
);
+11 -58
View File
@@ -1,37 +1,25 @@
import { useSession } from "next-auth/react";
import ClickAwayHandler from "@/components/ClickAwayHandler";
import { useState } from "react";
import useCollectionStore from "@/store/collections";
import { signOut } from "next-auth/react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faPlus,
faChevronDown,
faFolder,
faBox,
faHashtag,
faBookmark,
faCircleUser,
faSliders,
faArrowRightFromBracket,
} from "@fortawesome/free-solid-svg-icons";
import SidebarItem from "./SidebarItem";
import useTagStore from "@/store/tags";
import Link from "next/link";
import Dropdown from "@/components/Dropdown";
export default function () {
const { data: session } = useSession();
const [collectionInput, setCollectionInput] = useState(false);
const [profileDropdown, setProfileDropdown] = useState(false);
const { collections, addCollection } = useCollectionStore();
const { tags } = useTagStore();
const user = session?.user;
const toggleCollectionInput = () => {
setCollectionInput(!collectionInput);
};
@@ -48,48 +36,12 @@ export default function () {
};
return (
<div className="fixed bg-gray-100 top-0 bottom-0 left-0 w-80 p-2 overflow-y-auto hide-scrollbar border-solid border-r-sky-100 border z-10">
<div className="relative w-fit">
<div
className="flex gap-2 items-center mb-5 p-2 w-fit text-gray-600 cursor-pointer hover:outline outline-sky-100 outline-1 hover:bg-gray-50 rounded-md duration-100"
onClick={() => setProfileDropdown(!profileDropdown)}
id="profile-dropdown"
>
<FontAwesomeIcon
icon={faCircleUser}
className="h-5 pointer-events-none"
/>
<div className="flex items-center gap-1 pointer-events-none">
<p className="font-bold">{user?.name}</p>
<FontAwesomeIcon icon={faChevronDown} className="h-3" />
</div>
</div>
{profileDropdown ? (
<Dropdown
items={[
{
name: "Settings",
icon: <FontAwesomeIcon icon={faSliders} />,
},
{
name: "Logout",
icon: <FontAwesomeIcon icon={faArrowRightFromBracket} />,
onClick: () => {
signOut();
setProfileDropdown(!profileDropdown);
},
},
]}
onClickOutside={(e: Event) => {
const target = e.target as HTMLInputElement;
if (target.id !== "profile-dropdown") setProfileDropdown(false);
}}
className="absolute top-10 left-0"
/>
) : null}
</div>
<div className="fixed bg-gray-100 top-0 bottom-0 left-0 w-80 p-2 overflow-y-auto hide-scrollbar border-solid border-r-sky-100 border z-20">
<p className="p-2 text-sky-500 font-bold text-xl mb-5 leading-4">
Linkwarden
</p>
<Link href="links">
<Link href="/links">
<div className="hover:bg-gray-50 hover:outline outline-sky-100 outline-1 duration-100 text-sky-900 rounded-md my-1 p-2 cursor-pointer flex items-center gap-2">
<FontAwesomeIcon icon={faBookmark} className="w-4 text-sky-300" />
<p>All Links</p>
@@ -119,12 +71,13 @@ export default function () {
/>
</ClickAwayHandler>
) : (
<FontAwesomeIcon
icon={faPlus}
onClick={toggleCollectionInput}
<div
title="Add Collection"
className="select-none text-gray-500 rounded-md cursor-pointer hover:bg-white hover:outline outline-sky-100 outline-1 duration-100 p-1 w-3"
/>
onClick={toggleCollectionInput}
className="select-none text-gray-500 rounded-md cursor-pointer hover:bg-white hover:outline outline-sky-100 outline-1 duration-100 p-1"
>
<FontAwesomeIcon icon={faPlus} className="h-3 w-3" />
</div>
)}
</div>
<div>