use some server actions

This commit is contained in:
Matthias Kretschmann 2024-01-30 19:43:01 +00:00
parent a70016b73f
commit 18fdff4a10
Signed by: m
GPG Key ID: 606EEEF3C479A91F
9 changed files with 124 additions and 92 deletions

View File

@ -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<Metadata> {
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 (

32
src/app/actions.ts Normal file
View File

@ -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)
}

View File

@ -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<string>()
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()

View File

@ -9,15 +9,15 @@ import { usePathname } from 'next/navigation'
export default function Header() {
const pathname = usePathname()
const small = pathname !== '/'
const isSmall = pathname !== '/'
return (
<header className={`${styles.header} ${small ? styles.small : ''}`}>
<LogoUnit small={small} />
{!small ? <Networks label="Networks" /> : null}
<header className={`${styles.header} ${isSmall ? styles.small : ''}`}>
<LogoUnit small={isSmall} />
{!isSmall ? <Networks label="Networks" /> : null}
<div className={styles.meta}>
{!small ? <Location /> : null}
{!small ? <Availability /> : null}
{!isSmall ? <Location /> : null}
{!isSmall ? <Availability /> : null}
</div>
</header>
)

View File

@ -1,3 +1,8 @@
.location,
.wrapper {
min-height: 23px;
}
.location {
font-size: var(--font-size-small);
}

View File

@ -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<UseLocation>()
const isDifferentCountry = location?.now?.country !== location?.next?.country
const relativeTime = new RelativeTime({ locale: 'en' })
return now?.city ? (
<LazyMotion features={domAnimation}>
<m.section
aria-label="Location"
variants={moveInTop}
className={styles.location}
{...getAnimationProps(shouldReduceMotion)}
>
<Flag country={{ code: now.country_code, name: now.country }} />
{now?.city} <span>Now</span>
<div className={styles.next}>
{next?.city && (
<>
{isDifferentCountry && (
<Flag
country={{ code: next.country_code, name: next.country }}
/>
useEffect(() => {
async function fetchData() {
const location = await getLocation()
if (!location) return
setLocation(location)
}
fetchData()
}, [])
return (
<div className={styles.wrapper}>
{location?.now?.city ? (
<LazyMotion features={domAnimation}>
<m.section
aria-label="Location"
variants={moveInTop}
className={styles.location}
{...getAnimationProps(shouldReduceMotion)}
>
<Flag
country={{
code: location.now.country_code,
name: location.now.country
}}
/>
{location.now.city} <span>Now</span>
<div className={styles.next}>
{location?.next?.city && (
<>
{isDifferentCountry && (
<Flag
country={{
code: location.next.country_code,
name: location.next.country
}}
/>
)}
{location.next.city}{' '}
<span>
{relativeTime.from(new Date(location.next.date_start))}
</span>
</>
)}
{next.city}{' '}
<span>{relativeTime.from(new Date(next.date_start))}</span>
</>
)}
</div>
</m.section>
</LazyMotion>
) : null
</div>
</m.section>
</LazyMotion>
) : null}
</div>
)
}

View File

@ -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
}

View File

@ -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<UseLocation>()
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
}
}

View File

@ -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)