mirror of
https://github.com/hanzo-apps/gallery.git
synced 2026-10-30 14:48:38 +00:00
293 lines
11 KiB
TypeScript
293 lines
11 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { Dialog, DialogContent, YStack, XStack, Text, ScrollView } from '@hanzo/ui';
|
|
import { Grid } from '@hanzo/ui/grid';
|
|
import { Action } from '@hanzo/ui/marketing';
|
|
import type { Template } from '../templates-data';
|
|
import { c, round, mono } from '../lib/design';
|
|
|
|
interface ForkModalProps {
|
|
template: Template;
|
|
onClose: () => void;
|
|
}
|
|
|
|
const deploy = `
|
|
|
|
# Deploy to Hanzo Cloud
|
|
npx hanzo deploy`;
|
|
|
|
function setupCommands(template: Template): string {
|
|
const f = template.framework.toLowerCase();
|
|
const cd = `# Navigate to template\ncd "${template.path}"`;
|
|
const install = `\n\n# Install dependencies\nnpm install`;
|
|
|
|
if (f.includes('next.js') || f.includes('nextjs'))
|
|
return `${cd}${install}\n\n# Set up environment (if needed)\ncp .env.example .env.local\n\n# Run development server\nnpm run dev\n\n# Build for production\nnpm run build${deploy}`;
|
|
|
|
if (f.includes('react') && (f.includes('vite') || f.includes('18')))
|
|
return `${cd}${install}\n\n# Run development\nnpm run dev\n\n# Build for production\nnpm run build${deploy}`;
|
|
|
|
if (f.includes('react') && f.includes('cra'))
|
|
return `${cd}${install}\n\n# Run development\nnpm start\n\n# Build for production\nnpm run build${deploy}`;
|
|
|
|
if (f.includes('html') && f.includes('gulp'))
|
|
return `${cd}${install}\n\n# Run development\ngulp\n\n# Build for production\ngulp build\n\n# Deploy to Hanzo Cloud (static site)\nnpx hanzo deploy --static`;
|
|
|
|
if (f.includes('html'))
|
|
return `${cd}\n\n# Install dependencies (if needed)\nnpm install\n\n# Serve locally\nnpx serve .\n\n# Deploy to Hanzo Cloud (static site)\nnpx hanzo deploy --static`;
|
|
|
|
return `${cd}\n\n# See README for framework-specific setup`;
|
|
}
|
|
|
|
function estimate(template: Template): string {
|
|
const f = template.framework.toLowerCase();
|
|
if (f.includes('next.js') || f.includes('nextjs')) return '2-3 minutes';
|
|
if (f.includes('react')) return '3-4 minutes';
|
|
if (f.includes('html') && f.includes('gulp')) return '2-3 minutes';
|
|
return '2-5 minutes';
|
|
}
|
|
|
|
type Method = 'cloud' | 'local' | 'github';
|
|
|
|
const methods: { id: Method; head: string; note: string }[] = [
|
|
{ id: 'cloud', head: 'Deploy to Hanzo Cloud', note: 'One-click deployment to Hanzo global edge network' },
|
|
{ id: 'local', head: 'Download & Deploy Locally', note: 'Download template and deploy from your machine' },
|
|
{ id: 'github', head: 'Clone to GitHub', note: 'Fork to your GitHub and connect to Hanzo' },
|
|
];
|
|
|
|
const perks = [
|
|
'Instant global deployment',
|
|
'Global edge network (CDN)',
|
|
'Auto-scaling infrastructure',
|
|
'Built-in analytics dashboard',
|
|
'Automated CI/CD pipeline',
|
|
'SSL certificates included',
|
|
'Performance monitoring',
|
|
'99.99% uptime SLA',
|
|
];
|
|
|
|
/** A section heading inside the dialog. */
|
|
function Head({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<Text render="h3" m={0} fontSize="$3" lineHeight={20} fontWeight="600" color={c.ink}>
|
|
{children}
|
|
</Text>
|
|
);
|
|
}
|
|
|
|
/** A bordered well: the setup block and the path row each sit in one. */
|
|
function Well({ children }: { children: React.ReactNode }) {
|
|
return (
|
|
<YStack bg={c.card} rounded={round.frame} borderWidth={1} borderColor={c.edge} p={16} mb={20}>
|
|
{children}
|
|
</YStack>
|
|
);
|
|
}
|
|
|
|
export function ForkModal({ template, onClose }: ForkModalProps) {
|
|
const [method, setMethod] = useState<Method | null>(null);
|
|
const [busy, setBusy] = useState(false);
|
|
|
|
const repo = `https://github.com/hanzo-apps/template-${template.slug}`;
|
|
|
|
const run = async () => {
|
|
setBusy(true);
|
|
try {
|
|
if (method === 'cloud') {
|
|
await new Promise((r) => setTimeout(r, 2000));
|
|
alert('🚀 Deployment initiated!\n\nYour template is being deployed to Hanzo Cloud.\nYou will receive a deployment URL shortly.');
|
|
} else if (method === 'local') {
|
|
window.open(repo, '_blank');
|
|
alert('📦 Opening GitHub repository!\n\nClone the repo and follow the setup commands.');
|
|
} else if (method === 'github') {
|
|
await new Promise((r) => setTimeout(r, 1500));
|
|
alert('🔗 GitHub fork created!\n\nRepository forked to your account.\nConnect to Hanzo Cloud in the next step.');
|
|
}
|
|
} catch (error) {
|
|
alert('Error: ' + (error instanceof Error ? error.message : 'Unknown error'));
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
};
|
|
|
|
const copy = (text: string, said: string) => {
|
|
navigator.clipboard.writeText(text);
|
|
alert(said);
|
|
};
|
|
|
|
const ready = method !== null && !busy;
|
|
|
|
return (
|
|
<Dialog modal open onOpenChange={(open: boolean) => !open && onClose()}>
|
|
<DialogContent
|
|
showCloseButton={false}
|
|
width="100%"
|
|
maxW={896}
|
|
maxH="90vh"
|
|
p={0}
|
|
overflow="hidden"
|
|
rounded={round.card}
|
|
borderWidth={1}
|
|
borderColor={c.edge}
|
|
bg={c.card}
|
|
>
|
|
<XStack p={24} gap={16} justify="space-between" items="flex-start" borderBottomWidth={1} borderColor={c.edge}>
|
|
<YStack shrink={1}>
|
|
<Text render="h2" m={0} mb={6} fontSize="$7" lineHeight={26} fontWeight="500" color={c.ink}>
|
|
Fork {template.displayName} on Hanzo AI
|
|
</Text>
|
|
<Text fontSize="$2" lineHeight={18} color={c.muted}>
|
|
{template.framework} · {template.category}
|
|
</Text>
|
|
</YStack>
|
|
<Text
|
|
render="button"
|
|
onClick={onClose}
|
|
aria-label="Close"
|
|
display="grid"
|
|
placeItems="center"
|
|
shrink={0}
|
|
width={36}
|
|
height={36}
|
|
p={0}
|
|
borderWidth={0}
|
|
rounded={round.pill}
|
|
bg="transparent"
|
|
color={c.muted}
|
|
cursor="pointer"
|
|
hoverStyle={{ bg: c.wash, color: c.ink }}
|
|
>
|
|
<svg width={16} height={16} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" aria-hidden="true">
|
|
<path d="M6 6l12 12M18 6L6 18" />
|
|
</svg>
|
|
</Text>
|
|
</XStack>
|
|
|
|
<ScrollView flex={1} p={24}>
|
|
<YStack mb={24} gap={14}>
|
|
<Head>Choose Deployment Method</Head>
|
|
<Grid columns={{ min: 220, max: 3 }} gap={12}>
|
|
{methods.map((m) => {
|
|
const on = method === m.id;
|
|
return (
|
|
<YStack
|
|
key={m.id}
|
|
render="button"
|
|
onPress={() => setMethod(m.id)}
|
|
aria-pressed={on}
|
|
items="flex-start"
|
|
cursor="pointer"
|
|
p={16}
|
|
gap={6}
|
|
rounded={round.frame}
|
|
borderWidth={1}
|
|
borderColor={on ? c.chosen : c.edge}
|
|
bg={on ? c.wash : 'transparent'}
|
|
hoverStyle={{ borderColor: on ? c.chosen : c.strong }}
|
|
>
|
|
<Text fontSize="$3" lineHeight={20} fontWeight="600" color={c.ink} text="left">
|
|
{m.head}
|
|
</Text>
|
|
<Text fontSize="$2" lineHeight={18} color={c.muted} text="left">
|
|
{m.note}
|
|
</Text>
|
|
<Text {...mono} fontSize="$1" lineHeight={16} color={c.faint} text="left">
|
|
{m.id === 'cloud'
|
|
? `Estimated: ${estimate(template)}`
|
|
: m.id === 'local'
|
|
? `Path: ${template.path}`
|
|
: 'git clone ...'}
|
|
</Text>
|
|
</YStack>
|
|
);
|
|
})}
|
|
</Grid>
|
|
</YStack>
|
|
|
|
<Well>
|
|
<XStack items="center" justify="space-between" mb={12}>
|
|
<Head>Setup Commands</Head>
|
|
<Action render="button" onClick={() => copy(setupCommands(template), '✅ Setup commands copied to clipboard!')} minH={32} px={12}>
|
|
Copy
|
|
</Action>
|
|
</XStack>
|
|
<YStack bg={c.ground} p={16} rounded={round.control} borderWidth={1} borderColor={c.edge} overflow="scroll">
|
|
<Text render="pre" m={0} {...mono} fontSize="$2" lineHeight={20} color={c.ink} style={{ whiteSpace: 'pre' }}>
|
|
{setupCommands(template)}
|
|
</Text>
|
|
</YStack>
|
|
</Well>
|
|
|
|
<Well>
|
|
<XStack items="center" justify="space-between" gap={16}>
|
|
<YStack shrink={1} minW={0} gap={4}>
|
|
<Head>Template Path</Head>
|
|
<Text {...mono} fontSize="$2" lineHeight={18} color={c.muted}>
|
|
{template.path}
|
|
</Text>
|
|
</YStack>
|
|
<Action
|
|
render="button"
|
|
onClick={() => copy(template.path, `📋 Path copied!\n\nRelative path: ${template.path}`)}
|
|
shrink={0}
|
|
whiteSpace="nowrap"
|
|
>
|
|
Copy Path
|
|
</Action>
|
|
</XStack>
|
|
</Well>
|
|
|
|
<YStack rounded={round.frame} borderWidth={1} borderColor={c.edge} p={24} gap={16}>
|
|
<Head>What You Get with Hanzo AI</Head>
|
|
<Grid columns={{ min: 240, max: 2 }} gap={10}>
|
|
{perks.map((perk) => (
|
|
<XStack key={perk} items="center" gap={8}>
|
|
<Text aria-hidden fontSize="$2" color={c.ink}>
|
|
✓
|
|
</Text>
|
|
<Text fontSize="$2" lineHeight={20} color={c.muted}>
|
|
{perk}
|
|
</Text>
|
|
</XStack>
|
|
))}
|
|
</Grid>
|
|
</YStack>
|
|
</ScrollView>
|
|
|
|
<XStack p={20} px={24} items="center" justify="space-between" gap={16} flexWrap="wrap" borderTopWidth={1} borderColor={c.edge} bg={c.card}>
|
|
<Text fontSize="$2" lineHeight={18} color={c.muted}>
|
|
{method
|
|
? `Ready to ${method === 'cloud' ? 'deploy' : method === 'local' ? 'download' : 'clone'}?`
|
|
: 'Select a deployment method to continue'}
|
|
</Text>
|
|
<XStack gap={10}>
|
|
<Action render="button" onClick={onClose}>
|
|
Cancel
|
|
</Action>
|
|
<Action
|
|
fill
|
|
render="button"
|
|
onClick={run}
|
|
disabled={!ready}
|
|
aria-disabled={!ready}
|
|
opacity={ready ? 1 : 0.4}
|
|
cursor={ready ? 'pointer' : 'not-allowed'}
|
|
>
|
|
{busy
|
|
? 'Processing…'
|
|
: method === 'cloud'
|
|
? 'Deploy now'
|
|
: method === 'local'
|
|
? 'Download now'
|
|
: method === 'github'
|
|
? 'Clone to GitHub'
|
|
: 'Select option'}
|
|
</Action>
|
|
</XStack>
|
|
</XStack>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|