major bug fixed + error handling

This commit is contained in:
Daniel 2023-06-27 03:05:12 +03:30
parent f1bd48be83
commit 92f6ee3942
10 changed files with 122 additions and 86 deletions

View File

@ -14,6 +14,7 @@ import useModalStore from "@/store/modals";
import { faCalendarDays } from "@fortawesome/free-regular-svg-icons"; import { faCalendarDays } from "@fortawesome/free-regular-svg-icons";
import usePermissions from "@/hooks/usePermissions"; import usePermissions from "@/hooks/usePermissions";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import isValidUrl from "@/lib/client/isValidUrl";
type Props = { type Props = {
link: LinkIncludingShortenedCollectionAndTags; link: LinkIncludingShortenedCollectionAndTags;
@ -78,7 +79,8 @@ export default function LinkCard({ link, count, className }: Props) {
setExpandDropdown(false); setExpandDropdown(false);
}; };
const url = new URL(link.url); const url = isValidUrl(link.url) ? new URL(link.url) : undefined;
const formattedDate = new Date(link.createdAt as string).toLocaleString( const formattedDate = new Date(link.createdAt as string).toLocaleString(
"en-US", "en-US",
{ {
@ -122,18 +124,20 @@ export default function LinkCard({ link, count, className }: Props) {
}} }}
className="flex items-start gap-5 sm:gap-10 h-full w-full p-5" className="flex items-start gap-5 sm:gap-10 h-full w-full p-5"
> >
<Image {url && (
src={`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url.origin}&size=32`} <Image
width={64} src={`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url.origin}&size=32`}
height={64} width={64}
alt="" height={64}
className="blur-sm absolute w-16 group-hover:opacity-80 duration-100 rounded-md bottom-5 right-5 opacity-60 select-none" alt=""
draggable="false" className="blur-sm absolute w-16 group-hover:opacity-80 duration-100 rounded-md bottom-5 right-5 opacity-60 select-none"
onError={(e) => { draggable="false"
const target = e.target as HTMLElement; onError={(e) => {
target.style.opacity = "0"; const target = e.target as HTMLElement;
}} target.style.display = "none";
/> }}
/>
)}
<div className="flex justify-between gap-5 w-full h-full z-0"> <div className="flex justify-between gap-5 w-full h-full z-0">
<div className="flex flex-col justify-between w-full"> <div className="flex flex-col justify-between w-full">

View File

@ -24,7 +24,7 @@ type Props =
activeLink: LinkIncludingShortenedCollectionAndTags; activeLink: LinkIncludingShortenedCollectionAndTags;
}; };
export default function EditLink({ export default function AddOrEditLink({
toggleLinkModal, toggleLinkModal,
method, method,
activeLink, activeLink,
@ -70,8 +70,7 @@ export default function EditLink({
} }
}, []); }, []);
const shortendURL = // const shortendURL = method === "UPDATE" ? new URL(link.url).host.toLowerCase() : undefined;
method === "UPDATE" ? new URL(link.url).host.toLowerCase() : undefined;
const setTags = (e: any) => { const setTags = (e: any) => {
const tagNames = e.map((e: any) => { const tagNames = e.map((e: any) => {

View File

@ -19,6 +19,7 @@ import {
faFileImage, faFileImage,
faFilePdf, faFilePdf,
} from "@fortawesome/free-regular-svg-icons"; } from "@fortawesome/free-regular-svg-icons";
import isValidUrl from "@/lib/client/isValidUrl";
type Props = { type Props = {
link: LinkIncludingShortenedCollectionAndTags; link: LinkIncludingShortenedCollectionAndTags;
@ -56,7 +57,7 @@ export default function LinkDetails({ link }: Props) {
const colorThief = new ColorThief(); const colorThief = new ColorThief();
const url = new URL(link.url); const url = isValidUrl(link.url) ? new URL(link.url) : undefined;
const rgbToHex = (r: number, g: number, b: number): string => const rgbToHex = (r: number, g: number, b: number): string =>
"#" + "#" +
@ -123,7 +124,7 @@ export default function LinkDetails({ link }: Props) {
<div <div
className={`relative flex gap-5 items-start ${!imageError && "-mt-24"}`} className={`relative flex gap-5 items-start ${!imageError && "-mt-24"}`}
> >
{!imageError && ( {!imageError && url && (
<Image <Image
src={`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url.origin}&size=32`} src={`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url.origin}&size=32`}
width={42} width={42}
@ -160,7 +161,7 @@ export default function LinkDetails({ link }: Props) {
rel="noreferrer" rel="noreferrer"
className="text-sm text-gray-500 break-all hover:underline cursor-pointer w-fit" className="text-sm text-gray-500 break-all hover:underline cursor-pointer w-fit"
> >
{url.host} {url ? url.host : link.url}
</Link> </Link>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
import { Tab } from "@headlessui/react"; import { Tab } from "@headlessui/react";
import { LinkIncludingShortenedCollectionAndTags } from "@/types/global"; import { LinkIncludingShortenedCollectionAndTags } from "@/types/global";
import EditLink from "./EditLink"; import AddOrEditLink from "./AddOrEditLink";
import LinkDetails from "./LinkDetails"; import LinkDetails from "./LinkDetails";
type Props = type Props =
@ -72,13 +72,16 @@ export default function LinkModal({
<Tab.Panel> <Tab.Panel>
{activeLink && method === "UPDATE" ? ( {activeLink && method === "UPDATE" ? (
<EditLink <AddOrEditLink
toggleLinkModal={toggleLinkModal} toggleLinkModal={toggleLinkModal}
method="UPDATE" method="UPDATE"
activeLink={activeLink} activeLink={activeLink}
/> />
) : ( ) : (
<EditLink toggleLinkModal={toggleLinkModal} method="CREATE" /> <AddOrEditLink
toggleLinkModal={toggleLinkModal}
method="CREATE"
/>
)} )}
</Tab.Panel> </Tab.Panel>
</Tab.Panels> </Tab.Panels>

View File

@ -2,6 +2,7 @@ import { faChevronRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Image from "next/image"; import Image from "next/image";
import { Link as LinkType, Tag } from "@prisma/client"; import { Link as LinkType, Tag } from "@prisma/client";
import isValidUrl from "@/lib/client/isValidUrl";
interface LinksIncludingTags extends LinkType { interface LinksIncludingTags extends LinkType {
tags: Tag[]; tags: Tag[];
@ -13,7 +14,8 @@ type Props = {
}; };
export default function LinkCard({ link, count }: Props) { export default function LinkCard({ link, count }: Props) {
const url = new URL(link.url); const url = isValidUrl(link.url) ? new URL(link.url) : undefined;
const formattedDate = new Date( const formattedDate = new Date(
link.createdAt as unknown as string link.createdAt as unknown as string
).toLocaleString("en-US", { ).toLocaleString("en-US", {
@ -25,30 +27,34 @@ export default function LinkCard({ link, count }: Props) {
return ( return (
<a href={link.url} target="_blank" rel="noreferrer" className="rounded-3xl"> <a href={link.url} target="_blank" rel="noreferrer" className="rounded-3xl">
<div className="bg-gradient-to-tr from-slate-200 from-10% to-gray-50 via-20% shadow-md sm:hover:shadow-none duration-100 rounded-3xl cursor-pointer p-5 flex items-start relative gap-5 sm:gap-10 group/item"> <div className="bg-gradient-to-tr from-slate-200 from-10% to-gray-50 via-20% shadow-md sm:hover:shadow-none duration-100 rounded-3xl cursor-pointer p-5 flex items-start relative gap-5 sm:gap-10 group/item">
<Image {url && (
src={`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url.origin}&size=32`} <>
width={42} <Image
height={42} src={`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url.origin}&size=32`}
alt="" width={42}
className="select-none mt-3 z-10 rounded-full shadow border-[3px] border-white bg-white" height={42}
draggable="false" alt=""
onError={(e) => { className="select-none mt-3 z-10 rounded-full shadow border-[3px] border-white bg-white"
const target = e.target as HTMLElement; draggable="false"
target.style.opacity = "0"; onError={(e) => {
}} const target = e.target as HTMLElement;
/> target.style.display = "none";
<Image }}
src={`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url.origin}&size=32`} />
width={80} <Image
height={80} src={`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url.origin}&size=32`}
alt="" width={80}
className="blur-sm absolute left-2 opacity-40 select-none hidden sm:block" height={80}
draggable="false" alt=""
onError={(e) => { className="blur-sm absolute left-2 opacity-40 select-none hidden sm:block"
const target = e.target as HTMLElement; draggable="false"
target.style.opacity = "0"; onError={(e) => {
}} const target = e.target as HTMLElement;
/> target.style.display = "none";
}}
/>
</>
)}
<div className="flex justify-between items-center gap-5 w-full h-full z-0"> <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 flex-col justify-between">
<div className="flex items-baseline gap-1"> <div className="flex items-baseline gap-1">
@ -74,7 +80,7 @@ export default function LinkCard({ link, count }: Props) {
<div className="flex gap-2 items-center flex-wrap mt-2"> <div className="flex gap-2 items-center flex-wrap mt-2">
<p className="text-gray-500">{formattedDate}</p> <p className="text-gray-500">{formattedDate}</p>
<div className="text-sky-400 font-bold flex items-center gap-1"> <div className="text-sky-400 font-bold flex items-center gap-1">
<p>{url.host}</p> <p>{url ? url.host : link.url}</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -103,39 +103,43 @@ export default async function getLink(userId: number, body: string) {
query.searchQuery && !query.searchFilter?.tags query.searchQuery && !query.searchFilter?.tags
? undefined ? undefined
: { : {
some: { some: query.tagId
id: query.tagId ? query.tagId : undefined, // If tagId was defined, filter by tag ? {
name: // If tagId was defined, filter by tag
query.searchQuery && query.searchFilter?.tags id: query.tagId,
? { name:
contains: query.searchQuery, query.searchQuery && query.searchFilter?.tags
mode: "insensitive", ? {
} contains: query.searchQuery,
: undefined, mode: "insensitive",
OR: [ }
{ ownerId: userId }, // Tags owned by the user : undefined,
{ OR: [
links: { { ownerId: userId }, // Tags owned by the user
some: { {
name: { links: {
contains: some: {
query.searchQuery && query.searchFilter?.tags name: {
? query.searchQuery contains:
: undefined, query.searchQuery &&
mode: "insensitive", query.searchFilter?.tags
}, ? query.searchQuery
collection: { : undefined,
members: { mode: "insensitive",
some: { },
userId, // Tags from collections where the user is a member collection: {
members: {
some: {
userId, // Tags from collections where the user is a member
},
},
}, },
}, },
}, },
}, },
}, ],
}, }
], : undefined,
},
}, },
}, },
], ],
@ -153,5 +157,7 @@ export default async function getLink(userId: number, body: string) {
}, },
}); });
console.log(links);
return { response: links, status: 200 }; return { response: links, status: 200 };
} }

View File

@ -1,9 +1,13 @@
export default async function getTitle(url: string) { export default async function getTitle(url: string) {
const response = await fetch(url); try {
const text = await response.text(); const response = await fetch(url);
const text = await response.text();
// regular expression to find the <title> tag // regular expression to find the <title> tag
let match = text.match(/<title.*>([^<]*)<\/title>/); let match = text.match(/<title.*>([^<]*)<\/title>/);
if (match) return match[1] + " [AUTO GENERATED]"; if (match) return match[1] + " [AUTO GENERATED]";
else return ""; else return "";
} catch (err) {
console.log(err);
}
} }

8
lib/client/isValidUrl.ts Normal file
View File

@ -0,0 +1,8 @@
export default function isValidUrl(string: string) {
try {
new URL(string);
return true;
} catch (err) {
return false;
}
}

View File

@ -46,6 +46,8 @@ export default function Index() {
); );
}, [router, collections]); }, [router, collections]);
console.log(links);
return ( return (
<MainLayout> <MainLayout>
<div className="p-5 flex flex-col gap-5 w-full"> <div className="p-5 flex flex-col gap-5 w-full">

View File

@ -49,9 +49,12 @@ export default function PublicCollections() {
{data.name} {data.name}
</p> </p>
<hr className="mt-5 max-w-[30rem] mx-auto border-1 border-slate-400" /> {data.description && (
<>
<p className="mt-2 text-gray-500">{data.description}</p> <hr className="mt-5 max-w-[30rem] mx-auto border-1 border-slate-400" />
<p className="mt-2 text-gray-500">{data.description}</p>
</>
)}
</div> </div>
<div className="flex flex-col gap-5 my-8"> <div className="flex flex-col gap-5 my-8">