use some server actions
This commit is contained in:
parent
a70016b73f
commit
18fdff4a10
|
@ -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 (
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
.location,
|
||||
.wrapper {
|
||||
min-height: 23px;
|
||||
}
|
||||
|
||||
.location {
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue