Blend the verify tutorial into the step; remove it from /organizations

Add hideHeader, bare, and stacked props to VerifyTutorial. The how-to step
now renders it borderless with the header hidden (single step header) and
the demo card stacked full-width above the step list so it matches the
button width below. Remove the tutorial from /organizations, leaving just
the 'Start verifying' CTA card.
This commit is contained in:
lemon
2026-06-12 18:23:00 -07:00
parent 528e87e9e4
commit abad9eca9d
3 changed files with 82 additions and 57 deletions
+1 -1
View File
@@ -512,7 +512,7 @@ function VerifierHowtoStep({ onFinish }: { onFinish: () => void }) {
</p>
</div>
<VerifyTutorial />
<VerifyTutorial hideHeader bare stacked />
<Button onClick={onFinish} className="w-full h-12 text-base rounded-full">
{t('onboarding.verifier.howto.finish')}
+58 -22
View File
@@ -66,7 +66,24 @@ function usePrefersReducedMotion(): boolean {
return ref.current;
}
export function VerifyTutorial({ className }: { className?: string }) {
interface VerifyTutorialProps {
className?: string;
/** Hide the component's internal eyebrow/title/lede header (when the host
* already provides one). */
hideHeader?: boolean;
/** Drop the bordered card chrome so it blends into the surrounding page. */
bare?: boolean;
/** Stack the demo full-width above the step list instead of the
* side-by-side two-column layout. */
stacked?: boolean;
}
export function VerifyTutorial({
className,
hideHeader = false,
bare = false,
stacked = false,
}: VerifyTutorialProps) {
const { t } = useTranslation();
const reducedMotion = usePrefersReducedMotion();
@@ -118,40 +135,50 @@ export function VerifyTutorial({ className }: { className?: string }) {
return (
<section
className={cn(
'rounded-2xl border border-primary/20 bg-gradient-to-br from-primary/[0.07] via-background to-background p-6 sm:p-8 shadow-sm',
!bare &&
'rounded-2xl border border-primary/20 bg-gradient-to-br from-primary/[0.07] via-background to-background p-6 sm:p-8 shadow-sm',
className,
)}
aria-labelledby="verify-tutorial-title"
>
{/* Header */}
<div className="flex flex-wrap items-start justify-between gap-4 mb-8">
<div className="max-w-md">
<p className="inline-flex items-center gap-1.5 text-xs font-semibold tracking-widest uppercase text-primary mb-2">
<BadgeCheck className="size-4" />
{t('organizations.tutorial.eyebrow')}
</p>
<h3
id="verify-tutorial-title"
className="text-xl sm:text-2xl font-bold tracking-tight mb-2"
>
{t('organizations.tutorial.title')}
</h3>
<p className="text-sm text-muted-foreground leading-relaxed">
{t('organizations.tutorial.lede')}
</p>
{!hideHeader && (
<div className="flex flex-wrap items-start justify-between gap-4 mb-8">
<div className="max-w-md">
<p className="inline-flex items-center gap-1.5 text-xs font-semibold tracking-widest uppercase text-primary mb-2">
<BadgeCheck className="size-4" />
{t('organizations.tutorial.eyebrow')}
</p>
<h3
id="verify-tutorial-title"
className="text-xl sm:text-2xl font-bold tracking-tight mb-2"
>
{t('organizations.tutorial.title')}
</h3>
<p className="text-sm text-muted-foreground leading-relaxed">
{t('organizations.tutorial.lede')}
</p>
</div>
</div>
</div>
)}
<div className="grid gap-8 lg:grid-cols-2 lg:items-center">
{/* ── Left: animated mock campaign card ───────────────────────── */}
<div
className={cn(
stacked
? 'space-y-6'
: 'grid gap-8 lg:grid-cols-2 lg:items-center',
)}
>
{/* ── Animated mock campaign card ──────────────────────────────── */}
<DemoStage
phaseIndex={phaseIndex}
menuVisible={menuVisible}
verified={verified}
reducedMotion={reducedMotion}
fullWidth={stacked}
/>
{/* ── Right: step list, synced to the animation ───────────────── */}
{/* ── Step list, synced to the animation ──────────────────────── */}
<ol className="space-y-3">
{stepCopy.map((step, i) => {
const active = i === phaseIndex;
@@ -212,6 +239,8 @@ interface DemoStageProps {
menuVisible: boolean;
verified: boolean;
reducedMotion: boolean;
/** Span the full container width instead of the narrow `max-w-sm` card. */
fullWidth?: boolean;
}
function DemoStage({
@@ -219,11 +248,18 @@ function DemoStage({
menuVisible,
verified,
reducedMotion,
fullWidth = false,
}: DemoStageProps) {
const { t } = useTranslation();
return (
<div className="relative mx-auto w-full max-w-sm select-none" aria-hidden="true">
<div
className={cn(
'relative w-full select-none',
fullWidth ? 'mx-0' : 'mx-auto max-w-sm',
)}
aria-hidden="true"
>
{/* Mock campaign card */}
<div className="overflow-hidden rounded-2xl border border-border/60 bg-card shadow-md">
{/* Banner */}
+23 -34
View File
@@ -8,13 +8,10 @@ import {
ShieldCheck,
} from 'lucide-react';
import { VerifyTutorial } from '@/components/organizations/VerifyTutorial';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { useAppContext } from '@/hooks/useAppContext';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useOnboarding } from '@/contexts/onboardingContextDef';
import { useVerifierStatement } from '@/hooks/useVerifierStatement';
/**
* The /organizations page. A landing-style document modeled on the
@@ -181,40 +178,32 @@ export function OrganizationsPage() {
*/
function VerifierEditor() {
const { t } = useTranslation();
const { user } = useCurrentUser();
const { isVerifier } = useVerifierStatement(user?.pubkey);
const { startSignup } = useOnboarding();
return (
<div className="space-y-8">
<Card className="border-border/60 shadow-sm">
<CardContent className="py-12 px-8 flex flex-col items-center gap-6 text-center">
<div className="p-4 rounded-full bg-primary/10">
<Building2 className="size-8 text-primary" />
</div>
<div className="space-y-2 max-w-sm">
<h3 className="text-xl font-bold tracking-tight">
{t('organizations.getStartedCard.title')}
</h3>
<p className="text-muted-foreground text-sm leading-relaxed">
{t('organizations.getStartedCard.body')}
</p>
</div>
<Button
size="lg"
className="gap-2"
onClick={() => startSignup({ role: 'verifier' })}
>
<BadgeCheck className="size-5" />
{t('organizations.getStartedCard.cta')}
</Button>
</CardContent>
</Card>
{/* Once the org's statement is live, teach them the actual
verify gesture: the three-dots menu on any campaign card. */}
{isVerifier && <VerifyTutorial />}
</div>
<Card className="border-border/60 shadow-sm">
<CardContent className="py-12 px-8 flex flex-col items-center gap-6 text-center">
<div className="p-4 rounded-full bg-primary/10">
<Building2 className="size-8 text-primary" />
</div>
<div className="space-y-2 max-w-sm">
<h3 className="text-xl font-bold tracking-tight">
{t('organizations.getStartedCard.title')}
</h3>
<p className="text-muted-foreground text-sm leading-relaxed">
{t('organizations.getStartedCard.body')}
</p>
</div>
<Button
size="lg"
className="gap-2"
onClick={() => startSignup({ role: 'verifier' })}
>
<BadgeCheck className="size-5" />
{t('organizations.getStartedCard.cta')}
</Button>
</CardContent>
</Card>
);
}