From 18fdff4a108149a5ef49a3fe911ec8881c5a2ae6 Mon Sep 17 00:00:00 2001 From: Matthias Kretschmann Date: Tue, 30 Jan 2024 19:43:01 +0000 Subject: [PATCH] use some server actions --- src/app/[slug]/page.tsx | 7 ++ src/app/actions.ts | 32 +++++++++ src/components/404/index.tsx | 22 ++----- src/components/Header/index.tsx | 12 ++-- src/components/Location/index.module.css | 5 ++ src/components/Location/index.tsx | 83 ++++++++++++++++-------- src/components/Location/types.ts | 13 ++++ src/hooks/useLocation.ts | 41 ------------ src/lib/content.ts | 1 + 9 files changed, 124 insertions(+), 92 deletions(-) create mode 100644 src/app/actions.ts create mode 100644 src/components/Location/types.ts delete mode 100644 src/hooks/useLocation.ts diff --git a/src/app/[slug]/page.tsx b/src/app/[slug]/page.tsx index 82976f6..316cc20 100644 --- a/src/app/[slug]/page.tsx +++ b/src/app/[slug]/page.tsx @@ -7,6 +7,7 @@ import { getProjectBySlug, getProjectSlugs } from '../../lib/content' +import { notFound } from 'next/navigation' type Props = { params: { slug: string } @@ -18,6 +19,7 @@ export async function generateMetadata( parent: ResolvingMetadata ): Promise { const project = await getProjectBySlug(params.slug) + if (!project) return return { title: project.title, @@ -35,6 +37,11 @@ export async function generateMetadata( export default async function ProjectPage({ params }: Props) { const project = await getProjectBySlug(params.slug) + + if (!project) { + notFound() + } + const projects = await getAllProjects(['slug', 'title', 'images']) return ( diff --git a/src/app/actions.ts b/src/app/actions.ts new file mode 100644 index 0000000..050ec46 --- /dev/null +++ b/src/app/actions.ts @@ -0,0 +1,32 @@ +'use server' + +import { GiphyFetch } from '@giphy/js-fetch-api' +import { revalidatePath } from 'next/cache' + +export async function getLocation() { + try { + const response = await fetch('https://location.kretschmann.io') + if (!response.ok) + throw new Error('Network response for location was not ok.') + + const data = await response.json() + return data + } catch (error) { + console.error(error.message) + } +} + +export async function getRandomGif(tag: string, pathname?: string) { + try { + // Famous last words: + // "It's just the 404 page so why not expose the dev API key" + const giphyClient = new GiphyFetch('LfXRwufRyt6PK414G2kKJBv3L8NdnxyR') + const { data } = await giphyClient.random({ tag }) + const gif = data.images.original.mp4 + return gif + } catch (error) { + console.error(error.message) + } + + if (pathname) revalidatePath(pathname) +} diff --git a/src/components/404/index.tsx b/src/components/404/index.tsx index e083a4a..4f9d4cb 100644 --- a/src/components/404/index.tsx +++ b/src/components/404/index.tsx @@ -4,36 +4,24 @@ import { MouseEvent, useEffect, useState } from 'react' import Link from 'next/link' import Button from '../Button' import styles from './index.module.css' +import { getRandomGif } from '../../app/actions' +import { usePathname } from 'next/navigation' const tag = 'cat' -async function getRandomGif() { - const Giphy = await import('@giphy/js-fetch-api') - - try { - // Famous last words: - // "It's just the 404 page so why not expose the dev API key" - const giphyClient = new Giphy.GiphyFetch('LfXRwufRyt6PK414G2kKJBv3L8NdnxyR') - let response = await giphyClient.random({ tag }) - const gif = response.data.images.original.mp4 - return gif - } catch (error) { - console.error(error.message) - } -} - export default function NotFound() { + const pathname = usePathname() const [gif, setGif] = useState() async function handleClick(e: MouseEvent) { e.preventDefault() - const gif = await getRandomGif() + const gif = await getRandomGif(tag, pathname) setGif(gif) } useEffect(() => { async function init() { - const gif = await getRandomGif() + const gif = await getRandomGif(tag) setGif(gif) } init() diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index 3a6f747..dc60dd8 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -9,15 +9,15 @@ import { usePathname } from 'next/navigation' export default function Header() { const pathname = usePathname() - const small = pathname !== '/' + const isSmall = pathname !== '/' return ( -
- - {!small ? : null} +
+ + {!isSmall ? : null}
- {!small ? : null} - {!small ? : null} + {!isSmall ? : null} + {!isSmall ? : null}
) diff --git a/src/components/Location/index.module.css b/src/components/Location/index.module.css index 9622cb9..a6ea1c0 100644 --- a/src/components/Location/index.module.css +++ b/src/components/Location/index.module.css @@ -1,3 +1,8 @@ +.location, +.wrapper { + min-height: 23px; +} + .location { font-size: var(--font-size-small); } diff --git a/src/components/Location/index.tsx b/src/components/Location/index.tsx index 428da0e..aa361dc 100644 --- a/src/components/Location/index.tsx +++ b/src/components/Location/index.tsx @@ -2,41 +2,68 @@ import RelativeTime from '@yaireo/relative-time' import { LazyMotion, domAnimation, m, useReducedMotion } from 'framer-motion' -import { useLocation } from '../../hooks/useLocation' import { getAnimationProps, moveInTop } from '../Transitions' import { Flag } from './Flag' import styles from './index.module.css' +import { useEffect, useState } from 'react' +import { getLocation } from '../../app/actions' +import { UseLocation } from './types' export default function Location() { - const { now, next } = useLocation() const shouldReduceMotion = useReducedMotion() - const isDifferentCountry = now?.country !== next?.country + + const [location, setLocation] = useState() + + const isDifferentCountry = location?.now?.country !== location?.next?.country const relativeTime = new RelativeTime({ locale: 'en' }) - return now?.city ? ( - - - - {now?.city} Now -
- {next?.city && ( - <> - {isDifferentCountry && ( - + useEffect(() => { + async function fetchData() { + const location = await getLocation() + if (!location) return + setLocation(location) + } + fetchData() + }, []) + + return ( +
+ {location?.now?.city ? ( + + + + {location.now.city} Now +
+ {location?.next?.city && ( + <> + {isDifferentCountry && ( + + )} + {location.next.city}{' '} + + {relativeTime.from(new Date(location.next.date_start))} + + )} - {next.city}{' '} - {relativeTime.from(new Date(next.date_start))} - - )} -
-
-
- ) : null +
+ + + ) : null} +
+ ) } diff --git a/src/components/Location/types.ts b/src/components/Location/types.ts new file mode 100644 index 0000000..8cec120 --- /dev/null +++ b/src/components/Location/types.ts @@ -0,0 +1,13 @@ +export type Location = { + country: string + city: string + country_code: string + date_start: string + date_end: string +} + +export type UseLocation = { + now: Location + next: Location + previous: Location +} diff --git a/src/hooks/useLocation.ts b/src/hooks/useLocation.ts deleted file mode 100644 index d05f2d8..0000000 --- a/src/hooks/useLocation.ts +++ /dev/null @@ -1,41 +0,0 @@ -'use client' - -import { useEffect, useState } from 'react' - -export type Location = { - country: string - city: string - country_code: string - date_start: string - date_end: string -} - -export type UseLocation = { - now: Location - next: Location - previous: Location -} - -export const useLocation = () => { - const [location, setLocation] = useState() - - useEffect(() => { - async function fetchData() { - try { - const response = await fetch('https://location.kretschmann.io') - const data = await response.json() - if (!data) return - setLocation(data) - } catch (error) { - console.error(error.message) - } - } - fetchData() - }, []) - - return { - now: location?.now, - next: location?.next, - previous: location?.previous - } -} diff --git a/src/lib/content.ts b/src/lib/content.ts index 73cc2a6..dd461fc 100644 --- a/src/lib/content.ts +++ b/src/lib/content.ts @@ -62,6 +62,7 @@ export async function getProjectImages(slug: string) { export async function getProjectBySlug(slug: string, fields: string[] = []) { const project = projects.find((item) => item.slug === slug) + if (!project) return // enhance data with additional fields const descriptionHtml = await markdownToHtml(project.description)