mirror of
https://github.com/hanzo-apps/gallery.git
synced 2026-10-30 14:48:38 +00:00
143 lines
4.5 KiB
TypeScript
143 lines
4.5 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import Image from 'next/image';
|
|
import { YStack, XStack, Text, Spinner } from '@hanzo/ui';
|
|
import { Grid } from '@hanzo/ui/grid';
|
|
import { Action, Chip, Display, Eyebrow, Lede, Line, Title } from '@hanzo/ui/marketing';
|
|
import { templates } from './templates-data';
|
|
import { shot } from './lib/shot';
|
|
import { getUniqueTemplates } from './lib/template-utils';
|
|
import type { Template } from './templates-data';
|
|
import { c, column, lift, round } from './lib/design';
|
|
import { Stars } from './components/stars';
|
|
|
|
export default function NotFound() {
|
|
const [pick, setPick] = useState<Template | null>(null);
|
|
const [fading, setFading] = useState(false);
|
|
const unique = getUniqueTemplates(templates);
|
|
|
|
const another = () => unique[Math.floor(Math.random() * unique.length)];
|
|
|
|
useEffect(() => {
|
|
setPick(another());
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
function reroll() {
|
|
setFading(true);
|
|
setTimeout(() => {
|
|
setPick(another());
|
|
setFading(false);
|
|
}, 200);
|
|
}
|
|
|
|
if (!pick) {
|
|
return (
|
|
<YStack minH="70vh" bg={c.ground} items="center" justify="center">
|
|
<Spinner size={32} />
|
|
</YStack>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<YStack minH="70vh" bg={c.ground} items="center" justify="center" py={96}>
|
|
<YStack {...column(896)}>
|
|
<YStack items="center" mb={48} gap={12}>
|
|
<Eyebrow>404</Eyebrow>
|
|
<Display text="center">Template not found</Display>
|
|
<Lede text="center">Here is one to start from instead.</Lede>
|
|
</YStack>
|
|
|
|
<YStack
|
|
transition="quick"
|
|
bg={c.card}
|
|
rounded={round.card}
|
|
borderWidth={1}
|
|
borderColor={c.edge}
|
|
p={32}
|
|
boxShadow={lift.card}
|
|
opacity={fading ? 0.5 : 1}
|
|
scale={fading ? 0.98 : 1}
|
|
>
|
|
<Title quiet mb={24}>
|
|
How about this instead?
|
|
</Title>
|
|
|
|
<Grid columns={{ min: 280, max: 2 }} gap={24} style={{ marginBottom: 24 }}>
|
|
<YStack
|
|
position="relative"
|
|
aspectRatio={16 / 9}
|
|
rounded={round.frame}
|
|
overflow="hidden"
|
|
borderWidth={1}
|
|
borderColor={c.edge}
|
|
bg={c.raised}
|
|
>
|
|
<Image src={shot(pick.screenshot)} alt={pick.displayName} fill unoptimized style={{ objectFit: 'cover' }} />
|
|
</YStack>
|
|
|
|
<YStack justify="center" gap={16}>
|
|
<Line render="h3" size="x2" weight="500" display="block">
|
|
{pick.displayName}
|
|
</Line>
|
|
<Text
|
|
fontSize="$3"
|
|
lineHeight={22}
|
|
color={c.muted}
|
|
style={{
|
|
display: '-webkit-box',
|
|
WebkitLineClamp: 3,
|
|
WebkitBoxOrient: 'vertical',
|
|
overflow: 'hidden',
|
|
}}
|
|
>
|
|
{pick.description || `Premium ${pick.displayName} template with modern design and functionality.`}
|
|
</Text>
|
|
|
|
<XStack flexWrap="wrap" gap={8}>
|
|
<Chip px={12} py={4}>
|
|
{pick.framework}
|
|
</Chip>
|
|
<Chip px={12} py={4}>
|
|
{pick.category}
|
|
</Chip>
|
|
<Chip px={12} py={4}>
|
|
Tier {pick.tier}
|
|
</Chip>
|
|
</XStack>
|
|
|
|
<XStack items="center" gap={8}>
|
|
<Stars n={pick.rating} size="$6" />
|
|
<Text fontSize="$2" lineHeight={18} color={c.faint}>
|
|
({pick.rating}/5)
|
|
</Text>
|
|
</XStack>
|
|
</YStack>
|
|
</Grid>
|
|
|
|
<XStack flexWrap="wrap" gap={12} justify="center">
|
|
<Action fill href={`/templates/${pick.slug}`}>
|
|
View This Template
|
|
</Action>
|
|
<Action render="button" onClick={reroll} disabled={fading}>
|
|
Show Another Random
|
|
</Action>
|
|
</XStack>
|
|
</YStack>
|
|
|
|
<XStack flexWrap="wrap" gap={12} justify="center" mt={48}>
|
|
<Action href="/gallery">← Browse All Templates</Action>
|
|
<Action href="/">Home</Action>
|
|
</XStack>
|
|
|
|
<YStack items="center" mt={48}>
|
|
<Text fontSize="$2" lineHeight={18} color={c.faint}>
|
|
Fun fact: We have {unique.length} amazing templates waiting for you!
|
|
</Text>
|
|
</YStack>
|
|
</YStack>
|
|
</YStack>
|
|
);
|
|
}
|