Skip to content

Commit bd1e69a

Browse files
author
Oleksandr Pavlov
committed
up
1 parent c981049 commit bd1e69a

File tree

16 files changed

+276
-34
lines changed

16 files changed

+276
-34
lines changed

app/components/header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export function Header() {
3434
<Link
3535
key={link.title}
3636
href={link.href}
37-
className="p-1 font-medium text-gray-900 dark:text-gray-100 sm:p-4"
37+
className="link-underline rounded py-1 px-2 text-gray-900 hover:bg-gray-200 dark:text-gray-100 dark:hover:bg-gray-700 sm:py-2 sm:px-3"
3838
>
3939
{link.title}
4040
</Link>

app/components/icon/index.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import Image from 'next/image';
2+
3+
const FolderIcon = () => {
4+
return <div className="h-10 w-10 text-primary-color-500 dark:text-primary-color-dark-500">
5+
<svg
6+
xmlns="http://www.w3.org/2000/svg"
7+
role="img"
8+
viewBox="0 0 24 24"
9+
fill="none"
10+
stroke="currentColor"
11+
strokeWidth="1"
12+
strokeLinecap="round"
13+
strokeLinejoin="round"
14+
className="feather feather-folder"
15+
>
16+
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
17+
</svg>
18+
</div>
19+
}
20+
21+
export default FolderIcon

app/components/post-card.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Post } from "contentlayer/generated";
2+
import { compareDesc, format, parseISO } from "date-fns";
3+
import Link from "next/link";
4+
import Tag from "./tag";
5+
6+
export function PostCard(post: Post) {
7+
return (
8+
<div className="py-6">
9+
<div className="space-y-2 bg-transparent bg-opacity-20 p-2 transition duration-200 hover:rounded-xl hover:bg-gray-100 dark:hover:bg-gray-800 xl:grid xl:grid-cols-4 xl:items-baseline xl:space-y-0">
10+
<time dateTime={post.date} className="text-sm font-normal leading-6 text-gray-500 dark:text-gray-400">
11+
{format(parseISO(post.date), "LLLL d, yyyy")}
12+
</time>
13+
<div className="space-y-5 xl:col-span-4">
14+
<div className="space-y-1">
15+
<h2 className="text-2xl font-bold leading-8 tracking-tight">
16+
<Link
17+
href={post.url}
18+
className="text-gray-900 transition duration-500 ease-in-out hover:text-primary-500 dark:text-gray-100 dark:hover:text-primary-500"
19+
legacyBehavior>
20+
{post.title}
21+
</Link>
22+
</h2>
23+
</div>
24+
<div className="flex flex-wrap">
25+
{post.tags!.map((tag) => (
26+
<Tag key={tag} text={tag} />
27+
))}
28+
</div>
29+
</div>
30+
<div className="space-y-5 xl:col-span-4">
31+
<div className="max-w-none pt-5 text-gray-500 dark:text-gray-400">
32+
{post.summary}
33+
</div>
34+
</div>
35+
</div>
36+
37+
</div>
38+
);
39+
}

app/components/project-card.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import SocialIcon from './social-icons'
2+
import FolderIcon from './icon'
3+
4+
const Card = ({ title, description, imgSrc, href, github, tech1, tech2, tech3 } : any) => (
5+
<div className="md p-4 md:w-1/2" style={{ maxWidth: '544px' }}>
6+
<div className="h-full transform overflow-hidden rounded-md border-2 border-solid border-gray-200 bg-transparent bg-opacity-20 transition duration-500 hover:scale-105 hover:rounded-md hover:border-primary-500 hover:bg-gray-300 dark:border-gray-700 dark:hover:border-primary-500 dark:hover:bg-gray-800">
7+
<div className="p-6">
8+
<div className="flex flex-row items-center justify-between">
9+
<div className="my-2">
10+
<FolderIcon />
11+
</div>
12+
<div className="flex flex-row justify-between">
13+
<div className="mx-1.5">
14+
{href ? <SocialIcon kind="external" href={href} size={6} /> : null}
15+
</div>
16+
<div className="mx-1.5">
17+
{github ? <SocialIcon kind="github" href={github} size={6} /> : null}
18+
</div>
19+
</div>
20+
</div>
21+
<h2 className="mb-3 text-2xl font-bold leading-8 tracking-tight">{title}</h2>
22+
23+
<p className="prose mb-3 max-w-none text-gray-500 dark:text-gray-400">{description}</p>
24+
<div className="flex flex-row justify-between">
25+
<div className="text-sm text-gray-400">
26+
{tech1} &#8226; {tech2} &#8226; {tech3}
27+
</div>
28+
</div>
29+
</div>
30+
</div>
31+
</div>
32+
)
33+
34+
export default Card

