1
- import { Metadata } from 'next' ;
2
- import formatString from "@/app/functions/formatString " ;
1
+ import { db } from "@/lib/firebase" ;
2
+ import { collection , query , where , getDocs , orderBy } from "firebase/firestore " ;
3
3
import PostNavigation from "@/components/PostNavigation" ;
4
4
import SocialSharing from "@/components/SocialSharing" ;
5
5
import Link from "next/link" ;
6
- import { db } from "@/lib/firebase" ;
7
- import { collection , query , where , getDocs , orderBy } from "firebase/firestore" ;
6
+ import {
7
+ RiInstagramLine ,
8
+ RiTwitterFill ,
9
+ RiYoutubeFill ,
10
+ RiGithubFill ,
11
+ RiTiktokFill ,
12
+ RiPatreonFill ,
13
+ RiFacebookFill ,
14
+ RiLinkedinFill ,
15
+ RiDiscordFill ,
16
+ } from "react-icons/ri" ;
8
17
9
18
type AuthorData = {
10
19
uid : string ;
@@ -14,10 +23,8 @@ type AuthorData = {
14
23
avatar : string ;
15
24
imgAlt : string ;
16
25
slug : string ;
17
- biography : {
18
- summary : string ;
19
- body : string ;
20
- } ;
26
+ biography : { summary : string ; body : string } ;
27
+ socials ?: { [ key : string ] : string } ; // Social media map
21
28
} ;
22
29
23
30
type ArticleData = {
@@ -31,33 +38,6 @@ type ArticleData = {
31
38
authorUID : string ;
32
39
} ;
33
40
34
- export async function generateMetadata ( {
35
- params,
36
- } : {
37
- params : { author : string } ;
38
- } ) : Promise < Metadata > {
39
- const decodedAuthor = decodeURIComponent ( params . author ) ;
40
- try {
41
- const authorsRef = collection ( db , "authors" ) ;
42
- const q = query ( authorsRef , where ( "slug" , "==" , decodedAuthor ) ) ;
43
- const snapshot = await getDocs ( q ) ;
44
-
45
- if ( snapshot . empty ) {
46
- return { title : "Author Not Found | L.A.P Docs" } ;
47
- }
48
-
49
- const authorData = snapshot . docs [ 0 ] . data ( ) as AuthorData ;
50
- return {
51
- title : `${ authorData . name } | L.A.P Docs` ,
52
- description : authorData . biography . summary
53
- } ;
54
- } catch ( error ) {
55
- return {
56
- title : "Author Profile | L.A.P Docs"
57
- } ;
58
- }
59
- }
60
-
61
41
// Function to fetch author data
62
42
async function getAuthorData ( slug : string ) {
63
43
try {
@@ -66,19 +46,18 @@ async function getAuthorData(slug: string) {
66
46
const q = query ( authorsRef , where ( "slug" , "==" , decodedAuthor ) ) ;
67
47
const authorSnapshot = await getDocs ( q ) ;
68
48
69
- if ( authorSnapshot . empty ) {
70
- return null ;
71
- }
49
+ if ( authorSnapshot . empty ) return null ;
72
50
73
51
const authorData = authorSnapshot . docs [ 0 ] . data ( ) as AuthorData ;
52
+
74
53
const articlesQuery = query (
75
54
collection ( db , "articles" ) ,
76
55
where ( "authorUID" , "==" , authorData . uid ) ,
77
56
orderBy ( "date" , "desc" )
78
57
) ;
79
58
const articlesSnapshot = await getDocs ( articlesQuery ) ;
80
59
81
- const articles = articlesSnapshot . docs . map ( doc => ( {
60
+ const articles = articlesSnapshot . docs . map ( ( doc ) => ( {
82
61
uid : doc . id ,
83
62
...doc . data ( ) ,
84
63
date : doc . data ( ) . date ?. toDate ( ) || new Date ( ) ,
@@ -91,71 +70,74 @@ async function getAuthorData(slug: string) {
91
70
}
92
71
}
93
72
94
- // Non-async page component
73
+ // **Dynamically Map Social Links to Icons**
74
+ const SOCIAL_ICONS : { [ key : string ] : any } = {
75
+ youtube : RiYoutubeFill ,
76
+ github : RiGithubFill ,
77
+ instagram : RiInstagramLine ,
78
+ twitter : RiTwitterFill ,
79
+ tiktok : RiTiktokFill ,
80
+ patreon : RiPatreonFill ,
81
+ facebook : RiFacebookFill ,
82
+ linkedin : RiLinkedinFill ,
83
+ discord : RiDiscordFill ,
84
+ } ;
85
+
86
+ // **Non-async page component**
95
87
export default function Page ( { params } : { params : { author : string } } ) {
96
88
return < AuthorPage authorSlug = { params . author } /> ;
97
89
}
98
90
99
- // Async server component
91
+ // ** Async server component**
100
92
async function AuthorPage ( { authorSlug } : { authorSlug : string } ) {
101
93
const data = await getAuthorData ( authorSlug ) ;
102
-
94
+
103
95
if ( ! data ) {
104
96
return < div className = "p-8" > Author not found</ div > ;
105
97
}
106
-
98
+
107
99
const { author : authorData , articles } = data ;
108
100
101
+ // **Dynamically Generate Social Media Links**
102
+ const socialLinks = authorData . socials
103
+ ? Object . entries ( authorData . socials )
104
+ . filter ( ( [ platform , url ] ) => url ) // Remove empty links
105
+ . map ( ( [ platform , url ] ) => ( {
106
+ href : url ,
107
+ ariaLabel : `Visit ${ authorData . name } 's ${ platform } page` ,
108
+ Icon : SOCIAL_ICONS [ platform . toLowerCase ( ) ] || RiGithubFill , // Default to GitHub icon if unknown
109
+ } ) )
110
+ : [ ] ;
111
+
109
112
return (
110
113
< main className = "max-w-[95rem] w-full mx-auto px-4 sm:pt-4 xs:pt-2 lg:pb-4 md:pb-4 sm:pb-2 xs:pb-2" >
111
114
< PostNavigation href = "/authors" > Author</ PostNavigation >
112
-
115
+
113
116
< article className = "max-w-[75rem] w-full mx-auto grid lg:grid-cols-[300px_680px] gap-8 md:gap-6 justify-around" >
114
- { /* Author Profile Section */ }
117
+ { /* ** Author Profile Section** */ }
115
118
< div className = "w-fit" >
116
119
< img
117
120
src = { authorData . avatar }
118
121
alt = { authorData . imgAlt }
119
- className = "w-full max-w-[300px] h-auto"
122
+ className = "w-full max-w-[300px] h-auto rounded-full "
120
123
/>
121
- < div className = "flex justify-between border-t border-black mt-12 pt-6" >
122
- < p className = "uppercase font-semibold text-lg" > Follow</ p >
123
- < SocialSharing
124
- links = { [
125
- {
126
- href : "#" ,
127
- ariaLabel : "Visit our Instagram page" ,
128
- src : "/icons/ri_instagram-line.svg" ,
129
- alt : "Instagram logo" ,
130
- } ,
131
- {
132
- href : "#" ,
133
- ariaLabel : "Visit our Twitter page" ,
134
- src : "/icons/ri_twitter-fill.svg" ,
135
- alt : "Twitter logo" ,
136
- } ,
137
- {
138
- href : "#" ,
139
- ariaLabel : "Visit our YouTube page" ,
140
- src : "/icons/ri_youtube-fill.svg" ,
141
- alt : "YouTube logo" ,
142
- } ,
143
- ] }
144
- />
145
- </ div >
124
+ { socialLinks . length > 0 && (
125
+ < div className = "flex justify-between border-t border-white mt-12 pt-6" >
126
+ < p className = "uppercase font-semibold text-lg" > Follow:</ p >
127
+ < SocialSharing links = { socialLinks } />
128
+ </ div >
129
+ ) }
146
130
</ div >
147
131
148
- { /* Author Biography */ }
132
+ { /* ** Author Biography** */ }
149
133
< article >
150
134
< h1 className = "text-subheading pb-8" > { authorData . name } </ h1 >
151
- < p className = "text-blog-summary pb-12" >
152
- { authorData . biography . summary }
153
- </ p >
135
+ < p className = "text-blog-summary pb-12" > { authorData . biography . summary } </ p >
154
136
< p className = "text-blog-body" > { authorData . biography . body } </ p >
155
137
</ article >
156
138
</ article >
157
139
158
- { /* Author Articles */ }
140
+ { /* ** Author Articles** */ }
159
141
< div className = "pb-12 md:pb-48" >
160
142
< h2 className = "text-blog-subheading mt-[9.5rem] pt-12 pb-12 md:pb-24" >
161
143
Articles by { authorData . name }
@@ -166,6 +148,7 @@ async function AuthorPage({ authorSlug }: { authorSlug: string }) {
166
148
) ;
167
149
}
168
150
151
+ // **Component to Render Author’s Articles**
169
152
function AuthorArticles ( { articles } : { articles : ArticleData [ ] } ) {
170
153
if ( articles . length === 0 ) {
171
154
return (
@@ -176,55 +159,44 @@ function AuthorArticles({ articles }: { articles: ArticleData[] }) {
176
159
}
177
160
178
161
return (
179
- < div className = "grid md:grid-cols-2 border border-black border-collapse " >
162
+ < div className = "grid md:grid-cols-2" >
180
163
{ articles . map ( ( article ) => (
181
- < article
182
- className = "flex items-center gap-2 md:gap-12 p-8 border border-black"
183
- key = { article . uid }
184
- >
185
- < Link href = { `/magazine/${ article . slug } ` } className = "flex-shrink-0" >
164
+ < article className = "flex items-center gap-2 md:gap-12 p-8 border border-white" key = { article . uid } >
165
+ < Link href = { `/posts/${ article . slug } ` } className = "flex-shrink-0" >
186
166
< img
187
167
className = "h-[150px] w-[150px] object-cover hover:scale-105 transition-transform"
188
168
src = { article . img }
189
169
alt = { article . title }
190
170
/>
191
171
</ Link >
192
-
172
+
193
173
< div >
194
174
< p className = "heading3-title pb-4" >
195
- < Link
196
- href = { `/magazine/${ article . slug } ` }
197
- className = "hover:text-gray-600 transition-colors"
198
- >
175
+ < Link href = { `/posts/${ article . slug } ` } className = "hover:text-white transition-colors" >
199
176
{ article . title }
200
177
</ Link >
201
178
</ p >
202
-
179
+
203
180
< div className = "flex flex-wrap gap-4" >
204
181
< div className = "flex items-center" >
205
182
< p className = "font-semibold pr-2" > Date:</ p >
206
- < time
207
- dateTime = { article . date . toISOString ( ) }
208
- className = "text-gray-600"
209
- >
183
+ < time dateTime = { article . date . toISOString ( ) } className = "text-white" >
210
184
{ article . date . toLocaleDateString ( "en-US" , {
211
185
year : "numeric" ,
212
186
month : "long" ,
213
187
day : "numeric" ,
214
188
} ) }
215
189
</ time >
216
190
</ div >
217
-
191
+
218
192
< div className = "flex items-center" >
219
193
< p className = "font-semibold pr-2" > Category:</ p >
220
- < span className = "text-gray-600" >
221
- { formatString ( article . label ) }
222
- </ span >
194
+ < span className = "text-white" > { article . label } </ span >
223
195
</ div >
224
196
</ div >
225
197
</ div >
226
198
</ article >
227
199
) ) }
228
200
</ div >
229
201
) ;
230
- }
202
+ }
0 commit comments