Skip to content

Commit

Permalink
Merge pull request #34 from firehawk89/add-metadata
Browse files Browse the repository at this point in the history
Add Metadata
  • Loading branch information
firehawk89 authored Feb 13, 2024
2 parents 5a2302d + 5c777c6 commit 297e42f
Show file tree
Hide file tree
Showing 17 changed files with 1,648 additions and 32 deletions.
4 changes: 3 additions & 1 deletion project-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ Skauna
hookform
sonner
Bootcamp
Udemy
Udemy
Csvg
Cpath
Binary file added public/fonts/Raleway-Medium.ttf
Binary file not shown.
Binary file added public/fonts/Raleway-SemiBold.ttf
Binary file not shown.
1 change: 1 addition & 0 deletions public/hero-bg.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 31 additions & 4 deletions src/app/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
import ProjectPageChips from '@/components/project-page-chips'
import Card from '@/components/ui/card'
import Chips from '@/components/ui/chips/chips'
import ChipsItem from '@/components/ui/chips/chips-item'
import Content from '@/components/ui/content'
import Heading from '@/components/ui/heading'
import IconsList from '@/components/ui/icons-list/icons-list'
import IconsListItem from '@/components/ui/icons-list/icons-list-item'
import ImagePlaceholder from '@/components/ui/image-placeholder'
import { LINK } from '@/types/enums/Link'
import { SITE_URL } from '@/utils'
import { getProject, getSlugs } from '@/utils/projects'
import Image from 'next/image'
import Link from 'next/link'
import { BiArrowBack } from 'react-icons/bi'
import { FaGithub } from 'react-icons/fa'
import { TfiWorld } from 'react-icons/tfi'

type ProjectPageProps = {
params: { slug: string }
}

export async function generateMetadata({ params: { slug } }: ProjectPageProps) {
const { body, title } = await getProject(slug)

return {
description: body,
openGraph: {
images: {
alt: `${title || slug}`,
height: 630,
type: 'image/png',
url: `${SITE_URL}/api/og?title=${title || slug}${body && `&description=${body}`}`,
width: 1200,
},
},
title,
}
}

