@@ -11,15 +11,21 @@ import {
11
11
Text ,
12
12
} from '@radix-ui/themes' ;
13
13
import { IconArrowLeft } from '@tabler/icons-react' ;
14
+ import { type Metadata } from 'next' ;
14
15
import Image from 'next/image' ;
15
16
import Link from 'next/link' ;
16
17
import { type FC } from 'react' ;
17
18
19
+ import { Footer } from '@/components/footer' ;
18
20
import { firstName , lastName } from '@/constants/me' ;
19
21
import { routes } from '@/constants/site-config' ;
20
22
import { getArticles } from '@/lib/queries' ;
21
23
import { dateFormatter } from '@/lib/utils' ;
22
24
25
+ const sm = 768 ;
26
+ const md = 1024 ;
27
+ const containerMaxWidth = 1136 ;
28
+
23
29
const BlogPage : FC = async ( ) => {
24
30
const articles = await getArticles ( ) ;
25
31
const featuredArticle = articles . reduce ( ( highestViewedArticle , article ) =>
@@ -28,121 +34,139 @@ const BlogPage: FC = async () => {
28
34
29
35
return (
30
36
< Container >
31
- < Section >
32
- < Button asChild highContrast size = "3" variant = "ghost" >
33
- < Link href = { routes . home . pathname } >
34
- < IconArrowLeft size = { 20 } />
35
- { firstName } { lastName }
36
- </ Link >
37
- </ Button >
38
- < Card asChild mt = "8" variant = "ghost" >
39
- < Link href = { `${ routes . blog . pathname } /${ featuredArticle . slug } ` } >
40
- < Grid asChild columns = { { sm : '2' } } gapX = "5" gapY = "4" >
41
- < article >
42
- < div >
43
- < Box
44
- className = "aspect-video rounded-[var(--card-border-radius)]"
45
- overflow = "hidden"
46
- position = "relative"
47
- style = { { boxShadow : 'var(--base-card-surface-box-shadow)' } }
48
- >
49
- { featuredArticle . coverPhoto ? (
50
- < Image
51
- fill
52
- alt = { featuredArticle . title }
53
- className = "object-cover"
54
- src = { featuredArticle . coverPhoto }
55
- sizes = { [
56
- `(min-width: 1136px) ${ 1136 / 2 } px` , // container size 4 max width = 1136px
57
- '(min-width: 768px) 50vw' , // breakpoint sm = 768px
58
- '100vw' ,
59
- ] . join ( ',' ) }
60
- />
37
+ < Section asChild >
38
+ < main >
39
+ < Button asChild highContrast size = "3" variant = "ghost" >
40
+ < Link href = { routes . home . pathname } >
41
+ < IconArrowLeft size = { 20 } />
42
+ { firstName } { lastName }
43
+ </ Link >
44
+ </ Button >
45
+ < Heading mt = "2" size = "9" >
46
+ Blog
47
+ </ Heading >
48
+ < Card asChild mt = "8" variant = "ghost" >
49
+ < Link href = { `${ routes . blog . pathname } /${ featuredArticle . slug } ` } >
50
+ < Grid asChild columns = { { sm : '2' } } gapX = "5" gapY = "4" >
51
+ < article >
52
+ < div >
53
+ < Box
54
+ className = "aspect-video rounded-[var(--card-border-radius)]"
55
+ overflow = "hidden"
56
+ position = "relative"
57
+ style = { {
58
+ boxShadow : 'var(--base-card-surface-box-shadow)' ,
59
+ } }
60
+ >
61
+ { featuredArticle . coverPhoto ? (
62
+ < Image
63
+ fill
64
+ alt = { featuredArticle . title }
65
+ className = "object-cover"
66
+ src = { featuredArticle . coverPhoto }
67
+ sizes = { [
68
+ `(min-width: ${ containerMaxWidth } px) ${ containerMaxWidth / 2 } px` ,
69
+ `(min-width: ${ sm } px) 50vw` ,
70
+ '100vw' ,
71
+ ] . join ( ',' ) }
72
+ />
73
+ ) : null }
74
+ </ Box >
75
+ </ div >
76
+ < Flex direction = "column" gap = { { initial : '2' , sm : '3' } } >
77
+ { featuredArticle . categories &&
78
+ featuredArticle . categories . length > 0 ? (
79
+ < Flex gap = "3" >
80
+ { featuredArticle . categories . map ( ( category ) => (
81
+ < Badge key = { category } size = "3" >
82
+ { category }
83
+ </ Badge >
84
+ ) ) }
85
+ </ Flex >
61
86
) : null }
62
- </ Box >
63
- </ div >
64
- < div >
65
- { featuredArticle . categories &&
66
- featuredArticle . categories . length > 0 ? (
67
- < Flex gap = "3" >
68
- { featuredArticle . categories . map ( ( category ) => (
69
- < Badge key = { category } size = "3" >
70
- { category }
71
- </ Badge >
72
- ) ) }
73
- </ Flex >
74
- ) : null }
75
- < Heading mt = "4" size = { { initial : '4' , xs : '6' , md : '8' } } >
76
- { featuredArticle . title }
77
- </ Heading >
78
- < Text
79
- className = "line-clamp-3"
80
- mt = "2"
81
- size = { { initial : '3' , md : '4' } }
82
- >
83
- { featuredArticle . description }
84
- </ Text >
85
- < Text color = "gray" mt = "2" size = { { initial : '2' , md : '3' } } >
86
- { dateFormatter . format ( featuredArticle . createdAt ) }
87
- </ Text >
88
- </ div >
89
- </ article >
90
- </ Grid >
91
- </ Link >
92
- </ Card >
93
- < Grid columns = { { xs : '2' , md : '3' } } gap = "5" mt = "8" >
94
- { articles
95
- . filter ( ( { id } ) => id !== featuredArticle . id )
96
- . map (
97
- ( { id , createdAt , coverPhoto , slug , title , categories = [ ] } ) => (
98
- < Card key = { id } asChild variant = "ghost" >
99
- < Link href = { ` ${ routes . blog . pathname } / ${ slug } ` } >
100
- < article >
101
- < Box
102
- className = "aspect-video rounded-[var(--card-border-radius)]"
103
- overflow = "hidden"
104
- position = "relative"
105
- style = { {
106
- boxShadow : 'var(--base-card-surface-box-shadow)' ,
107
- } }
108
- >
109
- { coverPhoto ? (
110
- < Image
111
- fill
112
- alt = { title }
113
- className = "object-cover"
114
- src = { coverPhoto }
115
- sizes = { [
116
- `(min-width: 1136px) ${ 1136 / 2 } px` , // container size 4 max width = 1136px
117
- '(min-width: 1024px) 33vw' , // breakpoint md = 1024px
118
- '(min-width: 768px) 50vw' , // breakpoint sm = 768px
119
- '100vw' ,
120
- ] . join ( ',' ) }
121
- / >
122
- ) : null }
123
- </ Box >
124
- { categories . length > 0 ? (
125
- < Flex gap = "2" mt = "3" wrap = "wrap" >
126
- { categories . map ( ( category ) => (
127
- < Badge key = { category } > { category } </ Badge >
128
- ) ) }
87
+ < Heading size = { { initial : '4' , xs : '6' , md : '8' } } >
88
+ { featuredArticle . title }
89
+ </ Heading >
90
+ < Text
91
+ className = "line-clamp-3"
92
+ size = { { initial : '3' , md : '4' } }
93
+ >
94
+ { featuredArticle . description }
95
+ </ Text >
96
+ < Text as = "p" color = "gray" size = { { initial : '2' , md : '3' } } >
97
+ { dateFormatter . format ( featuredArticle . createdAt ) }
98
+ </ Text >
99
+ </ Flex >
100
+ </ article >
101
+ </ Grid >
102
+ </ Link >
103
+ </ Card >
104
+ < Grid columns = { { xs : '2' , md : '3' } } gap = "5" mt = "8" >
105
+ { articles
106
+ . filter ( ( { id } ) => id !== featuredArticle . id )
107
+ . map (
108
+ ( {
109
+ id ,
110
+ createdAt ,
111
+ coverPhoto ,
112
+ slug ,
113
+ title ,
114
+ categories = [ ] ,
115
+ } ) => (
116
+ < Card key = { id } asChild variant = "ghost" >
117
+ < Link href = { ` ${ routes . blog . pathname } / ${ slug } ` } >
118
+ < article >
119
+ < Box
120
+ className = "aspect-video rounded-[var(--card-border-radius)]"
121
+ overflow = "hidden"
122
+ position = "relative"
123
+ style = { {
124
+ boxShadow : 'var(--base-card-surface-box-shadow)' ,
125
+ } }
126
+ >
127
+ { coverPhoto ? (
128
+ < Image
129
+ fill
130
+ alt = { title }
131
+ className = "object-cover"
132
+ src = { coverPhoto }
133
+ sizes = { [
134
+ `(min-width: ${ containerMaxWidth } px) ${ containerMaxWidth / 3 } px` ,
135
+ `(min-width: ${ md } px) 33vw` ,
136
+ `(min-width: ${ sm } px) 50vw` ,
137
+ '100vw' ,
138
+ ] . join ( ',' ) }
139
+ />
140
+ ) : null }
141
+ </ Box >
142
+ < Flex direction = "column" gap = "2" mt = "4" >
143
+ { categories . length > 0 ? (
144
+ < Flex gap = "2" wrap = "wrap" >
145
+ { categories . map ( ( category ) => (
146
+ < Badge key = { category } > { category } </ Badge >
147
+ ) ) }
148
+ </ Flex >
149
+ ) : null }
150
+ < Heading size = "3" > { title } </ Heading >
151
+ < Text as = "p" color = "gray" size = "2" >
152
+ { dateFormatter . format ( createdAt ) }
153
+ </ Text >
129
154
</ Flex >
130
- ) : null }
131
- < Heading mt = "3" size = "3" >
132
- { title }
133
- </ Heading >
134
- < Text color = "gray" mt = "2" size = "2" >
135
- { dateFormatter . format ( createdAt ) }
136
- </ Text >
137
- </ article >
138
- </ Link >
139
- </ Card >
140
- ) ,
141
- ) }
142
- </ Grid >
155
+ </ article >
156
+ </ Link >
157
+ </ Card >
158
+ ) ,
159
+ ) }
160
+ </ Grid >
161
+ </ main >
143
162
</ Section >
163
+ < Footer />
144
164
</ Container >
145
165
) ;
146
166
} ;
147
167
168
+ export const metadata = {
169
+ title : routes . blog . name ,
170
+ } satisfies Metadata ;
171
+
148
172
export default BlogPage ;
0 commit comments