Skip to content

Commit 6d89019

Browse files
committed
feat: ✨ /authors/[author] redesign to match /blog
1 parent f513146 commit 6d89019

1 file changed

Lines changed: 92 additions & 109 deletions

File tree

src/pages/authors/[author].tsx

Lines changed: 92 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import dayjs from 'dayjs';
2-
import { motion } from 'framer-motion';
2+
import { AnimatePresence, motion } from 'framer-motion';
33
import fs from 'fs';
44
import matter from 'gray-matter';
55
import { GetStaticPaths, GetStaticProps } from 'next';
6+
import Image from 'next/image';
67
import Link from 'next/link';
78
import { useRouter } from 'next/router';
89
import wordsCount from 'words-count';
@@ -38,109 +39,122 @@ interface BlogProps {
3839
};
3940
}
4041

41-
// The Author Page Content
4242
const Author: React.FC<BlogProps> = ({ filteredBlogList, authorInfo }) => {
4343
const router = useRouter();
4444
const { author } = router.query;
4545

4646
return (
47-
<section className="relative mx-auto mt-20 flex h-full w-full max-w-screen-lg flex-col overflow-hidden px-5 sm:px-10 sm:py-10">
47+
<div className="relative pt-24">
4848
<Seo
4949
templateTitle={`${authorInfo.name} - Authors`}
5050
templateUrl={`https://fairdataihub.org/authors/${author}`}
5151
templateDescription={`Blog posts by ${authorInfo.name} at FAIR Data Innovations Hub.`}
5252
templateImage="https://fairdataihub.org/thumbnails/index.png"
5353
/>
5454

55-
<div className="mb-5 px-2 pt-5 sm:pt-0 md:px-7">
56-
<h1 className="mb-2 text-left text-4xl font-bold sm:text-4xl">
57-
{filteredBlogList.length}
58-
{` `}
59-
{filteredBlogList.length === 1 ? `post` : `posts`} {` `}
60-
{`written by`} {` `}
61-
{authorInfo.name}
62-
</h1>
63-
<Link
64-
href={authorInfo.href}
65-
passHref
66-
className="text-url cursor-pointer hover:underline"
67-
target={authorInfo.external ? `_blank` : `_self`}
68-
rel={authorInfo.external ? `noopener noreferrer` : undefined}
69-
>
70-
<h2 className="text-url cursor-pointer text-left hover:underline">
71-
View profile
72-
</h2>
73-
</Link>
55+
<div aria-hidden className="pointer-events-none fixed inset-0 -z-10">
56+
<div className="absolute top-0 left-1/2 h-180 w-250 -translate-x-1/2 bg-[radial-gradient(ellipse_at_center,rgba(211,75,171,0.30),rgba(211,75,171,0.12)_40%,transparent_75%)] blur-3xl" />
7457
</div>
7558

76-
<hr className="mx-6 my-2 border-dashed border-slate-200" />
77-
78-
{filteredBlogList.map((post, idx) => {
79-
const { slug, frontMatter, timeToRead } = post;
80-
81-
return (
82-
<motion.div
83-
key={slug}
84-
layout
85-
className="my-2"
86-
initial={{ opacity: 0, y: 20, scale: 0.95 }}
87-
whileInView={{
88-
opacity: 1,
89-
y: 0,
90-
scale: 1,
91-
transition: {
92-
duration: 0.2,
93-
delay: idx * 0.02,
94-
ease: `easeOut`,
95-
},
96-
}}
97-
viewport={{
98-
once: true,
99-
amount: 0.1,
100-
margin: `0px 0px 150px 0px`,
101-
}}
102-
exit={{ opacity: 0, y: 10, scale: 0.98 }}
103-
>
104-
<BlogListItem
105-
slug={slug}
106-
title={frontMatter.title}
107-
subtitle={frontMatter.subtitle}
108-
date={frontMatter.date}
109-
timeToRead={timeToRead}
110-
heroImage={frontMatter.heroImage}
111-
imageAuthor={frontMatter.imageAuthor}
112-
tags={frontMatter.tags}
113-
category={frontMatter.category}
114-
/>
115-
</motion.div>
116-
);
117-
})}
118-
</section>
59+
<section className="container mx-auto max-w-7xl px-4 pt-8 pb-16">
60+
<motion.header
61+
initial={{ opacity: 0, y: 10 }}
62+
animate={{ opacity: 1, y: 0 }}
63+
transition={{ duration: 0.4, ease: `easeOut` }}
64+
className="mb-8 sm:mb-10"
65+
>
66+
<div className="flex items-center gap-4 sm:gap-5">
67+
<div className="relative h-16 w-16 shrink-0 overflow-hidden rounded-full ring-2 ring-slate-200 sm:h-20 sm:w-20">
68+
<Image
69+
src={authorInfo.avatar}
70+
alt={authorInfo.name}
71+
fill
72+
sizes="(min-width:640px) 80px, 64px"
73+
className="object-cover"
74+
/>
75+
</div>
76+
77+
<div>
78+
<h1 className="text-3xl font-black tracking-tight text-stone-900 sm:text-4xl dark:text-stone-100">
79+
{filteredBlogList.length}
80+
{` `}
81+
{filteredBlogList.length === 1 ? `post` : `posts`} by{` `}
82+
{authorInfo.name}
83+
</h1>
84+
<Link
85+
href={authorInfo.href}
86+
passHref
87+
className="text-url cursor-pointer text-sm hover:underline"
88+
target={authorInfo.external ? `_blank` : `_self`}
89+
rel={authorInfo.external ? `noopener noreferrer` : undefined}
90+
>
91+
View profile
92+
</Link>
93+
</div>
94+
</div>
95+
96+
<div className="via-primary/60 mt-6 h-px w-full bg-linear-to-r from-transparent to-transparent" />
97+
</motion.header>
98+
99+
<AnimatePresence mode="popLayout">
100+
<motion.ul layout initial={false} className="list-none space-y-4">
101+
{filteredBlogList.map(({ slug, frontMatter, timeToRead }, idx) => (
102+
<motion.div
103+
key={slug}
104+
layout
105+
initial={{ opacity: 0, y: 20, scale: 0.95 }}
106+
whileInView={{
107+
opacity: 1,
108+
y: 0,
109+
scale: 1,
110+
transition: {
111+
duration: 0.2,
112+
delay: idx * 0.02,
113+
ease: `easeOut`,
114+
},
115+
}}
116+
viewport={{
117+
once: true,
118+
amount: 0.1,
119+
margin: `0px 0px 150px 0px`,
120+
}}
121+
exit={{ opacity: 0, y: 10, scale: 0.98 }}
122+
>
123+
<BlogListItem
124+
slug={slug}
125+
title={frontMatter.title}
126+
subtitle={frontMatter.subtitle}
127+
date={frontMatter.date}
128+
timeToRead={timeToRead}
129+
heroImage={frontMatter.heroImage}
130+
imageAuthor={frontMatter.imageAuthor}
131+
tags={frontMatter.tags}
132+
category={frontMatter.category}
133+
authors={frontMatter.authors}
134+
/>
135+
</motion.div>
136+
))}
137+
</motion.ul>
138+
</AnimatePresence>
139+
</section>
140+
</div>
119141
);
120142
};
121143

