From a5cd4aebdacb533f685f32323284dcfe224257da Mon Sep 17 00:00:00 2001 From: Matthias Kretschmann Date: Sun, 4 Feb 2024 22:09:00 +0000 Subject: [PATCH 01/12] refactor: handle content generation as prebuild step --- .gitignore | 3 +- package-lock.json | 7 -- package.json | 12 +-- scripts/content/content.test-disabled.ts | 48 +++++++++++ scripts/content/generateProjects.ts | 32 +++++++ scripts/content/images.ts | 36 ++++++++ {src/lib => scripts/content}/markdown.test.ts | 0 {src/lib => scripts/content}/markdown.ts | 0 scripts/content/rgbDataURL.ts | 11 +++ scripts/content/transformProject.ts | 20 +++++ scripts/favicon.ts | 4 +- scripts/new.ts | 18 ++-- scripts/prebuild.ts | 3 + src/app/[slug]/getAllSlugs.ts | 6 ++ src/app/[slug]/getProjectBySlug.ts | 5 ++ src/app/[slug]/page.tsx | 35 +++----- src/app/__tests__/[slug].test.tsx | 11 +-- src/app/__tests__/layout.test.tsx | 8 +- src/app/__tests__/page.test.tsx | 10 --- src/app/actions.ts | 18 +++- src/app/page.tsx | 13 +-- src/app/providers.tsx | 2 +- src/components/Button/index.tsx | 17 ++-- src/components/HostnameCheck/index.tsx | 2 +- src/components/Icon/index.test.tsx | 8 +- src/components/Location/Location.tsx | 70 ++++++++------- src/components/LogoUnit/index.tsx | 1 - src/components/Networks/index.test.tsx | 2 +- src/components/Networks/index.tsx | 2 +- src/components/Project/index.tsx | 7 +- src/components/ProjectImage/index.test.tsx | 4 +- src/components/ProjectImage/index.tsx | 64 +++----------- src/components/ProjectNav/index.tsx | 4 +- src/components/ProjectPreview/index.tsx | 9 +- src/components/Projects/index.tsx | 4 +- src/components/Repositories/index.test.tsx | 5 -- src/components/Repositories/index.tsx | 8 +- src/components/Repository/index.test.tsx | 2 +- src/components/ThemeSwitch/index.tsx | 2 +- src/components/Transitions.ts | 6 ++ src/components/Vcard/index.tsx | 3 +- src/components/Vcard/types.ts | 19 +++++ src/lib/content.test.ts | 53 ------------ src/lib/content.ts | 85 ------------------- src/types/image.ts | 6 +- tests/jest.config.ts | 2 +- tests/jest.setup.tsx | 4 +- tsconfig.json | 19 +++-- 48 files changed, 356 insertions(+), 354 deletions(-) create mode 100644 scripts/content/content.test-disabled.ts create mode 100644 scripts/content/generateProjects.ts create mode 100644 scripts/content/images.ts rename {src/lib => scripts/content}/markdown.test.ts (100%) rename {src/lib => scripts/content}/markdown.ts (100%) create mode 100644 scripts/content/rgbDataURL.ts create mode 100644 scripts/content/transformProject.ts create mode 100644 scripts/prebuild.ts create mode 100644 src/app/[slug]/getAllSlugs.ts create mode 100644 src/app/[slug]/getProjectBySlug.ts create mode 100644 src/components/Vcard/types.ts delete mode 100644 src/lib/content.test.ts delete mode 100644 src/lib/content.ts diff --git a/.gitignore b/.gitignore index 133a6d1..a75448e 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,5 @@ coverage public/matomo.js # public/favicon* # public/apple-touch-icon* -# public/manifest* \ No newline at end of file +# public/manifest* +generated \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c61582a..6f77d9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,6 @@ "jest-environment-jsdom": "^29.7.0", "js-yaml": "^4.1.0", "ora": "^8.0.1", - "prepend": "^1.0.2", "prettier": "^3.2.4", "sharp": "^0.33.2", "sharp-ico": "^0.1.5", @@ -12159,12 +12158,6 @@ "node": ">= 0.8.0" } }, - "node_modules/prepend": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/prepend/-/prepend-1.0.2.tgz", - "integrity": "sha512-ImzjnkCUZRATBOHwlpmxz0IY21r+3/bcK3OTcrrU0njZlTWh58tgD7L5dH3TiZcc8ShDU7JEa5wk/UufWF6p7w==", - "dev": true - }, "node_modules/prettier": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", diff --git a/package.json b/package.json index a909a59..1b26537 100644 --- a/package.json +++ b/package.json @@ -8,19 +8,20 @@ "author": "Matthias Kretschmann ", "type": "module", "scripts": { - "start": "next", + "start": "npm run prebuild && next", "build": "next build", - "preview": "npm run build && next start", - "export": "next export", + "preview": "next start", + "export": "npm run prebuild && next export", "typecheck": "tsc", "lint:js": "next lint", "lint:css": "stylelint ./src/**/*.css", "lint": "npm run lint:js && npm run lint:css", "format": "prettier --write 'src/**/*.{ts,tsx,css}'", "jest": "jest --coverage -c tests/jest.config.ts", - "test": "NODE_ENV=test npm run lint && npm run typecheck && npm run jest", + "test": "NODE_ENV=test npm run prebuild && npm run lint && npm run typecheck && npm run jest", "new": "ts-node-esm ./scripts/new.ts", - "favicon": "ts-node-esm ./scripts/favicon.ts" + "favicon": "ts-node-esm ./scripts/favicon.ts", + "prebuild": "ts-node-esm ./scripts/prebuild.ts" }, "dependencies": { "@giphy/js-fetch-api": "^5.3.0", @@ -53,7 +54,6 @@ "jest-environment-jsdom": "^29.7.0", "js-yaml": "^4.1.0", "ora": "^8.0.1", - "prepend": "^1.0.2", "prettier": "^3.2.4", "sharp": "^0.33.2", "sharp-ico": "^0.1.5", diff --git a/scripts/content/content.test-disabled.ts b/scripts/content/content.test-disabled.ts new file mode 100644 index 0000000..e59d715 --- /dev/null +++ b/scripts/content/content.test-disabled.ts @@ -0,0 +1,48 @@ +// import { getProjectBySlug, getProjectImages } from '.' + +// jest.setTimeout(20000) + +// describe('lib/content', () => { +// test('getProjectSlugs', async () => { +// const slugs: string[] = getProjectSlugs() +// expect(slugs).toContain('ipixelpad') +// }) + +// test('getProjectBySlug', async () => { +// const project = await getProjectBySlug('ipixelpad') +// expect(project).toBeDefined() +// expect(project.images[0].src).toContain('ipixelpad') +// }) + +// test('getProjectBySlug returns early', async () => { +// const project = await getProjectBySlug('gibberish') +// expect(project).not.toBeDefined() +// }) + +// test('getProjectImages', async () => { +// const images = await getProjectImages('ipixelpad') +// expect(images).toBeDefined() +// expect(images[0].src).toContain('ipixelpad') +// // expect(images[0].blurDataURL).toBeDefined() +// expect(images[0].width).toBeDefined() +// expect(images[0].height).toBeDefined() +// expect(images[0].format).toBeDefined() +// }) + +// test('getAllProjects', async () => { +// const projects = await getAllProjects([ +// 'title', +// 'description', +// 'slug', +// 'images', +// 'techstack', +// 'links' +// ]) +// expect(projects).toBeDefined() +// }) + +// test('getAllProjects without fields', async () => { +// const projects = await getAllProjects() +// expect(projects).toBeDefined() +// }) +// }) diff --git a/scripts/content/generateProjects.ts b/scripts/content/generateProjects.ts new file mode 100644 index 0000000..4bc999a --- /dev/null +++ b/scripts/content/generateProjects.ts @@ -0,0 +1,32 @@ +import fs from 'fs' +import yaml from 'js-yaml' +import ora from 'ora' +import { join } from 'path' +import type ProjectType from '../../src/types/project.js' +import { transformProject } from './transformProject.js' + +const contentDirectory = join(process.cwd(), '_content') +const projectsOriginal = yaml.load( + fs.readFileSync(`${contentDirectory}/projects.yml`, 'utf8') +) as ProjectType[] +const projectsOutput = 'generated/projects.json' + +export async function generateProjects(): Promise { + const spinner = ora('Generating projects content...\n').start() + const slugs = projectsOriginal.map(({ slug }: { slug: string }) => slug) + + try { + const projects: ProjectType[] = [] + + for (const slug of slugs) { + spinner.text = `Generating content for ${slug}...\n` + const project = await transformProject(projectsOriginal, slug) + if (project) projects.push(project) + } + + fs.writeFileSync(projectsOutput, JSON.stringify(projects, null, 2)) + spinner.succeed(`Projects content written to ${projectsOutput}\n`) + } catch (error: unknown) { + spinner.fail((error as Error).message) + } +} diff --git a/scripts/content/images.ts b/scripts/content/images.ts new file mode 100644 index 0000000..fb27f08 --- /dev/null +++ b/scripts/content/images.ts @@ -0,0 +1,36 @@ +import type ImageType from '@/src/types/image' +import fs from 'fs' +import { join } from 'path' +import sharp from 'sharp' +import { rgbDataURL } from './rgbDataURL.js' + +const imagesDirectory = join(process.cwd(), 'public', 'images') + +export async function getProjectImages(slug: string) { + const allImages = fs.readdirSync(imagesDirectory, 'utf8') + const projectImages = allImages.filter((image) => image.includes(slug)) + + let images: ImageType[] = [] + + await Promise.all( + projectImages.map(async (image) => { + const file = `${imagesDirectory}/${image}` + const transformer = sharp(file) + const { width, height, format } = await transformer.metadata() + const { dominant } = await transformer.stats() + const blurDataURL = rgbDataURL(dominant.r, dominant.g, dominant.b) + + const imageType: ImageType = { + width, + height, + format, + blurDataURL, + src: `/images/${image}` + } + images.push(imageType) + }) + ) + // Sort images by sequentially numbered name to be sure + images = images.sort((a, b) => a.src.localeCompare(b.src)) + return images +} diff --git a/src/lib/markdown.test.ts b/scripts/content/markdown.test.ts similarity index 100% rename from src/lib/markdown.test.ts rename to scripts/content/markdown.test.ts diff --git a/src/lib/markdown.ts b/scripts/content/markdown.ts similarity index 100% rename from src/lib/markdown.ts rename to scripts/content/markdown.ts diff --git a/scripts/content/rgbDataURL.ts b/scripts/content/rgbDataURL.ts new file mode 100644 index 0000000..117b7f5 --- /dev/null +++ b/scripts/content/rgbDataURL.ts @@ -0,0 +1,11 @@ +// Pixel GIF code adapted from https://stackoverflow.com/a/33919020/266535 +const keyStr = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' +const triplet = (e1: number, e2: number, e3: number) => + keyStr.charAt(e1 >> 2) + + keyStr.charAt(((e1 & 3) << 4) | (e2 >> 4)) + + keyStr.charAt(((e2 & 15) << 2) | (e3 >> 6)) + + keyStr.charAt(e3 & 63) + +export const rgbDataURL = (r: number, g: number, b: number) => + `data:image/gif;base64,R0lGODlhAQABAPAA${triplet(0, r, g) + triplet(b, 255, 255)}/yH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==` diff --git a/scripts/content/transformProject.ts b/scripts/content/transformProject.ts new file mode 100644 index 0000000..2a6df0e --- /dev/null +++ b/scripts/content/transformProject.ts @@ -0,0 +1,20 @@ +import ProjectType from '../../src/types/project' +import { getProjectImages } from './images.js' +import { markdownToHtml } from './markdown.js' + +export async function transformProject( + projectsOriginal: ProjectType[], + slug: string +) { + const project = projectsOriginal.find((item) => item.slug === slug) + if (!project) return + + // enhance data with additional fields + const descriptionHtml = await markdownToHtml(project.description) + project.descriptionHtml = descriptionHtml + + const images = await getProjectImages(slug) + project.images = images + + return project +} diff --git a/scripts/favicon.ts b/scripts/favicon.ts index b79fd4e..92f1809 100644 --- a/scripts/favicon.ts +++ b/scripts/favicon.ts @@ -86,8 +86,8 @@ async function buildFavicons() { Add this to src/components/Meta/Favicon.tsx: ${outputMeta} `) - } catch (error) { - console.error(error.message) + } catch (error: unknown) { + console.error((error as Error).message) } } diff --git a/scripts/new.ts b/scripts/new.ts index 28faefc..89f6156 100644 --- a/scripts/new.ts +++ b/scripts/new.ts @@ -1,10 +1,8 @@ #!/usr/bin/env ts-node - import fs from 'fs' -import path from 'path' -import prepend from 'prepend' -import slugify from 'slugify' import ora from 'ora' +import path from 'path' +import slugify from 'slugify' const templatePath = path.join(process.cwd(), 'scripts', 'new.yml') const template = fs.readFileSync(templatePath).toString() @@ -26,7 +24,13 @@ const newContents = template .split('SLUG') .join(titleSlug) -prepend(projects, newContents, (error) => { - if (error) spinner.fail(error) - spinner.succeed(`Added '${title}' to top of projects.yml file.`) +// prepend newContents to projects.yml file +fs.readFile(projects, 'utf8', (error, data) => { + if (error) spinner.fail(error.message) + + fs.writeFile(projects, newContents + data, (error) => { + if (error) spinner.fail(error.message) + + spinner.succeed(`Added '${title}' to top of projects.yml file.`) + }) }) diff --git a/scripts/prebuild.ts b/scripts/prebuild.ts new file mode 100644 index 0000000..1ec6c6f --- /dev/null +++ b/scripts/prebuild.ts @@ -0,0 +1,3 @@ +import { generateProjects } from './content/generateProjects.js' + +generateProjects() diff --git a/src/app/[slug]/getAllSlugs.ts b/src/app/[slug]/getAllSlugs.ts new file mode 100644 index 0000000..bdcaf19 --- /dev/null +++ b/src/app/[slug]/getAllSlugs.ts @@ -0,0 +1,6 @@ +import projects from '@/generated/projects.json' + +export function getAllSlugs() { + const slugs = projects.map(({ slug }: { slug: string }) => slug) + return slugs +} diff --git a/src/app/[slug]/getProjectBySlug.ts b/src/app/[slug]/getProjectBySlug.ts new file mode 100644 index 0000000..60af2b6 --- /dev/null +++ b/src/app/[slug]/getProjectBySlug.ts @@ -0,0 +1,5 @@ +import projects from '@/generated/projects.json' + +export function getProjectBySlug(slug: string) { + return projects.find((item) => item.slug === slug) +} diff --git a/src/app/[slug]/page.tsx b/src/app/[slug]/page.tsx index bd44b8c..fbddff4 100644 --- a/src/app/[slug]/page.tsx +++ b/src/app/[slug]/page.tsx @@ -1,26 +1,20 @@ -import { Metadata, ResolvingMetadata } from 'next' +import { Metadata } from 'next' import { notFound } from 'next/navigation' -import meta from '../../../_content/meta.json' -import Header from '../../components/Header/Header' -import Project from '../../components/Project' -import ProjectNav from '../../components/ProjectNav' -import { - getAllProjects, - getProjectBySlug, - getProjectSlugs -} from '../../lib/content' +import meta from '@/_content/meta.json' +import projects from '@/generated/projects.json' +import Header from '@/src/components/Header/Header' +import Project from '@/src/components/Project' +import ProjectNav from '@/src/components/ProjectNav' +import { getAllSlugs } from './getAllSlugs' +import { getProjectBySlug } from './getProjectBySlug' type Props = { params: { slug: string } - // searchParams: { [key: string]: string | string[] | undefined } } -export async function generateMetadata( - { params }: Props - // parent: ResolvingMetadata -): Promise { - const project = await getProjectBySlug(params.slug) - if (!project) return +export async function generateMetadata({ params }: Props): Promise { + const project = getProjectBySlug(params.slug) + if (!project) return {} return { title: project.title, @@ -37,12 +31,10 @@ export async function generateMetadata( } export default async function ProjectPage({ params }: Props) { - const project = await getProjectBySlug(params.slug) + const project = getProjectBySlug(params.slug) if (!project) notFound() - const projects = await getAllProjects(['slug', 'title', 'images']) - return ( <>
@@ -53,7 +45,6 @@ export default async function ProjectPage({ params }: Props) { } export async function generateStaticParams() { - const slugs = getProjectSlugs() - + const slugs = getAllSlugs() return slugs.map((slug) => ({ slug })) } diff --git a/src/app/__tests__/[slug].test.tsx b/src/app/__tests__/[slug].test.tsx index 57927aa..69c7836 100644 --- a/src/app/__tests__/[slug].test.tsx +++ b/src/app/__tests__/[slug].test.tsx @@ -1,13 +1,14 @@ import { render } from '@testing-library/react' import meta from '../../../_content/meta.json' import projectMock from '../../../tests/__fixtures__/project.json' -import projectsMock from '../../../tests/__fixtures__/projects.json' import Page, { generateMetadata, generateStaticParams } from '../[slug]/page' -jest.mock('../../lib/content', () => ({ - getAllProjects: jest.fn().mockImplementation(() => projectsMock), - getProjectBySlug: jest.fn().mockImplementation(() => projectMock), - getProjectSlugs: jest.fn().mockImplementation(() => ['slug1', 'slug2']) +jest.mock('../[slug]/getProjectBySlug', () => ({ + getProjectBySlug: jest.fn().mockImplementation(() => projectMock) +})) + +jest.mock('../[slug]/getAllSlugs', () => ({ + getAllSlugs: jest.fn().mockImplementationOnce(() => ['slug1', 'slug2']) })) describe('app: [slug]/page', () => { diff --git a/src/app/__tests__/layout.test.tsx b/src/app/__tests__/layout.test.tsx index b2d0184..f428fc5 100644 --- a/src/app/__tests__/layout.test.tsx +++ b/src/app/__tests__/layout.test.tsx @@ -1,11 +1,15 @@ import { render, screen } from '@testing-library/react' -import { dataLocation } from '../../../tests/__fixtures__/location' import Layout from '../layout' describe('app: /layout', () => { // suppress error "Warning: validateDOMNesting(...): cannot appear as a child of
" // https://github.com/testing-library/react-testing-library/issues/1250 - let originalError + let originalError: { + (...data: any[]): void + (message?: any, ...optionalParams: any[]): void + (...data: any[]): void + (message?: any, ...optionalParams: any[]): void + } beforeAll(() => { originalError = console.error diff --git a/src/app/__tests__/page.test.tsx b/src/app/__tests__/page.test.tsx index 65431d3..8226bc8 100644 --- a/src/app/__tests__/page.test.tsx +++ b/src/app/__tests__/page.test.tsx @@ -1,16 +1,6 @@ import { render, screen } from '@testing-library/react' -import projectsMock from '../../../tests/__fixtures__/projects.json' -import reposMock from '../../../tests/__fixtures__/repos.json' import Page from '../page' -jest.mock('../../lib/content', () => ({ - getAllProjects: jest.fn().mockImplementationOnce(() => projectsMock) -})) - -jest.mock('../../lib/github', () => ({ - getGithubRepos: jest.fn().mockImplementationOnce(() => reposMock) -})) - describe('app: /page', () => { it('renders correctly', async () => { render(await Page()) diff --git a/src/app/actions.ts b/src/app/actions.ts index 197c00e..30b6111 100644 --- a/src/app/actions.ts +++ b/src/app/actions.ts @@ -3,6 +3,7 @@ import { cache } from 'react' import { revalidatePath } from 'next/cache' import { GiphyFetch } from '@giphy/js-fetch-api' +import { getGithubRepos } from '../lib/github' export const preloadLocation = () => { void getLocation() @@ -16,8 +17,17 @@ export const getLocation = cache(async () => { const data = await response.json() return data - } catch (error) { - console.error(error.message) + } catch (error: unknown) { + console.error((error as Error).message) + } +}) + +export const getRepos = cache(async () => { + try { + const repos = await getGithubRepos() + return repos + } catch (error: unknown) { + console.error((error as Error).message) } }) @@ -29,8 +39,8 @@ export async function getRandomGif(tag: string, pathname?: string) { const { data } = await giphyClient.random({ tag }) const gif = data.images.original.mp4 return gif - } catch (error) { - console.error(error.message) + } catch (error: unknown) { + console.error((error as Error).message) } if (pathname) revalidatePath(pathname) diff --git a/src/app/page.tsx b/src/app/page.tsx index 772d355..978270f 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,13 +1,12 @@ +import { Suspense } from 'react' +import projects from '../../generated/projects.json' import Hero from '../components/Hero' import Projects from '../components/Projects' import Repositories from '../components/Repositories' -import { getAllProjects } from '../lib/content' -import { getGithubRepos } from '../lib/github' -import { preloadLocation } from './actions' +import { getRepos, preloadLocation } from './actions' export default async function IndexPage() { - const projects = await getAllProjects(['title', 'images', 'slug']) - const repos = await getGithubRepos() + const repos = await getRepos() preloadLocation() @@ -15,7 +14,9 @@ export default async function IndexPage() { <> - + Loading open source projects...

}> + +
) } diff --git a/src/app/providers.tsx b/src/app/providers.tsx index 7545883..8bea430 100644 --- a/src/app/providers.tsx +++ b/src/app/providers.tsx @@ -2,6 +2,6 @@ import { ThemeProvider } from 'next-themes' -export function Providers({ children }) { +export function Providers({ children }: { children: React.ReactNode }) { return {children} } diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 73e532d..77b04ee 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -1,9 +1,14 @@ import styles from './index.module.css' -const Button = ({ children, ...props }) => ( - - {children} - -) +declare type ButtonProps = React.DetailedHTMLProps< + React.AnchorHTMLAttributes, + HTMLAnchorElement +> -export default Button +export default function Button({ children, ...props }: ButtonProps) { + return ( + + {children} + + ) +} diff --git a/src/components/HostnameCheck/index.tsx b/src/components/HostnameCheck/index.tsx index 82f5eb4..7821b7f 100644 --- a/src/components/HostnameCheck/index.tsx +++ b/src/components/HostnameCheck/index.tsx @@ -7,7 +7,7 @@ type Props = { allowedHosts: string[] } -export async function generateMetadata({ params }) { +export async function generateMetadata({ params }: { params: Props }) { const isAllowedHost = params.allowedHosts.includes(window.location.hostname) if (!isAllowedHost) { diff --git a/src/components/Icon/index.test.tsx b/src/components/Icon/index.test.tsx index 17ee6e0..ddc642f 100644 --- a/src/components/Icon/index.test.tsx +++ b/src/components/Icon/index.test.tsx @@ -4,16 +4,16 @@ import Icon from '.' describe('Icon', () => { it('renders correctly', () => { const { container, rerender } = render() - expect(container.firstChild.nodeName).toBe('svg') + expect(container.firstChild?.nodeName).toBe('svg') rerender() - expect(container.firstChild.nodeName).toBe('svg') + expect(container.firstChild?.nodeName).toBe('svg') rerender() - expect(container.firstChild.nodeName).toBe('svg') + expect(container.firstChild?.nodeName).toBe('svg') rerender() - expect(container.firstChild.nodeName).toBe('svg') + expect(container.firstChild?.nodeName).toBe('svg') }) it('does not render with unknown name', () => { diff --git a/src/components/Location/Location.tsx b/src/components/Location/Location.tsx index 4c01c2e..a65e47d 100644 --- a/src/components/Location/Location.tsx +++ b/src/components/Location/Location.tsx @@ -1,13 +1,16 @@ 'use client' import { useEffect, useState } from 'react' +import { getLocation } from '@/src/app/actions' import RelativeTime from '@yaireo/relative-time' -import { getLocation } from '../../app/actions' +import { LazyMotion, domAnimation, m, useReducedMotion } from 'framer-motion' +import { fadeIn, getAnimationProps } from '../Transitions' import { Flag } from './Flag' import styles from './Location.module.css' import { UseLocation } from './types' export default function Location() { + const shouldReduceMotion = useReducedMotion() const [location, setLocation] = useState(null) const isDifferentCountry = location?.now?.country !== location?.next?.country @@ -24,37 +27,40 @@ export default function Location() { return (
{location?.now?.city ? ( -
- - {location.now.city} Now -
- {location?.next?.city && ( - <> - {isDifferentCountry && ( - - )} - {location.next.city}{' '} - - {relativeTime.from(new Date(location.next.date_start))} - - - )} -
-
+ + + + {location.now.city} Now +
+ {location?.next?.city && ( + <> + {isDifferentCountry && ( + + )} + {location.next.city}{' '} + + {relativeTime.from(new Date(location.next.date_start))} + + + )} +
+
+
) : null}
) diff --git a/src/components/LogoUnit/index.tsx b/src/components/LogoUnit/index.tsx index f323f4f..4bbc9d8 100644 --- a/src/components/LogoUnit/index.tsx +++ b/src/components/LogoUnit/index.tsx @@ -14,7 +14,6 @@ export default function LogoUnit({ small }: Props) { diff --git a/src/components/Networks/index.test.tsx b/src/components/Networks/index.test.tsx index f2b98ef..7cd0fe4 100644 --- a/src/components/Networks/index.test.tsx +++ b/src/components/Networks/index.test.tsx @@ -5,7 +5,7 @@ describe('Networks', () => { it('renders correctly from data file values', () => { const { container } = render() expect(container.firstChild).toBeInTheDocument() - expect(container.firstChild.nodeName).toBe('SECTION') + expect(container.firstChild?.nodeName).toBe('SECTION') }) it('renders correctly in small variant', () => { diff --git a/src/components/Networks/index.tsx b/src/components/Networks/index.tsx index 3a6eb95..13d7115 100644 --- a/src/components/Networks/index.tsx +++ b/src/components/Networks/index.tsx @@ -22,7 +22,7 @@ const containerVariants = { export default function Networks({ label, small }: Props) { const shouldReduceMotion = useReducedMotion() - const animationProps = getAnimationProps(shouldReduceMotion) + const animationProps = getAnimationProps(shouldReduceMotion || false) return ( diff --git a/src/components/Project/index.tsx b/src/components/Project/index.tsx index 1ac823d..2c2d8e9 100644 --- a/src/components/Project/index.tsx +++ b/src/components/Project/index.tsx @@ -12,7 +12,6 @@ import styles from './index.module.css' const containerVariants = { enter: { transition: { - delay: 0.3, staggerChildren: 0.2 } } @@ -25,7 +24,7 @@ export default function Project({ }) { const { title, descriptionHtml, images, links, techstack } = project const shouldReduceMotion = useReducedMotion() - const animationProps = getAnimationProps(shouldReduceMotion) + const animationProps = getAnimationProps(shouldReduceMotion || false) return (
@@ -42,7 +41,7 @@ export default function Project({ @@ -54,8 +53,6 @@ export default function Project({ alt={`Showcase image no. ${i + 1} for ${title}`} key={i} sizes="100vw" - // give priority to the first image - priority={i === 0} /> ))} diff --git a/src/components/ProjectImage/index.test.tsx b/src/components/ProjectImage/index.test.tsx index 1766d94..d64b551 100644 --- a/src/components/ProjectImage/index.test.tsx +++ b/src/components/ProjectImage/index.test.tsx @@ -16,6 +16,8 @@ describe('ProjectImage', () => { }) it('returns without errors without image', async () => { - render() + render( + + ) }) }) diff --git a/src/components/ProjectImage/index.tsx b/src/components/ProjectImage/index.tsx index 9ac8517..62614fc 100644 --- a/src/components/ProjectImage/index.tsx +++ b/src/components/ProjectImage/index.tsx @@ -1,24 +1,7 @@ -'use client' - -import { useEffect, useState } from 'react' import Image from 'next/image' -import { - LazyMotion, - domAnimation, - m, - useAnimation, - useReducedMotion -} from 'framer-motion' import ImageType from '../../types/image' -import { getAnimationProps } from '../Transitions' import styles from './index.module.css' -const animationVariants = { - initial: { opacity: 0 }, - enter: { opacity: 1 }, - exit: { opacity: 0 } -} - export default function ProjectImage({ image, alt, @@ -32,39 +15,20 @@ export default function ProjectImage({ className?: string priority?: boolean }) { - const [loaded, setLoaded] = useState(false) - const animationControls = useAnimation() - const shouldReduceMotion = useReducedMotion() - const animationProps = getAnimationProps(shouldReduceMotion) - - useEffect(() => { - if (loaded && animationControls) { - animationControls.start('enter') - } - }, [loaded, animationControls]) - return image ? ( - - - {alt} setLoaded(true)} - /> - - +
+ {alt} +
) : null } diff --git a/src/components/ProjectNav/index.tsx b/src/components/ProjectNav/index.tsx index 5126986..eb45aa3 100644 --- a/src/components/ProjectNav/index.tsx +++ b/src/components/ProjectNav/index.tsx @@ -6,7 +6,7 @@ import { Project } from './Project' import styles from './index.module.css' type Props = { - projects: Partial[] + projects: ProjectType[] currentSlug: string } @@ -19,7 +19,7 @@ export default function ProjectNav({ projects, currentSlug }: Props) { useEffect(() => { function scrollToCurrent() { const activeItem = currentItem.current - const scrollRect = scrollContainer.current.getBoundingClientRect() + const scrollRect = scrollContainer.current?.getBoundingClientRect() const activeRect = activeItem && activeItem.getBoundingClientRect() const newScrollLeftPosition = activeRect && diff --git a/src/components/ProjectPreview/index.tsx b/src/components/ProjectPreview/index.tsx index 3cab3a4..4f7fd54 100644 --- a/src/components/ProjectPreview/index.tsx +++ b/src/components/ProjectPreview/index.tsx @@ -7,22 +7,15 @@ type Props = { title: string slug: string image: ImageType - imagePriority: boolean } -export default function ProjectPreview({ - title, - slug, - image, - imagePriority -}: Props) { +export default function ProjectPreview({ title, slug, image }: Props) { return (