'use client'
import {
motion,
useMotionTemplate,
useMotionValue,
type MotionValue,
} from 'framer-motion'
import Link from 'next/link'
import { GridPattern } from '@/components/GridPattern'
import { Heading } from '@/components/Heading'
import { BoltIcon } from '@/components/icons/BoltIcon'
import { ClipboardIcon } from '@/components/icons/ClipboardIcon'
import { ListIcon } from '@/components/icons/ListIcon'
import { TagIcon } from '@/components/icons/TagIcon'
interface Resource {
href: string
name: string
description: string
icon: React.ComponentType<{ className?: string }>
pattern: Omit<
React.ComponentPropsWithoutRef<typeof GridPattern>,
'width' | 'height' | 'x'
>
}
const resources: Array<Resource> = [
{
href: '/building-tokens',
name: 'Tokens',
description:
'Learn how to create PASETO tokens with the fluent builder API and encrypt or sign your data.',
icon: TagIcon,
pattern: {
y: 16,
squares: [
[0, 1],
[1, 3],
],
},
},
{
href: '/claims',
name: 'Claims',
description:
'Understand the seven standard PASETO claims and how to add custom claims to your tokens.',
icon: ClipboardIcon,
pattern: {
y: -6,
squares: [
[-1, 2],
[1, 3],
],
},
},
{
href: '/keys',
name: 'Keys',
description:
'Manage symmetric keys for local tokens and asymmetric key pairs for public tokens.',
icon: BoltIcon,
pattern: {
y: 32,
squares: [
[0, 2],
[1, 4],
],
},
},
{
href: '/versions',
name: 'Versions',
description:
'Choose between PASETO versions V1-V4 and understand Local vs Public purposes.',
icon: ListIcon,
pattern: {
y: 22,
squares: [[0, 1]],
},
},
]
function ResourceIcon({ icon: Icon }: { icon: Resource['icon'] }) {
return (
<div className="flex h-7 w-7 items-center justify-center rounded-full bg-zinc-900/5 ring-1 ring-zinc-900/25 backdrop-blur-[2px] transition duration-300 group-hover:bg-white/50 group-hover:ring-zinc-900/25 dark:bg-white/7.5 dark:ring-white/15 dark:group-hover:bg-accent-300/10 dark:group-hover:ring-accent-400">
<Icon className="h-5 w-5 fill-zinc-700/10 stroke-zinc-700 transition-colors duration-300 group-hover:stroke-zinc-900 dark:fill-white/10 dark:stroke-zinc-400 dark:group-hover:fill-accent-300/10 dark:group-hover:stroke-accent-400" />
</div>
)
}
function ResourcePattern({
mouseX,
mouseY,
...gridProps
}: Resource['pattern'] & {
mouseX: MotionValue<number>
mouseY: MotionValue<number>
}) {
let maskImage = useMotionTemplate`radial-gradient(180px at ${mouseX}px ${mouseY}px, white, transparent)`
let style = { maskImage, WebkitMaskImage: maskImage }
return (
<div className="pointer-events-none">
<div className="absolute inset-0 rounded-2xl mask-[linear-gradient(white,transparent)] transition duration-300 group-hover:opacity-50">
<GridPattern
width={72}
height={56}
x="50%"
className="absolute inset-x-0 inset-y-[-30%] h-[160%] w-full skew-y-[-18deg] fill-black/2 stroke-black/5 dark:fill-white/1 dark:stroke-white/2.5"
{...gridProps}
/>
</div>
<motion.div
className="absolute inset-0 rounded-2xl bg-linear-to-r from-[oklch(97%_0.02_27.518)] to-[oklch(85%_0.08_27.518)] opacity-0 transition duration-300 group-hover:opacity-100 dark:from-[oklch(15%_0.06_27.518)] dark:to-[oklch(22%_0.09_27.518)]"
style={style}
/>
<motion.div
className="absolute inset-0 rounded-2xl opacity-0 mix-blend-overlay transition duration-300 group-hover:opacity-100"
style={style}
>
<GridPattern
width={72}
height={56}
x="50%"
className="absolute inset-x-0 inset-y-[-30%] h-[160%] w-full skew-y-[-18deg] fill-black/50 stroke-black/70 dark:fill-white/2.5 dark:stroke-white/10"
{...gridProps}
/>
</motion.div>
</div>
)
}
function Resource({ resource }: { resource: Resource }) {
let mouseX = useMotionValue(0)
let mouseY = useMotionValue(0)
function onMouseMove({
currentTarget,
clientX,
clientY,
}: React.MouseEvent<HTMLDivElement>) {
let { left, top } = currentTarget.getBoundingClientRect()
mouseX.set(clientX - left)
mouseY.set(clientY - top)
}
return (
<div
key={resource.href}
onMouseMove={onMouseMove}
className="group relative flex rounded-2xl bg-zinc-50 transition-shadow hover:shadow-md hover:shadow-zinc-900/5 dark:bg-white/2.5 dark:hover:shadow-black/5"
>
<ResourcePattern {...resource.pattern} mouseX={mouseX} mouseY={mouseY} />
<div className="absolute inset-0 rounded-2xl ring-1 ring-zinc-900/7.5 ring-inset group-hover:ring-zinc-900/10 dark:ring-white/10 dark:group-hover:ring-white/20" />
<div className="relative rounded-2xl px-4 pt-16 pb-4">
<ResourceIcon icon={resource.icon} />
<h3 className="mt-4 text-sm/7 font-semibold text-zinc-900 dark:text-white">
<Link href={resource.href}>
<span className="absolute inset-0 rounded-2xl" />
{resource.name}
</Link>
</h3>
<p className="mt-1 text-sm text-zinc-600 dark:text-zinc-400">
{resource.description}
</p>
</div>
</div>
)
}
export function Resources() {
return (
<div className="my-16 xl:max-w-none">
<Heading level={2} id="resources">
Resources
</Heading>
<div className="not-prose mt-4 grid grid-cols-1 gap-8 border-t border-zinc-900/5 pt-10 sm:grid-cols-2 xl:grid-cols-4 dark:border-white/5">
{resources.map((resource) => (
<Resource key={resource.href} resource={resource} />
))}
</div>
</div>
)
}