diff --git a/app/(protected)/admin/users/user-list.tsx b/app/(protected)/admin/users/user-list.tsx index 7a7df07..ea2fafb 100644 --- a/app/(protected)/admin/users/user-list.tsx +++ b/app/(protected)/admin/users/user-list.tsx @@ -162,7 +162,7 @@ export default function UsersList({ user }: UrlListProps) { key={user.id} className="grid animate-fade-in grid-cols-3 items-center animate-in sm:grid-cols-7" > - + diff --git a/app/(protected)/dashboard/records/record-list.tsx b/app/(protected)/dashboard/records/record-list.tsx index beac701..609ebb5 100644 --- a/app/(protected)/dashboard/records/record-list.tsx +++ b/app/(protected)/dashboard/records/record-list.tsx @@ -38,6 +38,7 @@ import CountUpFn from "@/components/dashboard/count-up"; import StatusDot from "@/components/dashboard/status-dot"; import { FormType, RecordForm } from "@/components/forms/record-form"; import { EmptyPlaceholder } from "@/components/shared/empty-placeholder"; +import { LinkPreviewer } from "@/components/shared/link-previewer"; export interface RecordListProps { user: Pick; @@ -211,16 +212,14 @@ export default function UserRecordsList({ user, action }: RecordListProps) { - - {record.name.endsWith(".wr.do") - ? record.name.slice(0, -6) - : record.name} - + diff --git a/app/(protected)/dashboard/scrape/meta-scraping.tsx b/app/(protected)/dashboard/scrape/meta-scraping.tsx index 2e77a05..eb4f92e 100644 --- a/app/(protected)/dashboard/scrape/meta-scraping.tsx +++ b/app/(protected)/dashboard/scrape/meta-scraping.tsx @@ -26,11 +26,22 @@ import { } from "@/components/ui/select"; import BlurImage from "@/components/shared/blur-image"; +export interface MetaScrapingProps { + title: string; + description: string; + image: string; + icon: string; + url: string; + lang: string; + author: string; + timestamp: string; +} + export default function MetaScraping() { const { theme } = useTheme(); const [currentLink, setCurrentLink] = useState("wr.do"); const [protocol, setProtocol] = useState("https://"); - const [metaInfo, setMetaInfo] = useState({ + const [metaInfo, setMetaInfo] = useState({ title: "", description: "", image: "", @@ -68,23 +79,21 @@ export default function MetaScraping() { }; const handleScrapingScreenshot = async () => { - if (currentScreenshotLink) { - setIsShoting(true); - const res = await fetch( - `/api/scraping/screenshot?url=${protocol}${currentScreenshotLink}`, - ); - console.log(res); - - if (!res.ok || res.status !== 200) { - toast.error(res.statusText); - } else { - const data = await res.json(); - console.log(data); - setScreenshotInfo(data); - toast.success("Success!"); - } - setIsShoting(false); - } + // if (currentScreenshotLink) { + // setIsShoting(true); + // const res = await fetch( + // `/api/scraping/screenshot?url=${protocol}${currentScreenshotLink}`, + // ); + // if (!res.ok || res.status !== 200) { + // toast.error(res.statusText); + // } else { + // const data = await res.json(); + // console.log(data); + // setScreenshotInfo(data); + // toast.success("Success!"); + // } + // setIsShoting(false); + // } }; return ( diff --git a/app/(protected)/dashboard/urls/url-list.tsx b/app/(protected)/dashboard/urls/url-list.tsx index 1abae9e..850be92 100644 --- a/app/(protected)/dashboard/urls/url-list.tsx +++ b/app/(protected)/dashboard/urls/url-list.tsx @@ -35,6 +35,7 @@ import { } from "@/components/ui/table"; import { Tooltip, + TooltipArrow, TooltipContent, TooltipProvider, TooltipTrigger, @@ -45,6 +46,7 @@ import { FormType } from "@/components/forms/record-form"; import { UrlForm } from "@/components/forms/url-form"; import { CopyButton } from "@/components/shared/copy-button"; import { EmptyPlaceholder } from "@/components/shared/empty-placeholder"; +import { LinkPreviewer } from "@/components/shared/link-previewer"; import UserUrlMetaInfo from "./meta"; @@ -219,21 +221,10 @@ export default function UserUrlsList({ user, action }: UrlListProps) { /> - - - - - {removeUrlSuffix(short.target)} - - - {short.target} - - + diff --git a/app/api/scraping/meta/route.ts b/app/api/scraping/meta/route.ts index 6f503e1..9c52fcf 100644 --- a/app/api/scraping/meta/route.ts +++ b/app/api/scraping/meta/route.ts @@ -4,7 +4,7 @@ import { checkUserStatus } from "@/lib/dto/user"; import { getCurrentUser } from "@/lib/session"; import { isLink } from "@/lib/utils"; -export const revalidate = 60; +export const revalidate = 600; export async function GET(req: Request) { try { diff --git a/components/shared/link-previewer.tsx b/components/shared/link-previewer.tsx new file mode 100644 index 0000000..1e8dc8f --- /dev/null +++ b/components/shared/link-previewer.tsx @@ -0,0 +1,103 @@ +import { useEffect, useState } from "react"; +import Link from "next/link"; +import BlogPost from "@/public/_static/illustrations/rocket-crashed.svg"; + +import { MetaScrapingProps } from "@/app/(protected)/dashboard/scrape/meta-scraping"; + +import { Skeleton } from "../ui/skeleton"; +import { + Tooltip, + TooltipArrow, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "../ui/tooltip"; +import BlurImage from "./blur-image"; + +export function LinkPreviewer({ + url, + formatUrl, +}: { + url: string; + formatUrl: string; +}) { + const [open, setOpen] = useState(false); + const [isScraping, setIsScraping] = useState(false); + const [metaInfo, setMetaInfo] = useState(null); + + const handleScrapingMeta = async () => { + if (url && open) { + setIsScraping(true); + const res = await fetch(`/api/scraping/meta?url=${url}`); + if (!res.ok || res.status !== 200) { + } else { + const data = await res.json(); + setMetaInfo(data); + } + setIsScraping(false); + } + }; + + useEffect(() => { + handleScrapingMeta(); + }, [url, open]); + + const renderLink = (text: string) => ( + + {text} + + ); + + return ( + + + setOpen(!open)} + > + {formatUrl} + + setOpen(false)} + onMouseLeave={() => setOpen(false)} + > + + {isScraping ? ( +
+ +

Previewing...

+
+ ) : metaInfo?.title !== "" ? ( +
+ +
+ {renderLink(metaInfo?.title ?? url)} +

+ {metaInfo?.description} +

+
+
+ ) : ( + <> +

+ Faild to preview link +

+ {renderLink(formatUrl ?? url)} + + )} +
+
+
+ ); +} +0; diff --git a/components/ui/tooltip.tsx b/components/ui/tooltip.tsx index 6a306b0..b0a4484 100644 --- a/components/ui/tooltip.tsx +++ b/components/ui/tooltip.tsx @@ -1,17 +1,19 @@ -"use client" +"use client"; -import * as React from "react" -import * as TooltipPrimitive from "@radix-ui/react-tooltip" +import * as React from "react"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; -import { cn } from "@/lib/utils" +import { cn } from "@/lib/utils"; -const TooltipProvider = TooltipPrimitive.Provider +const TooltipProvider = TooltipPrimitive.Provider; -const Tooltip = TooltipPrimitive.Root +const Tooltip = TooltipPrimitive.Root; -const TooltipTrigger = TooltipPrimitive.Trigger +const TooltipArrow = TooltipPrimitive.Arrow; -const TooltipPortal = TooltipPrimitive.Portal +const TooltipTrigger = TooltipPrimitive.Trigger; + +const TooltipPortal = TooltipPrimitive.Portal; const TooltipContent = React.forwardRef< React.ElementRef, @@ -22,11 +24,18 @@ const TooltipContent = React.forwardRef< sideOffset={sideOffset} className={cn( "z-[99999] overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1", - className + className, )} {...props} /> -)) -TooltipContent.displayName = TooltipPrimitive.Content.displayName +)); +TooltipContent.displayName = TooltipPrimitive.Content.displayName; -export { Tooltip, TooltipTrigger, TooltipContent, TooltipPortal, TooltipProvider } +export { + Tooltip, + TooltipTrigger, + TooltipContent, + TooltipPortal, + TooltipProvider, + TooltipArrow, +};