added somestuffs

This commit is contained in:
Cypro Freelance 2024-08-08 22:53:56 +05:30
parent 2a31e73dfb
commit 7a27e2b79e
5 changed files with 324 additions and 77 deletions

View file

@ -4,6 +4,8 @@ import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { ArrowLeft } from "lucide-react"; import { ArrowLeft } from "lucide-react";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import NotFound from "@/app/not-found";
import { Metadata } from "next";
async function getData(slug: string) { async function getData(slug: string) {
const query = ` const query = `
@ -25,6 +27,48 @@ interface BlogSlugArticle {
titleImage: string; titleImage: string;
} }
export async function generateMetadata({
params,
}: {
params: { slug: string };
}): Promise<Metadata> {
const data = await getData(params.slug);
if (!data) {
return {
title: "Not Found",
description: "Blog post not found",
};
}
return {
title: data.title,
description: data.smallDescription,
openGraph: {
title: data.title,
description: data.smallDescription,
url: `https://svrjs.org/blog/${data.currentSlug}`,
type: "website",
images: [
{
url: urlFor(data.titleImage).url(),
width: 800,
height: 600,
alt: data.title,
},
],
},
twitter: {
card: "summary_large_image",
site: "@SVR_JS",
title: data.title,
description: data.smallDescription,
images: [urlFor(data.titleImage).url()],
creator: "@SVR_JS",
},
};
}
export default async function BlogSlugArticle({ export default async function BlogSlugArticle({
params, params,
}: { }: {
@ -32,36 +76,42 @@ export default async function BlogSlugArticle({
}) { }) {
const data: BlogSlugArticle = await getData(params.slug); const data: BlogSlugArticle = await getData(params.slug);
if (!data) {
return <NotFound />;
}
return ( return (
<section className="max-w-5xl container mx-auto py-8 md:py-28 flex flex-col items-center px-4"> <>
<Link <section className="max-w-5xl container mx-auto py-8 md:py-28 flex flex-col items-center px-4">
href="/blog" <Link
className="self-start mb-8 text-primary hover:text-green-300 transition-all flex items-center" href="/blog?page=1"
> className="self-start mb-8 text-primary hover:text-green-300 transition-all flex items-center"
<ArrowLeft className="mr-2" /> >
Back to Blog <ArrowLeft className="mr-2" />
</Link> Back to Blog
<header className="text-start mb-12 w-full"> </Link>
{data.titleImage && ( <header className="text-start mb-12 w-full">
<div className="mb-8"> {data.titleImage && (
<h1 className="text-3xl md:text-4xl mb-12 font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400"> <div className="mb-8">
{data.title} <h1 className="text-3xl md:text-5xl mb-12 font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400">
</h1> {data.title}
<Image </h1>
src={urlFor(data.titleImage).url()} <Image
alt={data.title} src={urlFor(data.titleImage).url()}
width={1200} alt={data.title}
height={800} width={1200}
priority height={800}
className="w-full h-auto object-cover rounded-md" priority
/> className="w-full h-auto object-cover rounded-md"
</div> />
)} </div>
</header> )}
<Separator className="mb-6" /> </header>
<article className="prose max-w-full md:prose-lg dark:prose-invert"> <Separator className="mb-6" />
<PortableText value={data.content} /> <article className="prose max-w-full md:prose-lg dark:prose-invert">
</article> <PortableText value={data.content} />
</section> </article>
</section>
</>
); );
} }

View file

@ -4,9 +4,41 @@ import BlogCards from "@/components/cards/BlogCards";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Blog - SVRJS", title: "Blog - SVRJS",
description:
"Welcome to the SVR.JS Blog! Explore our latest blog posts featuring web development, web application security, and web server administration tips. Stay tuned for the latest SVR.JS updates.",
openGraph: {
title: "Blog - SVRJS",
description:
"Welcome to the SVR.JS Blog! Explore our latest blog posts featuring web development, web application security, and web server administration tips. Stay tuned for the latest SVR.JS updates.",
url: "https://svrjs.org/blog",
type: "website",
images: [
{
url: "https://svrjs.vercel.app/metadata/svrjs-cover.png",
width: 800,
height: 600,
alt: "Blog - SVRJS",
},
],
},
twitter: {
card: "summary_large_image",
site: "@SVR_JS",
title: "Blog - SVRJS",
description:
"Welcome to the SVR.JS Blog! Explore our latest blog posts featuring web development, web application security, and web server administration tips. Stay tuned for the latest SVR.JS updates.",
images: ["https://svrjs.vercel.app/metadata/svrjs-cover.png"],
creator: "@SVR_JS",
},
}; };
const BlogPage = () => { const BlogPage = async ({
searchParams,
}: {
searchParams: { page?: string };
}) => {
// Optionally, you can fetch some initial data here if needed.
return ( return (
<section <section
id="blog" id="blog"
@ -15,7 +47,7 @@ const BlogPage = () => {
<h1 className="text-3xl md:text-5xl mb-12 pb-1 md:pb-2 font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400"> <h1 className="text-3xl md:text-5xl mb-12 pb-1 md:pb-2 font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400">
SVRJS Blog Post SVRJS Blog Post
</h1> </h1>
<BlogCards /> <BlogCards searchParams={searchParams} />
</section> </section>
); );
}; };

View file

@ -4,6 +4,14 @@ import Image from "next/image";
import { ExternalLink } from "lucide-react"; import { ExternalLink } from "lucide-react";
import { client, urlFor } from "@/lib/sanity"; import { client, urlFor } from "@/lib/sanity";
import { Card, CardContent } from "../ui/card"; import { Card, CardContent } from "../ui/card";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
interface BlogPostcard { interface BlogPostcard {
title: string; title: string;
@ -12,57 +20,95 @@ interface BlogPostcard {
titleImage: string; titleImage: string;
} }
async function getData() { interface BlogCardsProps {
searchParams: { page?: string };
}
const BlogCards: React.FC<BlogCardsProps> = async ({ searchParams }) => {
const cardsPerPage = 6;
const currentPage = searchParams.page ? parseInt(searchParams.page) : 1;
// Fetch the blog posts
const query = `*[_type == 'blog'] | order(_createdAt desc) { const query = `*[_type == 'blog'] | order(_createdAt desc) {
title, title,
smallDescription, smallDescription,
"currentSlug": slug.current, "currentSlug": slug.current,
titleImage titleImage
}`; }[${(currentPage - 1) * cardsPerPage}...${currentPage * cardsPerPage}]`;
const data = await client.fetch(query); const posts: BlogPostcard[] = await client.fetch(query);
return data; // Fetch the total number of blog posts
} const totalPostsQuery = `count(*[_type == 'blog'])`;
const totalPosts: number = await client.fetch(totalPostsQuery);
const BlogCards = async () => { const totalPages = Math.ceil(totalPosts / cardsPerPage);
const data: BlogPostcard[] = await getData();
console.log(data);
return ( return (
<section className="grid max-w-6xl gap-4 mx-auto sm:grid-cols-2 lg:grid-cols-3"> <>
{data.map((post, idx) => ( <section className="grid max-w-6xl gap-4 mx-auto sm:grid-cols-2 lg:grid-cols-3">
<Card {posts.map((post, idx) => (
className="group h-full w-full rounded-lg border overflow-hidden" <Card
key={idx} className="group h-full w-full rounded-lg border overflow-hidden"
> key={idx}
<Link href={`/blog/${post.currentSlug}`} className="block"> >
<div className="relative overflow-hidden rounded-t-lg"> <Link href={`/blog/${post.currentSlug}`} className="block">
<Image <div className="relative overflow-hidden rounded-t-lg">
src={urlFor(post.titleImage).url()} <Image
alt="SVRJS Blog Cover" src={urlFor(post.titleImage).url()}
width={500} alt={post.title}
height={300} width={500}
className="w-full object-cover transition-transform duration-200 group-hover:scale-105" height={300}
/> priority
</div> className="w-full object-cover transition-transform duration-200 group-hover:scale-105"
<CardContent className="p-4"> />
<div className="flex-between mb-2 py-2 ">
<h3 className="text-xl font-semibold leading-tight">
{post.title}
</h3>
<div className="text-sm text-muted-foreground opacity-0 group-hover:opacity-100 duration-300">
<ExternalLink />
</div>
</div> </div>
<p className="text-sm text-muted-foreground"> <CardContent className="p-4">
{post.smallDescription} <div className="flex-between mb-2 py-2">
</p> <h3 className="text-xl font-semibold leading-tight">
</CardContent> {post.title}
</Link> </h3>
</Card> <div className="text-sm text-muted-foreground opacity-0 group-hover:opacity-100 duration-300">
))} <ExternalLink />
</section> </div>
</div>
<p className="text-sm text-muted-foreground">
{post.smallDescription}
</p>
</CardContent>
</Link>
</Card>
))}
</section>
<div className="flex-center mt-12">
{totalPages > 1 && (
<Pagination>
<PaginationContent>
<PaginationItem>
{currentPage > 1 && (
<PaginationPrevious href={`?page=${currentPage - 1}`} />
)}
</PaginationItem>
{Array.from({ length: totalPages }).map((_, i) => (
<PaginationItem key={i}>
<PaginationLink
href={`?page=${i + 1}`}
isActive={currentPage === i + 1}
>
{i + 1}
</PaginationLink>
</PaginationItem>
))}
<PaginationItem>
{currentPage < totalPages && (
<PaginationNext href={`?page=${currentPage + 1}`} />
)}
</PaginationItem>
</PaginationContent>
</Pagination>
)}
</div>
</>
); );
}; };

View file

@ -0,0 +1,117 @@
import * as React from "react"
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
import { cn } from "@/lib/utils"
import { ButtonProps, buttonVariants } from "@/components/ui/button"
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
<nav
role="navigation"
aria-label="pagination"
className={cn("mx-auto flex w-full justify-center", className)}
{...props}
/>
)
Pagination.displayName = "Pagination"
const PaginationContent = React.forwardRef<
HTMLUListElement,
React.ComponentProps<"ul">
>(({ className, ...props }, ref) => (
<ul
ref={ref}
className={cn("flex flex-row items-center gap-1", className)}
{...props}
/>
))
PaginationContent.displayName = "PaginationContent"
const PaginationItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<"li">
>(({ className, ...props }, ref) => (
<li ref={ref} className={cn("", className)} {...props} />
))
PaginationItem.displayName = "PaginationItem"
type PaginationLinkProps = {
isActive?: boolean
} & Pick<ButtonProps, "size"> &
React.ComponentProps<"a">
const PaginationLink = ({
className,
isActive,
size = "icon",
...props
}: PaginationLinkProps) => (
<a
aria-current={isActive ? "page" : undefined}
className={cn(
buttonVariants({
variant: isActive ? "outline" : "ghost",
size,
}),
className
)}
{...props}
/>
)
PaginationLink.displayName = "PaginationLink"
const PaginationPrevious = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("gap-1 pl-2.5", className)}
{...props}
>
<ChevronLeft className="h-4 w-4" />
<span>Previous</span>
</PaginationLink>
)
PaginationPrevious.displayName = "PaginationPrevious"
const PaginationNext = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("gap-1 pr-2.5", className)}
{...props}
>
<span>Next</span>
<ChevronRight className="h-4 w-4" />
</PaginationLink>
)
PaginationNext.displayName = "PaginationNext"
const PaginationEllipsis = ({
className,
...props
}: React.ComponentProps<"span">) => (
<span
aria-hidden
className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More pages</span>
</span>
)
PaginationEllipsis.displayName = "PaginationEllipsis"
export {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
}

View file

@ -1,12 +1,14 @@
import { createClient } from "next-sanity"; import { createClient } from "next-sanity";
import imageUrlBuilder from "@sanity/image-url"; import imageUrlBuilder from "@sanity/image-url";
export const client = createClient({ const config = {
apiVersion: "2023-05-03", apiVersion: "2023-08-08",
dataset: "production", dataset: "production",
projectId: `${process.env.SANITY_PROJECT_ID}`, projectId: `${process.env.SANITY_PROJECT_ID}`,
useCdn: false, // basically enable this for faster loading time useCdn: false, // ensure fresh data
}); };
export const client = createClient(config);
const builder = imageUrlBuilder(client); const builder = imageUrlBuilder(client);