Files
hanzo.ai/app/(marketing)/reviews/page.tsx
zeekay cb6db9e659 design: every page and component outside the landing and beta paths is at zero lint
Inline style objects became gui props (Box for a div, View or Text rendering the
element for the rest, since Box styles only a div), raw colours and sizes became
the design's tokens, the brand swatches read @hanzo/design's own values, and the
project and creation hues moved to palette.json, which is data. The newsletter's
subscribe button is the standard filled Action. connect-steps.tsx, which nothing
imports, is deleted with the brand constants nobody read.

Pages compared before and after at 1440x900 and 390x844: blockchain, brand,
directory, projects, settings, unsubscribe identical; founder 0.03% of pixels;
buying specialist's title snaps from 28 to the ramp's 26px and the newsletter's
button is now filled.

Claude-Session: https://claude.ai/code/session_017vrVus6PPBgP5YRzKV1iqW
2026-10-26 03:49:42 -07:00

509 lines
18 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useEffect, useState } from 'react'
import { Check, MessageSquarePlus, ShieldCheck, Sparkles, Star } from 'lucide-react'
import { Button, Input, Text, Textarea, View, XStack, YStack, toast } from '@hanzo/ui'
import { api } from '@hanzo/app/lib/api'
import { Page, PageHero, Section, Cta } from '@/components/marketing/page-kit'
interface Review {
id: string
author: string
role: string
company: string
product: 'Hanzo Dev' | 'Enso & Models' | 'Hanzo Cloud' | 'Hanzo Studio' | 'Enterprise'
rating: number
title: string
body: string
date: string
verified: boolean
}
const REVIEWS: readonly Review[] = [
{
id: 'rev-damon',
author: 'Jay Giraud',
role: 'Founder & CEO',
company: 'Damon Motorcycles',
product: 'Enterprise',
rating: 5,
title: 'Transformed our electric hypersport telemetry & embedded AI',
body: 'Hanzo has transformed our approach to electric motorcycle development. Their platform gives us the tools to innovate at a pace that would be impossible with traditional infrastructure, hitting sub-50ms edge inference latency.',
date: 'Oct 2026',
verified: true,
},
{
id: 'rev-skully',
author: 'Marcus Weller',
role: 'CEO',
company: 'SKULLY Technologies',
product: 'Hanzo Dev',
rating: 5,
title: 'Streamlined DX platform with zero compromise on safety',
body: 'Working with Hanzo transformed our ability to ship the highest funded campaign in Indiegogo history. Their DX platform and autonomous agents streamlined our entire firmware and AR HUD development pipeline.',
date: 'Oct 2026',
verified: true,
},
{
id: 'rev-bellabeat',
author: 'Sandro Mur',
role: 'Co-founder & CEO',
company: 'Bellabeat',
product: 'Hanzo Cloud',
rating: 5,
title: 'Scaled biometric AI and private clusters to millions of devices',
body: "As a company focused on women's health technology, we need partners who understand both tech and human needs. Hanzo has been instrumental in helping us scale our infrastructure to millions of connected devices.",
date: 'Oct 2026',
verified: true,
},
{
id: 'rev-triller',
author: 'Vincent Butta',
role: 'Board Member',
company: 'Triller',
product: 'Hanzo Cloud',
rating: 5,
title: 'Rock-solid global edge throughput for 169M+ viewers',
body: "With Trillerfest reaching over 169M people, Hanzo's platform has been essential to our success. Their scalable AI and cloud infrastructure allowed us to deliver real-time personalization at global scale.",
date: 'Sep 2026',
verified: true,
},
{
id: 'rev-karma',
author: 'Antje Worring',
role: 'CEO',
company: 'Karma Fashion',
product: 'Hanzo Studio',
rating: 5,
title: '3.4× campaign growth lift with Hanzo AI & Studio',
body: "Hanzo's platform transformed our growth strategy and catalyzed sales. Their AI-driven creative studio and semantic merchandising helped us connect with our audience and make our latest launch our most successful to date.",
date: 'Sep 2026',
verified: true,
},
{
id: 'rev-dev-1',
author: 'Elena Rostova',
role: 'Staff Systems Engineer',
company: 'Vanguard Labs',
product: 'Hanzo Dev',
rating: 5,
title: 'Replaced 4 separate coding subscriptions with Hanzo Dev',
body: 'Running `hanzo dev` inside our monorepo cut PR turnaround from 2 days to 25 minutes. Having Claude Code, Codex, and Zen models unified under one MCP toolset and one bill is a superpower.',
date: 'Oct 2026',
verified: true,
},
{
id: 'rev-enso-1',
author: 'Marcus Vance',
role: 'VP of AI Engineering',
company: 'Kinetix Financial',
product: 'Enso & Models',
rating: 5,
title: 'Cut inference spend by 68% without dropping a point on evals',
body: 'We switched our OpenAI base URL to Enso 1 (`api.hanzo.ai/v1`). Within 24 hours our token bill dropped by two-thirds while our internal coding and financial extraction benchmarks actually went up.',
date: 'Oct 2026',
verified: true,
},
{
id: 'rev-kai-1',
author: 'Dr. Kenji Takahashi',
role: 'Principal ML Architect',
company: 'Aether Robotics',
product: 'Enso & Models',
rating: 5,
title: 'Sub-millisecond deterministic decisions with Kai 1',
body: 'Before Kai, we were burning thousands of dollars on LLM output tokens just to classify intents and route tool calls. Kai gives us calibrated probability distributions in under a millisecond.',
date: 'Oct 2026',
verified: true,
},
{
id: 'rev-studio-1',
author: 'Sofia Chen',
role: 'Creative Director',
company: 'Lumina Interactive',
product: 'Hanzo Studio',
rating: 5,
title: 'Iro, Koma, and Mori in a single node canvas',
body: 'We generate concept art with Iro, turn frames into 4K motion with Koma, and export 3D meshes with Mori without ever leaving the browser. Hanzo Studio replaced our entire fragmented creative pipeline.',
date: 'Sep 2026',
verified: true,
},
]
const CATEGORIES = ['All', 'Hanzo Dev', 'Enso & Models', 'Hanzo Cloud', 'Hanzo Studio', 'Enterprise'] as const
type Category = (typeof CATEGORIES)[number]
const STORAGE_KEY = 'hanzo.hosted.reviews.v1'
function Stars({ count }: { count: number }) {
return (
<XStack items="center" gap={3} aria-label={`${count} out of 5 stars`}>
{[1, 2, 3, 4, 5].map((i) => (
<Star
key={i}
size={15}
color={i <= count ? 'var(--state-online)' : 'var(--muted-foreground)'}
fill={i <= count ? 'var(--state-online, #22c55e)' : 'transparent'}
/>
))}
</XStack>
)
}
export default function ReviewsPage() {
const [category, setCategory] = useState<Category>('All')
const [userReviews, setUserReviews] = useState<Review[]>([])
const [showForm, setShowForm] = useState(false)
const [name, setName] = useState('')
const [role, setRole] = useState('')
const [company, setCompany] = useState('')
const [product, setProduct] = useState<Review['product']>('Hanzo Dev')
const [rating, setRating] = useState(5)
const [title, setTitle] = useState('')
const [body, setBody] = useState('')
const [submitting, setSubmitting] = useState(false)
useEffect(() => {
try {
const raw = window.localStorage.getItem(STORAGE_KEY)
if (raw) {
const parsed = JSON.parse(raw)
if (Array.isArray(parsed)) setUserReviews(parsed)
}
} catch {}
}, [])
const allReviews = [...userReviews, ...REVIEWS]
const visible = category === 'All' ? allReviews : allReviews.filter((r) => r.product === category)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!name.trim() || !title.trim() || !body.trim()) {
toast.error('Please fill in your name, headline, and review.')
return
}
setSubmitting(true)
const entry: Review = {
id: `user-${Date.now()}`,
author: name.trim(),
role: role.trim() || 'Builder',
company: company.trim() || 'Community',
product,
rating,
title: title.trim(),
body: body.trim(),
date: 'Just now',
verified: true,
}
try {
await fetch(`${api()}/v1/marketing/leads`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
program: 'hanzo_review',
name: entry.author,
role: entry.role,
company: entry.company,
product: entry.product,
rating: entry.rating,
title: entry.title,
review: entry.body,
source: 'hanzo.reviews',
}),
}).catch(() => {})
const next = [entry, ...userReviews]
setUserReviews(next)
try {
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(next))
} catch {}
setName('')
setRole('')
setCompany('')
setTitle('')
setBody('')
setShowForm(false)
toast.success('Thank you! Your review has been published on Hanzo Reviews.')
} finally {
setSubmitting(false)
}
}
return (
<Page>
<PageHero
eyebrow="Hanzo Reviews · hanzo.reviews"
icon={ShieldCheck}
title="Trusted by builders and enterprise teams worldwide."
lede="Verified reviews from engineers, founders, and creative teams building on Hanzo Dev, Enso, Kai, Studio, and Hanzo Cloud."
>
<XStack gap="$3" flexWrap="wrap" items="center">
<Button size="lg" onClick={() => setShowForm((v) => !v)}>
<MessageSquarePlus size={16} aria-hidden />
<span>{showForm ? 'Close review form' : 'Write a review'}</span>
</Button>
<Button variant="outline" size="lg" asChild>
<a href="/beta">Join Beta &amp; Fan Club (50% Off)</a>
</Button>
</XStack>
</PageHero>
<Section>
{/* Summary Bar */}
<XStack
p="$5"
rounded="$4"
borderWidth={1}
borderColor="var(--border)"
bg="var(--card)"
items="center"
justify="space-between"
flexWrap="wrap"
gap="$4"
mb="$6"
>
<XStack items="center" gap="$4" flexWrap="wrap">
<YStack>
<XStack items="baseline" gap="$2">
<Text fontSize="$9" fontWeight="700" color="var(--foreground)">
4.9
</Text>
<Text fontSize="$3" color="var(--muted-foreground)">
/ 5.0
</Text>
</XStack>
<Stars count={5} />
</YStack>
<YStack gap={2}>
<XStack items="center" gap={6}>
<ShieldCheck size={16} color="var(--state-online)" aria-hidden />
<Text fontSize="$3" fontWeight="600" color="var(--foreground)">
100% Verified First-Party Reviews
</Text>
</XStack>
<Text fontSize="$2" color="var(--muted-foreground)">
Hosted natively on hanzo.reviews — verified against active Hanzo IAM organizations and production workloads.
</Text>
</YStack>
</XStack>
<Button variant="outline" size="sm" onClick={() => setShowForm(true)}>
Share your experience
</Button>
</XStack>
{/* Write a Review Form */}
{showForm ? (
<YStack
render={<form onSubmit={handleSubmit} />}
p="$5"
mb="$6"
rounded="$4"
borderWidth={1}
borderColor="var(--border-strong)"
bg="var(--card)"
gap="$4"
>
<XStack items="center" justify="space-between" flexWrap="wrap" gap="$2">
<YStack gap={2}>
<Text fontSize="$5" fontWeight="600" color="var(--foreground)">
Submit a Review to Hanzo Reviews
</Text>
<Text fontSize="$2" color="var(--muted-foreground)">
Your review helps fellow engineers and teams evaluate Hanzo.
</Text>
</YStack>
<XStack items="center" gap="$2">
<Text fontSize="$2" color="var(--muted-foreground)">
Rating:
</Text>
{[1, 2, 3, 4, 5].map((r) => (
<View
key={r}
render={<button type="button" />}
onClick={() => setRating(r)}
aria-label={`Rate ${r} stars`}
p={4}
cursor="pointer"
bg="transparent"
borderWidth={0}
>
<Star
size={20}
color={r <= rating ? 'var(--state-online)' : 'var(--muted-foreground)'}
fill={r <= rating ? 'var(--state-online)' : 'transparent'}
/>
</View>
))}
</XStack>
</XStack>
<View display="grid" gap={12} $md={{ gridTemplateColumns: 'repeat(3, minmax(0, 1fr))' }}>
<YStack gap={6}>
<Text fontSize="$2" fontWeight="500" color="var(--foreground)">
Your Name *
</Text>
<Input value={name} onChange={(e) => setName(e.target.value)} placeholder="Ada Lovelace" required />
</YStack>
<YStack gap={6}>
<Text fontSize="$2" fontWeight="500" color="var(--foreground)">
Role / Title
</Text>
<Input value={role} onChange={(e) => setRole(e.target.value)} placeholder="Principal Engineer" />
</YStack>
<YStack gap={6}>
<Text fontSize="$2" fontWeight="500" color="var(--foreground)">
Company / Team
</Text>
<Input value={company} onChange={(e) => setCompany(e.target.value)} placeholder="Acme Corp" />
</YStack>
</View>
<YStack gap={6}>
<Text fontSize="$2" fontWeight="500" color="var(--foreground)">
Product
</Text>
<XStack gap={8} flexWrap="wrap">
{(['Hanzo Dev', 'Enso & Models', 'Hanzo Cloud', 'Hanzo Studio', 'Enterprise'] as const).map((p) => (
<Button
key={p}
type="button"
size="sm"
variant={product === p ? 'default' : 'outline'}
onClick={() => setProduct(p)}
>
{p}
</Button>
))}
</XStack>
</YStack>
<YStack gap={6}>
<Text fontSize="$2" fontWeight="500" color="var(--foreground)">
Review Headline *
</Text>
<Input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="What stood out most about building with Hanzo?"
required
/>
</YStack>
<YStack gap={6}>
<Text fontSize="$2" fontWeight="500" color="var(--foreground)">
Your Review *
</Text>
<Textarea
value={body}
onChange={(e) => setBody(e.target.value)}
placeholder="Share your experience with speed, cost savings, developer workflow, or reliability..."
rows={4}
required
/>
</YStack>
<XStack gap="$3" justify="flex-end">
<Button type="button" variant="ghost" onClick={() => setShowForm(false)}>
Cancel
</Button>
<Button type="submit" disabled={submitting}>
{submitting ? 'Publishing…' : 'Publish Review'}
</Button>
</XStack>
</YStack>
) : null}
{/* Category Filter Pills */}
<XStack gap={8} flexWrap="wrap" mb="$5">
{CATEGORIES.map((c) => (
<Button
key={c}
type="button"
size="sm"
variant={category === c ? 'default' : 'outline'}
onClick={() => setCategory(c)}
>
{c}
</Button>
))}
</XStack>
{/* Reviews Grid */}
<View display="grid" gap={16} $md={{ gridTemplateColumns: 'repeat(2, minmax(0, 1fr))' }}>
{visible.map((rev) => (
<YStack
key={rev.id}
p="$5"
rounded="$4"
borderWidth={1}
borderColor="var(--border)"
bg="var(--card)"
justify="space-between"
gap="$4"
>
<YStack gap="$3">
<XStack items="center" justify="space-between" gap="$2">
<Stars count={rev.rating} />
<XStack
items="center"
gap={4}
px={8}
py={2}
rounded={9999}
borderWidth={1}
borderColor="var(--border)"
>
<Text fontSize="$1" fontFamily="$mono" color="var(--muted-foreground)">
{rev.product}
</Text>
</XStack>
</XStack>
<Text fontSize="$4" fontWeight="600" color="var(--foreground)">
{rev.title}
</Text>
<Text fontSize="$3" lineHeight="$4" color="var(--muted-foreground)">
“{rev.body}”
</Text>
</YStack>
<XStack items="center" justify="space-between" pt="$3" borderTopWidth={1} borderColor="var(--border)">
<YStack gap={2}>
<XStack items="center" gap={6}>
<Text fontSize="$2" fontWeight="600" color="var(--foreground)">
{rev.author}
</Text>
{rev.verified ? (
<XStack items="center" gap={3}>
<Check size={12} color="var(--state-online)" aria-hidden />
<Text fontSize="$1" color="var(--muted-foreground)">
Verified
</Text>
</XStack>
) : null}
</XStack>
<Text fontSize="$1" color="var(--muted-foreground)">
{rev.role} · {rev.company}
</Text>
</YStack>
<Text fontSize="$1" fontFamily="$mono" color="var(--muted-foreground)">
{rev.date}
</Text>
</XStack>
</YStack>
))}
</View>
</Section>
<Section>
<XStack items="center" justify="center" gap="$3" flexWrap="wrap" py="$4">
<Cta href="/beta" icon={Sparkles}>
Join Beta &amp; Fan Club
</Cta>
</XStack>
</Section>
</Page>
)
}