better public page [WIP]
This commit is contained in:
parent
021f7c9481
commit
d972ec2dab
|
@ -3,7 +3,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||
import { faCrown } from "@fortawesome/free-solid-svg-icons";
|
||||
import { CollectionIncludingMembersAndLinkCount } from "@/types/global";
|
||||
import ProfilePhoto from "@/components/ProfilePhoto";
|
||||
import { toast } from "react-hot-toast";
|
||||
import getPublicUserData from "@/lib/client/getPublicUserData";
|
||||
|
||||
type Props = {
|
||||
|
@ -11,10 +10,6 @@ type Props = {
|
|||
};
|
||||
|
||||
export default function ViewTeam({ collection }: Props) {
|
||||
const currentURL = new URL(document.URL);
|
||||
|
||||
const publicCollectionURL = `${currentURL.origin}/public/collections/${collection.id}`;
|
||||
|
||||
const [collectionOwner, setCollectionOwner] = useState({
|
||||
id: null,
|
||||
name: "",
|
||||
|
|
|
@ -6,12 +6,13 @@ import Dropdown from "@/components/Dropdown";
|
|||
import ClickAwayHandler from "@/components/ClickAwayHandler";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
import { useRouter } from "next/router";
|
||||
import Search from "@/components/Search";
|
||||
import SearchBar from "@/components/SearchBar";
|
||||
import useAccountStore from "@/store/account";
|
||||
import ProfilePhoto from "@/components/ProfilePhoto";
|
||||
import useModalStore from "@/store/modals";
|
||||
import { useTheme } from "next-themes";
|
||||
import useWindowDimensions from "@/hooks/useWindowDimensions";
|
||||
import ToggleDarkMode from "./ToggleDarkMode";
|
||||
|
||||
export default function Navbar() {
|
||||
const { setModal } = useModalStore();
|
||||
|
@ -56,7 +57,7 @@ export default function Navbar() {
|
|||
>
|
||||
<FontAwesomeIcon icon={faBars} className="w-5 h-5" />
|
||||
</div>
|
||||
<Search />
|
||||
<SearchBar />
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
onClick={() => {
|
||||
|
@ -76,6 +77,9 @@ export default function Navbar() {
|
|||
New Link
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ToggleDarkMode className="sm:flex hidden" />
|
||||
|
||||
<div className="relative">
|
||||
<div
|
||||
className="flex gap-1 group sm:hover:bg-slate-200 sm:hover:dark:bg-neutral-700 sm:hover:p-1 sm:hover:pr-2 duration-100 h-10 rounded-full items-center w-fit cursor-pointer"
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
import { faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { toast } from "react-hot-toast";
|
||||
|
||||
type Props = {
|
||||
placeHolder?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default function PublicSearchBar({ placeHolder, className }: Props) {
|
||||
const router = useRouter();
|
||||
|
||||
const routeQuery = router.query.q;
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState(
|
||||
routeQuery ? decodeURIComponent(routeQuery as string) : ""
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(router);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex items-center relative group">
|
||||
<label
|
||||
htmlFor="search-box"
|
||||
className="inline-flex w-fit absolute left-2 pointer-events-none rounded-md text-sky-500 dark:text-sky-500"
|
||||
>
|
||||
<FontAwesomeIcon icon={faMagnifyingGlass} className="w-4 h-4" />
|
||||
</label>
|
||||
|
||||
<input
|
||||
id="search-box"
|
||||
type="text"
|
||||
placeholder={placeHolder}
|
||||
value={searchQuery}
|
||||
onChange={(e) => {
|
||||
e.target.value.includes("%") &&
|
||||
toast.error("The search query should not contain '%'.");
|
||||
setSearchQuery(e.target.value.replace("%", ""));
|
||||
}}
|
||||
onKeyDown={(e) =>
|
||||
e.key === "Enter" &&
|
||||
router.push(
|
||||
"/public/collections/" +
|
||||
router.query.id +
|
||||
"?q=" +
|
||||
encodeURIComponent(searchQuery)
|
||||
)
|
||||
}
|
||||
className="border text-sm border-sky-100 bg-white dark:border-neutral-700 focus:border-sky-300 dark:focus:border-sky-600 rounded-md pl-7 py-1 pr-1 w-44 sm:w-60 dark:hover:border-neutral-600 md:focus:w-80 hover:border-sky-300 duration-100 outline-none dark:bg-neutral-800"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -4,24 +4,17 @@ import { useState } from "react";
|
|||
import { useRouter } from "next/router";
|
||||
import { toast } from "react-hot-toast";
|
||||
|
||||
export default function Search() {
|
||||
export default function SearchBar() {
|
||||
const router = useRouter();
|
||||
|
||||
const routeQuery = router.query.query;
|
||||
const routeQuery = router.query.q;
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState(
|
||||
routeQuery ? decodeURIComponent(routeQuery as string) : ""
|
||||
);
|
||||
|
||||
const [searchBox, setSearchBox] = useState(
|
||||
router.pathname.startsWith("/search") || false
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex items-center relative group"
|
||||
onClick={() => setSearchBox(true)}
|
||||
>
|
||||
<div className="flex items-center relative group">
|
||||
<label
|
||||
htmlFor="search-box"
|
||||
className="inline-flex w-fit absolute left-2 pointer-events-none rounded-md p-1 text-sky-500 dark:text-sky-500"
|
||||
|
@ -43,7 +36,6 @@ export default function Search() {
|
|||
e.key === "Enter" &&
|
||||
router.push("/search?q=" + encodeURIComponent(searchQuery))
|
||||
}
|
||||
autoFocus={searchBox}
|
||||
className="border border-sky-100 bg-gray-50 dark:border-neutral-700 focus:border-sky-300 dark:focus:border-sky-600 rounded-md pl-10 py-2 pr-2 w-44 sm:w-60 dark:hover:border-neutral-600 md:focus:w-80 hover:border-sky-300 duration-100 outline-none dark:bg-neutral-800"
|
||||
/>
|
||||
</div>
|
|
@ -2,7 +2,11 @@ import { useTheme } from "next-themes";
|
|||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSun, faMoon } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
export default function ToggleDarkMode() {
|
||||
type Props = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default function ToggleDarkMode({ className }: Props) {
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
const handleToggle = () => {
|
||||
|
@ -15,15 +19,13 @@ export default function ToggleDarkMode() {
|
|||
|
||||
return (
|
||||
<div
|
||||
className="flex gap-1 duration-100 h-10 rounded-full items-center w-fit cursor-pointer"
|
||||
className={`cursor-pointer flex select-none border border-sky-600 items-center justify-center dark:bg-neutral-900 bg-white hover:border-sky-500 group duration-100 rounded-full text-white w-10 h-10 ${className}`}
|
||||
onClick={handleToggle}
|
||||
>
|
||||
<div className="shadow bg-sky-700 dark:bg-sky-400 flex items-center justify-center rounded-full text-white w-10 h-10 duration-100">
|
||||
<FontAwesomeIcon
|
||||
icon={theme === "dark" ? faSun : faMoon}
|
||||
className="w-1/2 h-1/2"
|
||||
className="w-1/2 h-1/2 text-sky-600 group-hover:text-sky-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -11,8 +11,6 @@ const getPublicCollectionData = async (
|
|||
|
||||
const data = await res.json();
|
||||
|
||||
console.log(data);
|
||||
|
||||
setData(data.response);
|
||||
|
||||
return data;
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@headlessui/react": "^1.7.15",
|
||||
"@mozilla/readability": "^0.4.4",
|
||||
"@next/font": "13.4.9",
|
||||
"@prisma/client": "^4.16.2",
|
||||
"@stripe/stripe-js": "^1.54.1",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
|
@ -40,6 +39,7 @@
|
|||
"eslint-config-next": "13.4.9",
|
||||
"framer-motion": "^10.16.4",
|
||||
"jsdom": "^22.1.0",
|
||||
"lottie-web": "^5.12.2",
|
||||
"micro": "^10.0.1",
|
||||
"next": "13.4.12",
|
||||
"next-auth": "^4.22.1",
|
||||
|
|
|
@ -11,6 +11,16 @@ import useLinkStore from "@/store/links";
|
|||
import ProfilePhoto from "@/components/ProfilePhoto";
|
||||
import useModalStore from "@/store/modals";
|
||||
import ModalManagement from "@/components/ModalManagement";
|
||||
import ToggleDarkMode from "@/components/ToggleDarkMode";
|
||||
import { useTheme } from "next-themes";
|
||||
import getPublicUserData from "@/lib/client/getPublicUserData";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import PublicSearchBar from "@/components/PublicSearchBar";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faFilter, faSort } from "@fortawesome/free-solid-svg-icons";
|
||||
import FilterSearchDropdown from "@/components/FilterSearchDropdown";
|
||||
import SortDropdown from "@/components/SortDropdown";
|
||||
|
||||
const cardVariants: Variants = {
|
||||
offscreen: {
|
||||
|
@ -28,10 +38,27 @@ const cardVariants: Variants = {
|
|||
|
||||
export default function PublicCollections() {
|
||||
const { links } = useLinkStore();
|
||||
const { setModal } = useModalStore();
|
||||
const { modal, setModal } = useModalStore();
|
||||
|
||||
useEffect(() => {
|
||||
modal
|
||||
? (document.body.style.overflow = "hidden")
|
||||
: (document.body.style.overflow = "auto");
|
||||
}, [modal]);
|
||||
|
||||
const { theme } = useTheme();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const [collectionOwner, setCollectionOwner] = useState({
|
||||
id: null,
|
||||
name: "",
|
||||
username: "",
|
||||
image: "",
|
||||
});
|
||||
|
||||
useEffect(() => {}, []);
|
||||
|
||||
const [searchFilter, setSearchFilter] = useState({
|
||||
name: true,
|
||||
url: true,
|
||||
|
@ -59,31 +86,34 @@ export default function PublicCollections() {
|
|||
const [collection, setCollection] =
|
||||
useState<CollectionIncludingMembersAndLinkCount>();
|
||||
|
||||
document.body.style.background = "white";
|
||||
|
||||
useEffect(() => {
|
||||
if (router.query.id) {
|
||||
getPublicCollectionData(Number(router.query.id), setCollection);
|
||||
}
|
||||
|
||||
// document
|
||||
// .querySelector("body")
|
||||
// ?.classList.add(
|
||||
// "bg-gradient-to-br",
|
||||
// "from-slate-50",
|
||||
// "to-sky-50",
|
||||
// "min-h-screen"
|
||||
// );
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchOwner = async () => {
|
||||
if (collection) {
|
||||
const owner = await getPublicUserData(collection.ownerId as number);
|
||||
setCollectionOwner(owner);
|
||||
}
|
||||
};
|
||||
|
||||
fetchOwner();
|
||||
}, [collection]);
|
||||
|
||||
return collection ? (
|
||||
<div
|
||||
className="h-screen"
|
||||
style={{
|
||||
backgroundImage: `linear-gradient(${collection?.color}30 10%, #f3f4f6 30%, #f9fafb 100%)`,
|
||||
backgroundImage: `linear-gradient(${collection?.color}30 10%, ${
|
||||
theme === "dark" ? "#262626" : "#f3f4f6"
|
||||
} 50%, ${theme === "dark" ? "#171717" : "#ffffff"} 100%)`,
|
||||
}}
|
||||
>
|
||||
<ModalManagement />
|
||||
|
||||
{collection ? (
|
||||
<Head>
|
||||
<title>{collection.name} | Linkwarden</title>
|
||||
|
@ -96,14 +126,26 @@ export default function PublicCollections() {
|
|||
) : undefined}
|
||||
<div className="max-w-4xl mx-auto p-5 bg">
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-4xl text-black font-thin mb-5 capitalize mt-10">
|
||||
<p className="text-4xl font-thin mb-2 capitalize mt-10">
|
||||
{collection.name}
|
||||
</p>
|
||||
<div className="text-black">[Logo]</div>
|
||||
<div className="flex gap-2 items-center mt-8">
|
||||
<ToggleDarkMode className="w-8 h-8 flex" />
|
||||
<Link href="https://linkwarden.app/" target="_blank">
|
||||
<Image
|
||||
src={`/icon.png`}
|
||||
width={551}
|
||||
height={551}
|
||||
alt="Linkwarden"
|
||||
title="Linkwarden"
|
||||
className="h-8 w-fit mx-auto"
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className={`min-w-[15rem] ${collection.members[1] && "mr-3"}`}>
|
||||
<div className={`min-w-[15rem]`}>
|
||||
<div
|
||||
onClick={() =>
|
||||
setModal({
|
||||
|
@ -115,8 +157,16 @@ export default function PublicCollections() {
|
|||
defaultIndex: 0,
|
||||
})
|
||||
}
|
||||
className="hover:opacity-80 duration-100 flex justify-center sm:justify-end items-center w-fit sm:mr-0 sm:ml-auto cursor-pointer"
|
||||
className="hover:opacity-80 duration-100 flex justify-center sm:justify-end items-center w-fit cursor-pointer"
|
||||
>
|
||||
{collectionOwner.id ? (
|
||||
<ProfilePhoto
|
||||
src={
|
||||
collectionOwner.image ? collectionOwner.image : undefined
|
||||
}
|
||||
className={`w-8 h-8 border-2`}
|
||||
/>
|
||||
) : undefined}
|
||||
{collection.members
|
||||
.sort((a, b) => (a.userId as number) - (b.userId as number))
|
||||
.map((e, i) => {
|
||||
|
@ -124,28 +174,84 @@ export default function PublicCollections() {
|
|||
<ProfilePhoto
|
||||
key={i}
|
||||
src={e.user.image ? e.user.image : undefined}
|
||||
className={`${
|
||||
collection.members[1] && "-mr-3"
|
||||
} border-[3px]`}
|
||||
className={`w-8 h-8 border-2`}
|
||||
/>
|
||||
);
|
||||
})
|
||||
.slice(0, 4)}
|
||||
.slice(0, 3)}
|
||||
{collection?.members.length &&
|
||||
collection.members.length - 4 > 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 dark:border-neutral-700 -mr-3">
|
||||
+{collection?.members?.length - 4}
|
||||
collection.members.length - 3 > 0 ? (
|
||||
<div className="w-8 h-8 text-white flex items-center justify-center rounded-full border-2 bg-sky-600 dark:bg-sky-600 border-slate-200 dark:border-neutral-700">
|
||||
+{collection?.members?.length - 3}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<p className="ml-2 text-gray-500 dark:text-gray-300">
|
||||
By {collectionOwner.name} and {collection.members.length}{" "}
|
||||
others.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="mt-5">{collection.description}</p>
|
||||
|
||||
<hr className="mt-5 border-1 border-neutral-500" />
|
||||
|
||||
<div className="flex mb-5 mt-10 flex-col gap-5">
|
||||
<div className="flex justify-between">
|
||||
<PublicSearchBar
|
||||
placeHolder={`Search ${collection._count?.links} Links`}
|
||||
/>
|
||||
|
||||
<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-neutral-500 hover:bg-opacity-40 duration-100 p-1"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faFilter}
|
||||
id="filter-dropdown"
|
||||
className="w-5 h-5 text-gray-500 dark:text-gray-300"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{filterDropdown ? (
|
||||
<FilterSearchDropdown
|
||||
setFilterDropdown={setFilterDropdown}
|
||||
searchFilter={searchFilter}
|
||||
setSearchFilter={setSearchFilter}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="relative">
|
||||
<div
|
||||
onClick={() => setSortDropdown(!sortDropdown)}
|
||||
id="sort-dropdown"
|
||||
className="inline-flex rounded-md cursor-pointer hover:bg-neutral-500 hover:bg-opacity-40 duration-100 p-1"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faSort}
|
||||
id="sort-dropdown"
|
||||
className="w-5 h-5 text-gray-500 dark:text-gray-300"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{sortDropdown ? (
|
||||
<SortDropdown
|
||||
sortBy={sortBy}
|
||||
setSort={setSortBy}
|
||||
toggleSortDropdown={() => setSortDropdown(!sortDropdown)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="mt-2 text-black">{collection.description}</p>
|
||||
|
||||
<hr className="mt-5 border-1 border-slate-400" />
|
||||
|
||||
<div className="flex flex-col gap-5 my-8">
|
||||
<div className="flex flex-col gap-5">
|
||||
{links
|
||||
?.filter((e) => e.collectionId === Number(router.query.id))
|
||||
.map((e, i) => {
|
||||
|
@ -169,6 +275,7 @@ export default function PublicCollections() {
|
|||
</p> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 194 KiB |
10
yarn.lock
10
yarn.lock
|
@ -919,11 +919,6 @@
|
|||
dependencies:
|
||||
glob "7.1.7"
|
||||
|
||||
"@next/font@13.4.9":
|
||||
version "13.4.9"
|
||||
resolved "https://registry.yarnpkg.com/@next/font/-/font-13.4.9.tgz#5540e69a1a5fbd1113d622a89cdd21c0ab3906c8"
|
||||
integrity sha512-aR0XEyd1cxqaKuelQFDGwUBYV0wyZfJTNiRoSk1XsECTyMhiSMmCOY7yOPMuPlw+6cctca0GyZXGGFb5EVhiRw==
|
||||
|
||||
"@next/swc-darwin-arm64@13.4.12":
|
||||
version "13.4.12"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.12.tgz#326c830b111de8a1a51ac0cbc3bcb157c4c4f92c"
|
||||
|
@ -3627,6 +3622,11 @@ loose-envify@^1.1.0, loose-envify@^1.4.0:
|
|||
dependencies:
|
||||
js-tokens "^3.0.0 || ^4.0.0"
|
||||
|
||||
lottie-web@^5.12.2:
|
||||
version "5.12.2"
|
||||
resolved "https://registry.yarnpkg.com/lottie-web/-/lottie-web-5.12.2.tgz#579ca9fe6d3fd9e352571edd3c0be162492f68e5"
|
||||
integrity sha512-uvhvYPC8kGPjXT3MyKMrL3JitEAmDMp30lVkuq/590Mw9ok6pWcFCwXJveo0t5uqYw1UREQHofD+jVpdjBv8wg==
|
||||
|
||||
lru-cache@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
||||
|
|
Ŝarĝante…
Reference in New Issue