added link preview card
This commit is contained in:
parent
06875d47a4
commit
5bd643e672
77
components/LinkCard/index.tsx
Normal file
77
components/LinkCard/index.tsx
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { fetchMetadata } from "@/lib/fetchers/metadata";
|
||||||
|
import { HiOutlineCheckBadge } from "react-icons/hi2";
|
||||||
|
|
||||||
|
type LinkCardProps = {
|
||||||
|
url: string;
|
||||||
|
metadata?: {
|
||||||
|
title: string;
|
||||||
|
image?: string;
|
||||||
|
description?: string;
|
||||||
|
creator?: string;
|
||||||
|
type?: string;
|
||||||
|
"theme-color"?: string;
|
||||||
|
};
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
export default function LinkCard({
|
||||||
|
url,
|
||||||
|
metadata: _metadata,
|
||||||
|
className,
|
||||||
|
}: LinkCardProps) {
|
||||||
|
const [metadata, setMetadata] = useState(_metadata);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!metadata) {
|
||||||
|
fetchMetadata(url)
|
||||||
|
?.then((r) => {
|
||||||
|
if (r) {
|
||||||
|
setMetadata(r.data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => console.log("fetch error"));
|
||||||
|
}
|
||||||
|
}, [url]);
|
||||||
|
if (metadata) {
|
||||||
|
return (
|
||||||
|
<a href={url} target="_blank" rel="nonreferrer">
|
||||||
|
<Card className="group">
|
||||||
|
{metadata.image && (
|
||||||
|
<div className="h-[150px] overflow-hidden rounded-t-md">
|
||||||
|
<Image
|
||||||
|
width={250}
|
||||||
|
height={150}
|
||||||
|
src={metadata.image}
|
||||||
|
alt={metadata.title}
|
||||||
|
unoptimized
|
||||||
|
className={cn(
|
||||||
|
"w-auto object-cover object-center transition-all group-hover:scale-105",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="">
|
||||||
|
<CardHeader className="">
|
||||||
|
<CardTitle className="line-clamp-2">{metadata.title}</CardTitle>
|
||||||
|
<CardDescription className="line-clamp-3">
|
||||||
|
{metadata.description}
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
31
lib/fetchers/metadata.ts
Normal file
31
lib/fetchers/metadata.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { validateUrl } from "@/lib/utils";
|
||||||
|
import { createZodFetcher } from "zod-fetch";
|
||||||
|
|
||||||
|
const fetchWithZod = createZodFetcher();
|
||||||
|
|
||||||
|
const metadataSchema = z.object({
|
||||||
|
title: z.string(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
image: z.string().optional(),
|
||||||
|
creator: z.string().optional(),
|
||||||
|
type: z.string().optional(),
|
||||||
|
"theme-color": z.string().optional(),
|
||||||
|
});
|
||||||
|
const metadataSchemaResponse = z.object({
|
||||||
|
data: metadataSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function fetchMetadata(url: string) {
|
||||||
|
if (!validateUrl(url)) return;
|
||||||
|
return fetchWithZod(
|
||||||
|
// The schema you want to validate with
|
||||||
|
metadataSchemaResponse,
|
||||||
|
// Any parameters you would usually pass to fetch
|
||||||
|
"/api/metadata",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({ url }),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user