122144
export const getStaticPaths: GetStaticPaths = async () => {
123-
// Get the posts from the `blog` directory
124145
const files = fs.readdirSync(`./blog`);
125146

126147
const blogList = files.map((fileName) => {
127-
// Read the raw content of the file and parse the frontMatter
128148
const rawFileContent = fs.readFileSync(`blog/${fileName}`, `utf-8`);
129-
130149
const { data: frontMatter } = matter(rawFileContent);
131-
132-
return {
133-
frontMatter,
134-
};
150+
return { frontMatter };
135151
});
136152

137153
const authorsList: string[] = [];
138154

139155
for (const post of blogList) {
140156
const { frontMatter } = post;
141-
142157
const { authors } = frontMatter;
143-
144158
if (authors) {
145159
authors.forEach((author: string) => {
146160
if (!authorsList.includes(author)) {
@@ -150,67 +164,36 @@ export const getStaticPaths: GetStaticPaths = async () => {
150164
}
151165
}
152166

153-
const paths = [];
154-
155-
for (const author of authorsList) {
156-
paths.push({
157-
params: {
158-
author,
159-
},
160-
});
161-
}
167+
const paths = authorsList.map((author) => ({ params: { author } }));
162168

163-
return {
164-
paths,
165-
fallback: false,
166-
};
169+
return { paths, fallback: false };
167170
};
168171

169172
export const getStaticProps: GetStaticProps = async ({ params }) => {
170-
// Get the posts from the `blog` directory
171173
const files = fs.readdirSync(`./blog`);
172174

173175
const blogList = files.map((fileName) => {
174-
// Remove the .md extension and use the file name as the slug
175176
const slug = fileName.replace(`.md`, ``);
176-
177-
// Read the raw content of the file and parse the frontMatter
178177
const rawFileContent = fs.readFileSync(`blog/${fileName}`, `utf-8`);
179178
const timeToRead = Math.ceil(wordsCount(rawFileContent) / 265);
180-
181179
const { data: frontMatter } = matter(rawFileContent);
182-
183-
return {
184-
slug,
185-
frontMatter,
186-
timeToRead,
187-
};
180+
return { slug, frontMatter, timeToRead };
188181
});
189182

190-
// sort the posts by date in descending order
191183
blogList.sort((a, b) => {
192184
const a_date: any = dayjs(a.frontMatter.date, `YYYY-MM-DD`);
193185
const b_date: any = dayjs(b.frontMatter.date, `YYYY-MM-DD`);
194-
195186
return b_date - a_date;
196187
});
197188

198189
const filteredBlogList = blogList.filter((post) => {
199190
const { authors } = post.frontMatter;
200-
201191
return authors && authors.includes(params?.author as string);
202192
});
203193

204-
// Get author information
205194
const authorInfo = authorsJSON[params?.author as string];
206195

207-
// Return the posts data to the page as props
208-
return {
209-
props: {
210-
filteredBlogList,
211-
authorInfo,
212-
},
213-
};
196+
return { props: { filteredBlogList, authorInfo } };
214197
};
215198

216199
export default Author;

0 commit comments

Comments
 (0)