-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
254 additions
and
142 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
'use client' | ||
|
||
import type {LiveEventMessage, LiveEventRestart, LiveEventWelcome} from '@sanity/client' | ||
import {CorsOriginError} from '@sanity/client' | ||
import {useRouter} from 'next/navigation' | ||
import {useEffect} from 'react' | ||
import {useEffectEvent} from 'use-effect-event' | ||
import {client} from '../sanity/client' | ||
import {expireTags} from './actions' | ||
|
||
export function SanityLive() { | ||
const router = useRouter() | ||
|
||
const handleLiveEvent = useEffectEvent( | ||
(event: LiveEventMessage | LiveEventRestart | LiveEventWelcome) => { | ||
if (event.type === 'welcome') { | ||
console.info('Sanity is live with automatic revalidation of published content') | ||
} else if (event.type === 'message') { | ||
expireTags(event.tags) | ||
} else if (event.type === 'restart') { | ||
router.refresh() | ||
} | ||
}, | ||
) | ||
useEffect(() => { | ||
const subscription = client.live.events().subscribe({ | ||
next: (event) => { | ||
if (event.type === 'message' || event.type === 'restart' || event.type === 'welcome') { | ||
handleLiveEvent(event) | ||
} | ||
}, | ||
error: (error: unknown) => { | ||
if (error instanceof CorsOriginError) { | ||
console.warn( | ||
`Sanity Live is unable to connect to the Sanity API as the current origin - ${window.origin} - is not in the list of allowed CORS origins for this Sanity Project.`, | ||
error.addOriginUrl && `Add it here:`, | ||
error.addOriginUrl?.toString(), | ||
) | ||
} else { | ||
console.error(error) | ||
} | ||
}, | ||
}) | ||
return () => subscription.unsubscribe() | ||
}, [handleLiveEvent]) | ||
|
||
return null | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
'use client' | ||
|
||
import type {SyncTag} from '@sanity/client' | ||
import {useTransition} from 'react' | ||
import {randomColorTheme} from './actions' | ||
|
||
export function ThemeButton({tags}: {tags: SyncTag[]}) { | ||
const [pending, startTransition] = useTransition() | ||
return ( | ||
<button | ||
disabled={pending} | ||
onClick={() => | ||
startTransition(async () => { | ||
await randomColorTheme(tags) | ||
// Wait 2 seconds to stagger requests a little bit | ||
await new Promise((resolve) => setTimeout(resolve, 2_000)) | ||
}) | ||
} | ||
className={`bg-theme-button text-theme-button focus:ring-theme focus:ring-offset-theme rounded-md px-4 py-2 text-sm font-semibold transition ease-in-out focus:outline-none focus:ring-2 focus:ring-opacity-50 focus:ring-offset-2 focus:duration-0 disabled:cursor-not-allowed disabled:opacity-50 ${pending ? 'animate-pulse cursor-wait duration-150' : 'duration-1000'} `} | ||
> | ||
{pending ? 'Generating...' : 'Random Color Theme'} | ||
</button> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
'use client' | ||
|
||
import {useLayoutEffect, useState} from 'react' | ||
|
||
export function TimeSince({label, since}: {label: string; since: string}) { | ||
const [from, setFrom] = useState<null | Date>(null) | ||
const [now, setNow] = useState<null | Date>(null) | ||
useLayoutEffect(() => { | ||
setFrom(new Date(since)) | ||
const interval = setInterval(() => setNow(new Date()), 1000) | ||
return () => clearInterval(interval) | ||
}, [since]) | ||
|
||
let timeSince = '…' | ||
if (from && now) { | ||
timeSince = formatTimeSince(from, now) | ||
} | ||
|
||
return ( | ||
<div className="bg-theme-button text-theme-button absolute left-2 top-2 block rounded text-xs transition duration-1000 ease-in-out"> | ||
<span className="inline-block py-1 pl-2 pr-0.5">{label}:</span> | ||
<span className="bg-theme text-theme mr-0.5 inline-block rounded-r-sm px-1 py-0.5 tabular-nums transition duration-1000 ease-in-out"> | ||
fetched {timeSince} | ||
</span> | ||
</div> | ||
) | ||
} | ||
|
||
const rtf = new Intl.RelativeTimeFormat('en', {style: 'short'}) | ||
export function formatTimeSince(from: Date, to: Date): string { | ||
const seconds = Math.floor((from.getTime() - to.getTime()) / 1000) | ||
if (seconds > -60) { | ||
return rtf.format(Math.min(seconds, -1), 'second') | ||
} | ||
const minutes = Math.ceil(seconds / 60) | ||
if (minutes > -60) { | ||
return rtf.format(minutes, 'minute') | ||
} | ||
const hours = Math.ceil(minutes / 60) | ||
if (hours > -24) { | ||
return rtf.format(hours, 'hour') | ||
} | ||
const days = Math.ceil(hours / 24) | ||
return rtf.format(days, 'day') | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
'use server' | ||
|
||
import type {SyncTag} from '@sanity/client' | ||
import {revalidateTag} from 'next/cache' | ||
|
||
export async function expireTags(tags: SyncTag[]) { | ||
for (const tag of tags) { | ||
revalidateTag(tag) | ||
} | ||
console.log(`<SanityLive /> expired tags: ${tags.join(', ')}`) | ||
} | ||
|
||
export async function randomColorTheme(tags: SyncTag[]) { | ||
const res = await fetch('https://lcapi-examples-api.sanity.dev/api/random-color-theme', { | ||
method: 'PUT', | ||
}) | ||
for (const tag of tags) { | ||
revalidateTag(tag) | ||
} | ||
return res.json() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,47 @@ | ||
import type {Metadata} from 'next' | ||
import localFont from 'next/font/local' | ||
import './globals.css' | ||
import {sanityFetch} from '@/sanity/fetch' | ||
import {defineQuery} from 'groq' | ||
import {Suspense} from 'react' | ||
import {SanityLive} from './SanityLive' | ||
import {ThemeButton} from './ThemeButton' | ||
import {TimeSince} from './TimeSince' | ||
|
||
const geistSans = localFont({ | ||
src: './fonts/GeistVF.woff', | ||
variable: '--font-geist-sans', | ||
weight: '100 900', | ||
}) | ||
const geistMono = localFont({ | ||
src: './fonts/GeistMonoVF.woff', | ||
variable: '--font-geist-mono', | ||
weight: '100 900', | ||
}) | ||
const THEME_QUERY = defineQuery( | ||
`*[_id == "theme"][0]{background,text,"fetchedAt": dateTime(now())}`, | ||
) | ||
|
||
export const metadata: Metadata = { | ||
title: 'Create Next App', | ||
description: 'Generated by create next app', | ||
} | ||
|
||
export default function RootLayout({ | ||
export default async function RootLayout({ | ||
children, | ||
}: Readonly<{ | ||
children: React.ReactNode | ||
}>) { | ||
const {data, tags} = await sanityFetch({query: THEME_QUERY}) | ||
|
||
return ( | ||
<html lang="en"> | ||
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>{children}</body> | ||
<html | ||
lang="en" | ||
className="bg-theme text-theme transition-colors duration-1000 ease-in-out" | ||
style={{ | ||
['--theme-background' as string]: data?.background, | ||
['--theme-text' as string]: data?.text, | ||
}} | ||
> | ||
<body> | ||
<div className="relative flex min-h-dvh flex-col items-center justify-evenly overflow-auto"> | ||
{data?.fetchedAt && ( | ||
<Suspense> | ||
<TimeSince label="layout.tsx" since={data.fetchedAt} /> | ||
</Suspense> | ||
)} | ||
{children} | ||
<Suspense> | ||
<ThemeButton tags={tags!} /> | ||
</Suspense> | ||
</div> | ||
<Suspense> | ||
<SanityLive /> | ||
</Suspense> | ||
</body> | ||
</html> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,101 +1,35 @@ | ||
import Image from 'next/image' | ||
import {sanityFetch} from '@/sanity/fetch' | ||
import './globals.css' | ||
import {defineQuery} from 'groq' | ||
import type {Metadata} from 'next' | ||
import {Suspense} from 'react' | ||
import {TimeSince} from './TimeSince' | ||
|
||
export default function Home() { | ||
return ( | ||
<div className="grid min-h-screen grid-rows-[20px_1fr_20px] items-center justify-items-center gap-16 p-8 pb-20 font-[family-name:var(--font-geist-sans)] sm:p-20"> | ||
<main className="row-start-2 flex flex-col items-center gap-8 sm:items-start"> | ||
<Image | ||
className="dark:invert" | ||
src="https://nextjs.org/icons/next.svg" | ||
alt="Next.js logo" | ||
width={180} | ||
height={38} | ||
priority | ||
/> | ||
<ol className="list-inside list-decimal text-center font-[family-name:var(--font-geist-mono)] text-sm sm:text-left"> | ||
<li className="mb-2"> | ||
Get started by editing{' '} | ||
<code className="rounded bg-black/[.05] px-1 py-0.5 font-semibold dark:bg-white/[.06]"> | ||
src/app/page.tsx | ||
</code> | ||
. | ||
</li> | ||
<li>Save and see your changes instantly.</li> | ||
</ol> | ||
const DEMO_QUERY = defineQuery( | ||
`*[_type == "demo" && slug.current == $slug][0]{title,"fetchedAt": dateTime(now())}`, | ||
) | ||
const slug = 'next-14' | ||
|
||
export async function generateMetadata(): Promise<Metadata> { | ||
const {data} = await sanityFetch({query: DEMO_QUERY, params: {slug}}) | ||
return { | ||
title: data?.title || 'Next 14', | ||
} | ||
} | ||
|
||
<div className="flex flex-col items-center gap-4 sm:flex-row"> | ||
<a | ||
className="bg-foreground text-background flex h-10 items-center justify-center gap-2 rounded-full border border-solid border-transparent px-4 text-sm transition-colors hover:bg-[#383838] sm:h-12 sm:px-5 sm:text-base dark:hover:bg-[#ccc]" | ||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
<Image | ||
className="dark:invert" | ||
src="https://nextjs.org/icons/vercel.svg" | ||
alt="Vercel logomark" | ||
width={20} | ||
height={20} | ||
/> | ||
Deploy now | ||
</a> | ||
<a | ||
className="flex h-10 items-center justify-center rounded-full border border-solid border-black/[.08] px-4 text-sm transition-colors hover:border-transparent hover:bg-[#f2f2f2] sm:h-12 sm:min-w-44 sm:px-5 sm:text-base dark:border-white/[.145] dark:hover:bg-[#1a1a1a]" | ||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
Read our docs | ||
</a> | ||
</div> | ||
</main> | ||
<footer className="row-start-3 flex flex-wrap items-center justify-center gap-6"> | ||
<a | ||
className="flex items-center gap-2 hover:underline hover:underline-offset-4" | ||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
<Image | ||
aria-hidden | ||
src="https://nextjs.org/icons/file.svg" | ||
alt="File icon" | ||
width={16} | ||
height={16} | ||
/> | ||
Learn | ||
</a> | ||
<a | ||
className="flex items-center gap-2 hover:underline hover:underline-offset-4" | ||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
<Image | ||
aria-hidden | ||
src="https://nextjs.org/icons/window.svg" | ||
alt="Window icon" | ||
width={16} | ||
height={16} | ||
/> | ||
Examples | ||
</a> | ||
<a | ||
className="flex items-center gap-2 hover:underline hover:underline-offset-4" | ||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
<Image | ||
aria-hidden | ||
src="https://nextjs.org/icons/globe.svg" | ||
alt="Globe icon" | ||
width={16} | ||
height={16} | ||
/> | ||
Go to nextjs.org → | ||
</a> | ||
</footer> | ||
export default async function Home() { | ||
const {data} = await sanityFetch({query: DEMO_QUERY, params: {slug}}) | ||
|
||
return ( | ||
<div className="ring-theme relative mx-2 rounded-lg px-2 pb-1 pt-8 ring-1"> | ||
<h1 className="min-w-64 text-balance text-4xl font-bold leading-tight tracking-tighter md:text-6xl lg:pr-8 lg:text-8xl"> | ||
{data?.title || 'Next 14'} | ||
</h1> | ||
{data?.fetchedAt && ( | ||
<Suspense> | ||
<TimeSince label="page.tsx" since={data.fetchedAt} /> | ||
</Suspense> | ||
)} | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import {createClient} from '@sanity/client' | ||
|
||
export const client = createClient({ | ||
projectId: 'hiomol4a', | ||
dataset: 'lcapi', | ||
apiVersion: '2024-09-22', | ||
useCdn: false, | ||
}) |
Oops, something went wrong.