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 usePermissions from "@/hooks/usePermissions";
|
||||
import { toast } from "react-hot-toast";
|
||||
import isValidUrl from "@/lib/client/isValidUrl";
|
||||
|
||||
type Props = {
|
||||
link: LinkIncludingShortenedCollectionAndTags;
|
||||
|
@ -78,7 +79,8 @@ export default function LinkCard({ link, count, className }: Props) {
|
|||
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(
|
||||
"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"
|
||||
>
|
||||
<Image
|
||||
src={`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url.origin}&size=32`}
|
||||
width={64}
|
||||
height={64}
|
||||
alt=""
|
||||
className="blur-sm absolute w-16 group-hover:opacity-80 duration-100 rounded-md bottom-5 right-5 opacity-60 select-none"
|
||||
draggable="false"
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
target.style.opacity = "0";
|
||||
}}
|
||||
/>
|
||||
{url && (
|
||||
<Image
|
||||
src={`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url.origin}&size=32`}
|
||||
width={64}
|
||||
height={64}
|
||||
alt=""
|
||||
className="blur-sm absolute w-16 group-hover:opacity-80 duration-100 rounded-md bottom-5 right-5 opacity-60 select-none"
|
||||
draggable="false"
|
||||
onError={(e) => {
|
||||
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 flex-col justify-between w-full">
|
||||
|
|
|
@ -24,7 +24,7 @@ type Props =
|
|||
activeLink: LinkIncludingShortenedCollectionAndTags;
|
||||
};
|
||||
|
||||
export default function EditLink({
|
||||
export default function AddOrEditLink({
|
||||
toggleLinkModal,
|
||||
method,
|
||||
activeLink,
|
||||
|
@ -70,8 +70,7 @@ export default function EditLink({
|
|||
}
|
||||
}, []);
|
||||
|
||||
const shortendURL =
|
||||
method === "UPDATE" ? new URL(link.url).host.toLowerCase() : undefined;
|
||||
// const shortendURL = method === "UPDATE" ? new URL(link.url).host.toLowerCase() : undefined;
|
||||
|
||||
const setTags = (e: any) => {
|
||||
const tagNames = e.map((e: any) => {
|
|
@ -19,6 +19,7 @@ import {
|
|||
faFileImage,
|
||||
faFilePdf,
|
||||
} from "@fortawesome/free-regular-svg-icons";
|
||||
import isValidUrl from "@/lib/client/isValidUrl";
|
||||
|
||||
type Props = {
|
||||
link: LinkIncludingShortenedCollectionAndTags;
|
||||
|
@ -56,7 +57,7 @@ export default function LinkDetails({ link }: Props) {
|
|||
|
||||
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 =>
|
||||
"#" +
|
||||
|
@ -123,7 +124,7 @@ export default function LinkDetails({ link }: Props) {
|
|||
<div
|
||||
className={`relative flex gap-5 items-start ${!imageError && "-mt-24"}`}
|
||||
>
|
||||
{!imageError && (
|
||||
{!imageError && url && (
|
||||
<Image
|
||||
src={`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url.origin}&size=32`}
|
||||
width={42}
|
||||
|
@ -160,7 +161,7 @@ export default function LinkDetails({ link }: Props) {
|
|||
rel="noreferrer"
|
||||
className="text-sm text-gray-500 break-all hover:underline cursor-pointer w-fit"
|
||||
>
|
||||
{url.host}
|
||||
{url ? url.host : link.url}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Tab } from "@headlessui/react";
|
||||
import { LinkIncludingShortenedCollectionAndTags } from "@/types/global";
|
||||
import EditLink from "./EditLink";
|
||||
import AddOrEditLink from "./AddOrEditLink";
|
||||
import LinkDetails from "./LinkDetails";
|
||||
|
||||
type Props =
|
||||
|
@ -72,13 +72,16 @@ export default function LinkModal({
|
|||
|
||||
<Tab.Panel>
|
||||
{activeLink && method === "UPDATE" ? (
|
||||
<EditLink
|
||||
<AddOrEditLink
|
||||
toggleLinkModal={toggleLinkModal}
|
||||
method="UPDATE"
|
||||
activeLink={activeLink}
|
||||
/>
|
||||
) : (
|
||||
<EditLink toggleLinkModal={toggleLinkModal} method="CREATE" />
|
||||
<AddOrEditLink
|
||||
toggleLinkModal={toggleLinkModal}
|
||||
method="CREATE"
|
||||
/>
|
||||
)}
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
|
|
|
@ -2,6 +2,7 @@ import { faChevronRight } from "@fortawesome/free-solid-svg-icons";
|
|||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import Image from "next/image";
|
||||
import { Link as LinkType, Tag } from "@prisma/client";
|
||||
import isValidUrl from "@/lib/client/isValidUrl";
|
||||
|
||||
interface LinksIncludingTags extends LinkType {
|
||||
tags: Tag[];
|
||||
|
@ -13,7 +14,8 @@ type 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(
|
||||
link.createdAt as unknown as string
|
||||
).toLocaleString("en-US", {
|
||||
|
@ -25,30 +27,34 @@ export default function LinkCard({ link, count }: Props) {
|
|||
return (
|
||||
<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">
|
||||
<Image
|
||||
src={`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url.origin}&size=32`}
|
||||
width={42}
|
||||
height={42}
|
||||
alt=""
|
||||
className="select-none mt-3 z-10 rounded-full shadow border-[3px] border-white bg-white"
|
||||
draggable="false"
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
target.style.opacity = "0";
|
||||
}}
|
||||
/>
|
||||
<Image
|
||||
src={`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url.origin}&size=32`}
|
||||
width={80}
|
||||
height={80}
|
||||
alt=""
|
||||
className="blur-sm absolute left-2 opacity-40 select-none hidden sm:block"
|
||||
draggable="false"
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLElement;
|
||||
target.style.opacity = "0";
|
||||
}}
|
||||
/>
|
||||
{url && (
|
||||
<>
|
||||
<Image
|
||||
src={`https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=${url.origin}&size=32`}
|
||||
width={42}
|
||||
height={42}
|
||||
alt=""
|
||||
className="select-none mt-3 z-10 rounded-full shadow border-[3px] border-white bg-white"
|
||||
draggable="false"
|
||||
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}
|
||||
height={80}
|
||||
alt=""
|
||||
className="blur-sm absolute left-2 opacity-40 select-none hidden sm:block"
|
||||
draggable="false"
|
||||
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 flex-col justify-between">
|
||||
<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">
|
||||
<p className="text-gray-500">{formattedDate}</p>
|
||||
<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>
|
||||
|
|
|
@ -103,39 +103,43 @@ export default async function getLink(userId: number, body: string) {
|
|||
query.searchQuery && !query.searchFilter?.tags
|
||||
? undefined
|
||||
: {
|
||||
some: {
|
||||
id: query.tagId ? query.tagId : undefined, // If tagId was defined, filter by tag
|
||||
name:
|
||||
query.searchQuery && query.searchFilter?.tags
|
||||
? {
|
||||
contains: query.searchQuery,
|
||||
mode: "insensitive",
|
||||
}
|
||||
: undefined,
|
||||
OR: [
|
||||
{ ownerId: userId }, // Tags owned by the user
|
||||
{
|
||||
links: {
|
||||
some: {
|
||||
name: {
|
||||
contains:
|
||||
query.searchQuery && query.searchFilter?.tags
|
||||
? query.searchQuery
|
||||
: undefined,
|
||||
mode: "insensitive",
|
||||
},
|
||||
collection: {
|
||||
members: {
|
||||
some: {
|
||||
userId, // Tags from collections where the user is a member
|
||||
some: query.tagId
|
||||
? {
|
||||
// If tagId was defined, filter by tag
|
||||
id: query.tagId,
|
||||
name:
|
||||
query.searchQuery && query.searchFilter?.tags
|
||||
? {
|
||||
contains: query.searchQuery,
|
||||
mode: "insensitive",
|
||||
}
|
||||
: undefined,
|
||||
OR: [
|
||||
{ ownerId: userId }, // Tags owned by the user
|
||||
{
|
||||
links: {
|
||||
some: {
|
||||
name: {
|
||||
contains:
|
||||
query.searchQuery &&
|
||||
query.searchFilter?.tags
|
||||
? query.searchQuery
|
||||
: undefined,
|
||||
mode: "insensitive",
|
||||
},
|
||||
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 };
|
||||
}
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
export default async function getTitle(url: string) {
|
||||
const response = await fetch(url);
|
||||
const text = await response.text();
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
const text = await response.text();
|
||||
|
||||
// regular expression to find the <title> tag
|
||||
let match = text.match(/<title.*>([^<]*)<\/title>/);
|
||||
if (match) return match[1] + " [AUTO GENERATED]";
|
||||
else return "";
|
||||
// regular expression to find the <title> tag
|
||||
let match = text.match(/<title.*>([^<]*)<\/title>/);
|
||||
if (match) return match[1] + " [AUTO GENERATED]";
|
||||
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]);
|
||||
|
||||
console.log(links);
|
||||
|
||||
return (
|
||||
<MainLayout>
|
||||
<div className="p-5 flex flex-col gap-5 w-full">
|
||||
|
|
|
@ -49,9 +49,12 @@ export default function PublicCollections() {
|
|||
{data.name}
|
||||
</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>
|
||||
{data.description && (
|
||||
<>
|
||||
<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 className="flex flex-col gap-5 my-8">
|
||||
|
|
Ŝarĝante…
Reference in New Issue