app/components/social-icons/index.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {
2+
AiOutlineMail,
3+
AiOutlineGithub,
4+
AiOutlineFacebook,
5+
AiOutlineTwitter,
6+
AiOutlineGlobal,
7+
} from 'react-icons/ai'
8+
import { FaLinkedinIn } from 'react-icons/fa'
9+
import { FiExternalLink, FiMail } from 'react-icons/fi'
10+
11+
const components = {
12+
mail: FiMail,
13+
github: AiOutlineGithub,
14+
facebook: AiOutlineFacebook,
15+
linkedin: FaLinkedinIn,
16+
twitter: AiOutlineTwitter,
17+
website: AiOutlineGlobal,
18+
external: FiExternalLink,
19+
}
20+
21+
const SocialIcon = ({ kind, href, size = 8 }) => {
22+
if (!href || (kind === 'mail' && !/^mailto:\w+([.-]?\w+)@\w+([.-]?\w+)(.\w{2,3})+$/.test(href)))
23+
return null
24+
25+
const SocialSvg = components[kind]
26+
27+
return (
28+
<a
29+
className="text-sm text-gray-500 transition duration-200 hover:rotate-180 hover:text-gray-600"
30+
target="_blank"
31+
rel="noopener noreferrer"
32+
href={href}
33+
>
34+
<span className="sr-only">{kind}</span>
35+
<SocialSvg
36+
className={`text-gray-700 hover:text-primary-color-500 dark:text-gray-200 dark:hover:text-primary-color-dark-500 h-${size} w-${size}`}
37+
/>
38+
</a>
39+
)
40+
}
41+
42+
export default SocialIcon

app/components/tag.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Link from 'next/link'
2+
3+
const Tag = ({ text }: { text: string}) => {
4+
return (
5+
<Link href={`/tags/${text}`} legacyBehavior>
6+
<a className="mt-0 mr-3 rounded-lg border border-primary-500 py-1 px-3 text-sm font-medium uppercase text-primary-500 transition duration-500 ease-in-out hover:bg-primary-500 hover:text-gray-100 dark:hover:text-gray-900">
7+
{text}
8+
</a>
9+
</Link>
10+
)
11+
}
12+
13+
export default Tag;

app/page.tsx

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,12 @@ import Link from "next/link";
33
import { compareDesc, format, parseISO } from "date-fns";
44
import { allPosts, Post } from "contentlayer/generated";
55
import { RoughNotation } from 'react-rough-notation';
6-
6+
import { PostCard } from "./components/post-card";
77
const siteMetadata = {
88
title: 'Alex Pavlov - blog',
99
description: 'my personal blog where I share my notes, ideas and inspirations'
1010
}
1111