export async function generateStaticParams() {
const slugs = await getSlugs()
return slugs.map((slug) => ({ slug }))
Expand All @@ -28,7 +48,14 @@ export default async function Project({ params: { slug } }: ProjectPageProps) {
return (
<section className="pt-header">
<Content className="pb-16 pt-12 md:pb-24 md:pt-20" size="tight">
<article className="flex flex-col gap-5 md:gap-7">
<Link
className="group flex items-center gap-2 text-accent transition-colors hover:text-accent-light md:text-lg"
href={LINK.projects}
>
<BiArrowBack className="h-5 w-5 transition-transform group-hover:-translate-x-1" />{' '}
Go Back to Projects
</Link>
<article className="mt-3 flex flex-col gap-5 md:mt-5 md:gap-7">
<header>
{website || github ? (
<div className="flex items-center justify-between">
Expand All @@ -55,7 +82,7 @@ export default async function Project({ params: { slug } }: ProjectPageProps) {
)}
{body && (
<p
className="mt-7 md:text-lg"
className="mt-5 md:mt-7 md:text-lg"
dangerouslySetInnerHTML={{ __html: body }}
/>
)}
Expand Down
1,485 changes: 1,485 additions & 0 deletions src/app/api/og/route.tsx

Large diffs are not rendered by default.

Binary file modified src/app/favicon.ico
Binary file not shown.
21 changes: 17 additions & 4 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
// TODO: Adjust site metadata
// TODO: Add 404 page

import type { Metadata } from 'next'

import Footer from '@/components/footer'
import Header from '@/components/header/header'
import Providers from '@/store/providers'
import { cn } from '@/utils'
import { SITE_URL, cn } from '@/utils'

import { raleway } from './fonts'
import './globals.css'

export const metadata: Metadata = {
description: 'Generated by create next app',
title: 'Create Next App',
description:
"Hi, my name is Anton Bochkovskyi and I'm a Front-End Developer specializing in React, Next.js, and Tailwind CSS, with a keen focus on crafting responsive and visually stunning web applications.",
metadataBase: new URL(SITE_URL),
openGraph: {
images: {
alt: 'Anton Bochkovskyi - Front-End Developer',
height: 630,
type: 'image/png',
url: `${SITE_URL}/api/og?title=Anton%20Bochkovskyi%20-%20Front-End%20Developer`,
width: 1200,
},
},
title: {
default: 'Anton Bochkovskyi - Front-End Developer',
template: '%s | Anton Bochkovskyi - Front-End Developer',
},
}

export default function RootLayout({
Expand Down
12 changes: 12 additions & 0 deletions src/app/robots.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { SITE_URL } from '@/utils'
import { MetadataRoute } from 'next'

export default function robots(): MetadataRoute.Robots {
return {
rules: {
allow: '/',
userAgent: '*',
},
sitemap: `${SITE_URL}/sitemap.xml`,
}
}
27 changes: 27 additions & 0 deletions src/app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { SITE_URL } from '@/utils'
import { getProjectsMetadata } from '@/utils/projects'
import { MetadataRoute } from 'next'

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const projectsMetadata = await getProjectsMetadata()

const sitemapData: MetadataRoute.Sitemap = [
{
changeFrequency: 'monthly',
lastModified: new Date(),
priority: 1,
url: SITE_URL,
},
]

for (const project of projectsMetadata) {
sitemapData.push({
changeFrequency: 'weekly',
lastModified: project.lastModified,
priority: 0.8,
url: `${SITE_URL}/${project.slug}`,
})
}

return sitemapData
}
11 changes: 6 additions & 5 deletions src/components/header/header-navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import MenuItem from '@/components/header/menu/menu-item'
import Socials from '@/components/ui/socials'
import useMediaQuery from '@/hooks/use-media-query'
import HeaderContext from '@/store/header-context'
import { LINK } from '@/types/enums/Link'
import { MOBILE_BREAKPOINT, cn } from '@/utils'
import { FC, HTMLAttributes, useContext } from 'react'

Expand All @@ -17,8 +18,8 @@ const HeaderNavbar: FC<HeaderNavbarProps> = ({ className, ...props }) => {
return (
<nav
className={cn(
'fixed left-0 right-0 top-0 -translate-y-full border-b border-accent bg-light pb-10 pt-20 transition-transform duration-500 dark:bg-dark md:static md:translate-y-0 md:border-none md:bg-transparent md:p-0 md:dark:bg-transparent',
isMenuOpened && 'translate-y-0',
'fixed left-0 right-0 top-0 -translate-y-full border-b border-accent bg-light pb-10 pt-20 transition-transform duration-500 *:opacity-0 *:transition-opacity dark:bg-dark md:static md:translate-y-0 md:border-none md:bg-transparent md:p-0 md:*:opacity-100 md:dark:bg-transparent',
isMenuOpened && 'translate-y-0 *:opacity-100',
className
)}
{...props}
Expand All @@ -27,9 +28,9 @@ const HeaderNavbar: FC<HeaderNavbarProps> = ({ className, ...props }) => {
className="mx-auto uppercase"
orientation={isMobile ? 'vertical' : 'horizontal'}
>
<MenuItem href="/#about-me">About Me</MenuItem>
<MenuItem href="/#my-projects">My Projects</MenuItem>
<MenuItem href="/#contact-me">Contact Me</MenuItem>
<MenuItem href={LINK.about}>About Me</MenuItem>
<MenuItem href={LINK.projects}>My Projects</MenuItem>
<MenuItem href={LINK.contact}>Contact Me</MenuItem>
</Menu>
<Socials className="mt-6 justify-center md:hidden" />
</nav>
Expand Down
13 changes: 11 additions & 2 deletions src/components/sections/hero/hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { buttonVariants } from '@/components/ui/button'
import Content from '@/components/ui/content'
import Heading from '@/components/ui/heading'
import Socials from '@/components/ui/socials'
import { LINK } from '@/types/enums/Link'
import { cn } from '@/utils'
import Image from 'next/image'
import Link from 'next/link'
import { FC, HTMLAttributes } from 'react'

Expand All @@ -17,8 +19,15 @@ const Hero: FC<HeroProps> = ({ className, ...props }) => {
)}
{...props}
>
<Image
alt="Background"
className="-z-5 object-cover object-center"
fill
sizes="100vw"
src="/hero-bg.svg"
/>
<Content
className="flex flex-col items-center justify-center text-center"
className="z-5 relative flex flex-col items-center justify-center text-center"
size="tight"
>
<article>
Expand All @@ -32,7 +41,7 @@ const Hero: FC<HeroProps> = ({ className, ...props }) => {
</article>
<Link
className={cn('mt-5', buttonVariants({ variant: 'outline' }))}
href="/#my-projects"
href={LINK.projects}
>
My Projects
</Link>
Expand Down
3 changes: 2 additions & 1 deletion src/components/ui/logo.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client'

import HeaderContext from '@/store/header-context'
import { LINK } from '@/types/enums/Link'
import { cn } from '@/utils'
import Link from 'next/link'
import { AnchorHTMLAttributes, FC, useContext } from 'react'
Expand All @@ -13,7 +14,7 @@ const Logo: FC<LogoProps> = ({ className, ...props }) => {
return (
<Link
className={cn('text-3xl font-bold text-accent', className)}
href="/"
href={LINK.index}
onClick={closeMenu}
{...props}
>
Expand Down
6 changes: 6 additions & 0 deletions src/types/ProjectMetadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type ProjectMetadata = {
lastModified: Date
slug: string
}

export default ProjectMetadata
10 changes: 10 additions & 0 deletions src/types/enums/Link.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const LINK = {
about: '/#about-me',
contact: '/#contact-me',
index: '/',
projects: '/#my-projects',
} as const

type Link = keyof typeof LINK

export default Link
1 change: 1 addition & 0 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const MOBILE_BREAKPOINT = 767.98
export const SITE_URL = process.env.SITE_URL || 'http://localhost:3000'
51 changes: 36 additions & 15 deletions src/utils/projects.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import Project from '@/types/Project'
import ProjectMetadata from '@/types/ProjectMetadata'
import fs from 'fs'
import { readFile, readdir } from 'fs/promises'
import matter from 'gray-matter'
import { marked } from 'marked'
Expand All @@ -8,16 +10,6 @@ const PROJECTS_PATH = './content/projects'
const FILE_EXTENSION = '.md'
const DIR_PATH = path.join(process.cwd(), PROJECTS_PATH)

export const getSlugs = async (): Promise<string[]> => {
const files = await readdir(DIR_PATH)

const slugs = files
.filter((file) => file.endsWith(FILE_EXTENSION))
.map((file) => file.replace(FILE_EXTENSION, ''))

return slugs
}

export const getProjects = async (): Promise<Project[]> => {
const slugs = await getSlugs()

Expand All @@ -40,11 +32,7 @@ export const getProjects = async (): Promise<Project[]> => {
}

export const getProject = async (slug: string): Promise<Project> => {
const filePath = path.join(
process.cwd(),
PROJECTS_PATH,
`${slug}${FILE_EXTENSION}`
)
const filePath = getProjectFilePath(slug)

const text = await readFile(filePath, 'utf-8')
if (!text.trim()) throw new Error('Markdown file is empty!')
Expand All @@ -62,3 +50,36 @@ export const getProject = async (slug: string): Promise<Project> => {

return { body, features, github, image, slug, technologies, title, website }
}

export const getProjectsMetadata = async (): Promise<ProjectMetadata[]> => {
const slugs = await getSlugs()

let projectsMetadata: ProjectMetadata[] = []

if (!slugs.length) return projectsMetadata

for (const slug of slugs) {
const projectModifiedDate = getProjectModifiedDate(slug)
projectsMetadata.push({ lastModified: projectModifiedDate, slug })
}

return projectsMetadata
}

export const getSlugs = async (): Promise<string[]> => {
const files = await readdir(DIR_PATH)
const slugs = files
.filter((file) => file.endsWith(FILE_EXTENSION))
.map((file) => file.replace(FILE_EXTENSION, ''))
return slugs
}

export const getProjectFilePath = (slug: string): string => {
return path.join(process.cwd(), PROJECTS_PATH, `${slug}${FILE_EXTENSION}`)
}

export const getProjectModifiedDate = (slug: string): Date => {
const filePath = getProjectFilePath(slug)
const fileStats = fs.statSync(filePath)
return new Date(fileStats.mtime.toISOString())
}

0 comments on commit 297e42f

Please sign in to comment.