File tree Expand file tree Collapse file tree 8 files changed +175
-8
lines changed Expand file tree Collapse file tree 8 files changed +175
-8
lines changed Original file line number Diff line number Diff line change
1
+ "use server" ;
2
+
3
+ import type { Blog } from "@/types" ;
4
+
5
+ import { createClient } from "@/lib/supabase/server" ;
6
+
7
+ export const getBlogs = async ( ) : Promise < Blog [ ] > => {
8
+ const supabase = await createClient ( ) ;
9
+ const { data, error } = await supabase . from ( "blogs" ) . select ( "*" ) ;
10
+ if ( error ) throw new Error ( error . message ) ;
11
+ return data ;
12
+ } ;
13
+
14
+ export const getBlogBySlug = async ( slug : string ) : Promise < Blog | null > => {
15
+ const supabase = await createClient ( ) ;
16
+ const { data, error } = await supabase
17
+ . from ( "blogs" )
18
+ . select ( "*" )
19
+ . eq ( "slug" , slug )
20
+ . single ( ) ;
21
+
22
+ if ( error ) throw new Error ( error . message ) ;
23
+ return data ;
24
+ } ;
Original file line number Diff line number Diff line change
1
+ import { notFound } from "next/navigation" ;
2
+ import { getBlogBySlug } from "@/actions/blogs" ;
3
+ import { BlogDetail } from "@/components/blogs" ;
4
+
5
+ type Props = {
6
+ params : { slug : string } ;
7
+ } ;
8
+
9
+ export default async function BlogDetailPage ( { params } : Props ) {
10
+ const { slug } = await params ;
11
+ const blog = await getBlogBySlug ( slug ) ;
12
+ if ( ! blog ) return notFound ( ) ;
13
+ return < BlogDetail blog = { blog } /> ;
14
+ }
Original file line number Diff line number Diff line change @@ -2,22 +2,27 @@ import type { Metadata } from "next";
2
2
3
3
import { Heading } from "@/components/ui" ;
4
4
import { PROJECT_NAME } from "@/config/constants" ;
5
+ import { getBlogs } from "@/actions/blogs" ;
6
+ import { EmptyList } from "@/components/layout" ;
7
+ import { BlogList } from "@/components/blogs" ;
5
8
6
9
export const metadata : Metadata = {
7
10
title : `Blog - ${ PROJECT_NAME } ` ,
8
11
description : "Articles and posts" ,
9
12
} ;
10
13
11
- export default function BlogPage ( ) {
14
+ export default async function BlogPage ( ) {
15
+ const blogs = await getBlogs ( ) ;
16
+
17
+ const renderResources = ( ) => {
18
+ if ( blogs . length === 0 ) return < EmptyList type = "blogs" /> ;
19
+ return < BlogList blogs = { blogs } /> ;
20
+ } ;
21
+
12
22
return (
13
23
< main className = "max-w-screen-xl mx-auto p-4 flex flex-col gap-4" >
14
24
< Heading title = "Blog" subtitle = "Articles and posts" />
15
- < p >
16
- Lorem ipsum dolor sit amet consectetur adipisicing elit. Quae tenetur
17
- itaque tempora. Nostrum ut architecto libero! Quasi, iure placeat
18
- doloribus tenetur ullam veniam esse vero nemo! Molestias inventore velit
19
- praesentium!
20
- </ p >
25
+ { renderResources ( ) }
21
26
</ main >
22
27
) ;
23
28
}
Original file line number Diff line number Diff line change
1
+ "use client" ;
2
+
3
+ import type { Blog } from "@/types" ;
4
+
5
+ import Link from "next/link" ;
6
+ import { Card , CardFooter , Chip , Image } from "@heroui/react" ;
7
+
8
+ type Props = {
9
+ blog : Blog ;
10
+ } ;
11
+
12
+ export default function BlogCard ( { blog } : Props ) {
13
+ return (
14
+ < article className = "group" >
15
+ < Link href = { `/blog/${ blog . slug } ` } >
16
+ < Card
17
+ isBlurred
18
+ radius = "none"
19
+ className = "bg-white dark:bg-neutral-950 active:bg-neutral-800 transition-colors duration-1000 !outline-none shadow-none rounded-xl border-2 border-neutral-200 dark:border-neutral-800"
20
+ >
21
+ < figure className = "relative border-b-2 border-neutral-200 dark:border-neutral-800 overflow-hidden" >
22
+ < span className = "hidden group-hover:block absolute top-2 right-2 bg-neutral-950 dark:bg-white text-neutral-950 px-2 py-1 rounded-lg text-xs z-50" >
23
+ Uncategorized{ " " }
24
+ { /* TODO: Add category, tag or something called category */ }
25
+ </ span >
26
+ < Image
27
+ isBlurred
28
+ src = { blog . cover_url }
29
+ alt = { `Image by ${ blog . name } ` }
30
+ title = { blog . name }
31
+ width = { 400 }
32
+ height = { 200 }
33
+ loading = "lazy"
34
+ radius = "none"
35
+ className = "transform transition-transform duration-300 group-hover:scale-105"
36
+ />
37
+ </ figure >
38
+ < CardFooter >
39
+ < div className = "flex flex-col gap-2" >
40
+ < h2 className = "text-sm font-medium line-clamp-2" > { blog . name } </ h2 >
41
+ < div className = "flex flex-wrap gap-1" >
42
+ { blog . tags ?. map ( ( tag , idx ) => (
43
+ < Chip
44
+ key = { idx }
45
+ color = "default"
46
+ variant = "flat"
47
+ size = "sm"
48
+ radius = "sm"
49
+ >
50
+ < span className = "text-xs text-neutral-500" > { tag } </ span >
51
+ </ Chip >
52
+ ) ) }
53
+ </ div >
54
+ </ div >
55
+ </ CardFooter >
56
+ </ Card >
57
+ </ Link >
58
+ </ article >
59
+ ) ;
60
+ }
Original file line number Diff line number Diff line change
1
+ "use client" ;
2
+
3
+ import dynamic from "next/dynamic" ;
4
+ import { Blog } from "@/types" ;
5
+
6
+ const MarkdownPreview = dynamic (
7
+ ( ) => import ( "@uiw/react-markdown-preview" ) . then ( ( mod ) => mod . default ) ,
8
+ { ssr : false } ,
9
+ ) ;
10
+
11
+ type Props = {
12
+ blog : Blog ;
13
+ } ;
14
+
15
+ export default function BlogDetail ( { blog } : Props ) {
16
+ return (
17
+ < div className = "max-w-4xl mx-auto py-10 px-4 space-y-8" >
18
+ < h1 className = "text-3xl font-bold" > { blog . name } </ h1 >
19
+
20
+ { blog . cover_url && (
21
+ < img
22
+ src = { blog . cover_url }
23
+ alt = { `Image by ${ blog . name } ` }
24
+ className = "w-full max-h-96 object-cover rounded-xl shadow"
25
+ />
26
+ ) }
27
+
28
+ < div className = "text-sm text-gray-500" >
29
+ Publicated on { new Date ( blog . created_at ) . toLocaleDateString ( ) }
30
+ </ div >
31
+
32
+ < MarkdownPreview source = { blog . content } className = "prose max-w-none" />
33
+ </ div >
34
+ ) ;
35
+ }
Original file line number Diff line number Diff line change
1
+ import type { Blog } from "@/types" ;
2
+
3
+ import { BlogCard } from "@/components/blogs" ;
4
+
5
+ type Props = {
6
+ blogs : Blog [ ] ;
7
+ } ;
8
+
9
+ export default function BlogList ( { blogs } : Props ) {
10
+ return (
11
+ < section className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4" >
12
+ { blogs . map ( ( blog ) => (
13
+ < BlogCard key = { blog . id } blog = { blog } />
14
+ ) ) }
15
+ </ section >
16
+ ) ;
17
+ }
Original file line number Diff line number Diff line change @@ -10,7 +10,7 @@ const publicRoutes = [
10
10
"/blog" ,
11
11
"/faqs" ,
12
12
] ;
13
- const publicRoutePrefixes = [ "/auth/" , "/resources/" ] ;
13
+ const publicRoutePrefixes = [ "/auth/" , "/resources/" , "/blog" ] ;
14
14
15
15
export async function updateSession ( request : NextRequest ) {
16
16
let supabaseResponse = NextResponse . next ( {
Original file line number Diff line number Diff line change @@ -39,6 +39,18 @@ export type ResourceList = Pick<
39
39
"id" | "name" | "slug" | "image" | "tags" | "category"
40
40
> ;
41
41
42
+ export type Blog = {
43
+ id : string ;
44
+ name : string ;
45
+ slug : string ;
46
+ content : string ;
47
+ cover_url : string ;
48
+ excerpt : string ;
49
+ tags : string [ ] ;
50
+ author : string ;
51
+ created_at : string ;
52
+ } ;
53
+
42
54
export type Faq = {
43
55
id : string ;
44
56
question : string ;
You can’t perform that action at this time.
0 commit comments