12-
function PostCard(post: Post) {
13-
return (
14-
<div className="py-6">
15-
<div className="space-y-2 bg-transparent bg-opacity-20 p-2 transition duration-200 hover:rounded-xl hover:bg-gray-100 dark:hover:bg-gray-800 xl:grid xl:grid-cols-4 xl:items-baseline xl:space-y-0">
16-
<time dateTime={post.date} className="text-sm font-normal leading-6 text-gray-500 dark:text-gray-400">
17-
{format(parseISO(post.date), "LLLL d, yyyy")}
18-
</time>
19-
<div className="space-y-5 xl:col-span-4">
20-
<div className="space-y-1">
21-
<h2 className="text-2xl font-bold leading-8 tracking-tight">
22-
<Link
23-
href={post.url}
24-
className="text-gray-900 transition duration-500 ease-in-out hover:text-primary-500 dark:text-gray-100 dark:hover:text-primary-500"
25-
legacyBehavior>
26-
{post.title}
27-
</Link>
28-
</h2>
29-
</div>
30-
</div>
31-
<div className="space-y-5 xl:col-span-4">
32-
<div className="max-w-none pt-5 text-gray-500 dark:text-gray-400">
33-
{post.summary}
34-
</div>
35-
</div>
36-
</div>
37-
38-
</div>
39-
);
40-
}
41-
4212
function AboutMe() {
4313
return (
4414
<div className="pt-6">
@@ -185,6 +155,8 @@ export default function Home() {
185155
Latest
186156
</h2>
187157
<hr className="border-gray-200 dark:border-gray-700" />
158+
{!posts.length && 'No posts found.'}
159+
188160
{posts.map((post, idx) => (
189161
<PostCard key={idx} {...post} />
190162
))}

app/posts/[slug]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const PostPage = ({ params }: { params: { slug: string } }) => {
2020
//console.log(post.body.code);
2121

2222
return (
23-
<article className="py-4 mx-auto max-w-xl">
23+
<article className="">
2424
<div className="mb-8 text-center">
2525
<time dateTime={post.date} className="mb-1 text-xs text-gray-600">
2626
{format(parseISO(post.date), 'LLLL d, yyyy')}

app/projects/page.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
'use client';
2+
import Card from "app/components/project-card";
3+
import Link from "next/link";
4+
const siteMetadata = {
5+
title: 'Alex Pavlov - blog',
6+
description: 'my personal blog where I share my notes, ideas and inspirations'
7+
}
8+
9+
const projectsData = [
10+
{
11+
title: 'WebReaper',
12+
description: `WebReaper is a declarative high performance web scraper, crawler and parser in C#. Designed as simple, extensible and scalable web scraping solution.`,
13+
imgSrc: '/public/images/reaper.png',
14+
github: 'https://github.com/pavlovtech/WebReaper',
15+
tech1: '.NET',
16+
tech2: 'Puppeteer',
17+
tech3: 'MongoDB',
18+
tech4: 'Redis'
19+
}
20+
];
21+
22+
export default function Projects() {
23+
return (
24+
<>
25+
<div className="mx-auto max-w-6xl divide-y divide-gray-400">
26+
<div className="space-y-2 pt-6 pb-8 md:space-y-5">
27+
<h1 className="text-3xl font-extrabold leading-9 tracking-tight text-gray-900 dark:text-gray-100 sm:text-4xl sm:leading-10 md:text-6xl md:leading-14">
28+
Projects
29+
</h1>
30+
<p className="text-md leading-7 text-gray-500 dark:text-gray-400">
31+
A list of projects I have been working on or built
32+
</p>
33+
</div>
34+
<div className="container py-12">
35+
<div className="-m-4 flex flex-wrap">
36+
{projectsData.map((d) => (
37+
<Card
38+
key={d.title}
39+
title={d.title}
40+
description={d.description}
41+
imgSrc={d.imgSrc}
42+
href={d.href}
43+
github={d.github}
44+
tech1={d.tech1}
45+
tech2={d.tech2}
46+
tech3={d.tech3}
47+
/>
48+
))}
49+
</div>
50+
</div>
51+
</div>
52+
</>
53+
)
54+
}

app/tags/[slug]/page.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { compareDesc, format, parseISO } from 'date-fns'
2+
import { Post, allPosts } from 'contentlayer/generated'
3+
import { getMDXComponent } from 'next-contentlayer/hooks'
4+
import { PostCard } from 'app/components/post-card';
5+
6+
const TagPage = ({ params }: { params: { slug: string } }) => {
7+
8+
const tag = params.slug;
9+
10+
const posts = allPosts.filter(post => post.tags?.includes(tag)).sort((a, b) =>
11+
compareDesc(new Date(a.date), new Date(b.date))
12+
);
13+
14+
return (
15+
<div>
16+
<h2 className="flex pb-6 text-3xl font-extrabold tracking-tight text-gray-900 dark:text-gray-100 sm:text-3xl md:text-5xl">
17+
{tag}
18+
</h2>
19+
<hr className="border-gray-200 dark:border-gray-700" />
20+
{!posts.length && 'No posts found.'}
21+
22+
{posts.map((post, idx) => (
23+
<PostCard key={idx} {...post} />
24+
))}
25+
</div>
26+
)
27+
}
28+
29+
export default TagPage

app/tags/page.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
'use client';
2+
import Link from "next/link";
3+
import { compareDesc, format, parseISO } from "date-fns";
4+
import { allPosts, Post } from "contentlayer/generated";
5+
import Tag from "../components/tag";
6+
7+
8+
export default function TagsPage() {
9+
const allTags = allPosts.map(post => post.tags).flat()!;
10+
11+
const tags = [...new Set(allTags)];
12+
13+
return (
14+
<div className="space-y-2 bg-transparent bg-opacity-20 p-2 transition duration-200 hover:rounded-xl hover:bg-gray-100 dark:hover:bg-gray-800 xl:grid xl:grid-cols-4 xl:items-baseline xl:space-y-0">
15+
{tags.map(tag => <Tag text={tag!} />)}
16+
</div>
17+
);
18+
}

contentlayer.config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ const Post = defineDocumentType(() => ({
3030
type: 'boolean',
3131
description: 'draft',
3232
required: false,
33+
},
34+
featured: {
35+
type: 'boolean',
36+
description: 'featured',
37+
required: false,
38+
},
39+
tags: {
40+
type: 'list',
41+
of: { type: 'string' },
42+
required: false
3343
}
3444
},
3545
computedFields: {

package-lock.json

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"next-contentlayer": "^0.3.2",
1818
"react": "18.2.0",
1919
"react-dom": "18.2.0",
20+
"react-icons": "^4.8.0",
2021
"react-rough-notation": "^1.0.3",
2122
"rehype-autolink-headings": "^6.1.1",
2223
"rehype-parse": "^8.0.4",

public/images/reaper.png

5.84 KB
Loading

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"compilerOptions": {
3-
"target": "es5",
3+
"target": "es2015",
44
"lib": ["dom", "dom.iterable", "esnext"],
55
"allowJs": true,
66
"skipLibCheck": true,

0 commit comments

Comments
 (0)