major bug fixed + error handling
This commit is contained in:
parent
f1bd48be83
commit
92f6ee3942
|
@ -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">
|
||||||
|
|
|
@ -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) => {
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 };
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
export default function isValidUrl(string: string) {
|
||||||
|
try {
|
||||||
|
new URL(string);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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">
|
||||||
|
|
|
@ -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">
|
||||||
|
|
Ŝarĝante…
Reference in New Issue