step-profile.tsx
1 'use client' 2 3 import { useCallback, useId, useState } from 'react' 4 import { AgentAvatar } from '@/components/agents/agent-avatar' 5 import { StepShell } from './shared' 6 7 export interface StepProfileProps { 8 onContinue: (userName: string, avatarSeed: string) => void 9 onSkip: () => void 10 } 11 12 export function StepProfile({ onContinue, onSkip }: StepProfileProps) { 13 const [name, setName] = useState('') 14 const defaultAvatarSeed = useId().replace(/:/g, '') 15 const [avatarSeed, setAvatarSeed] = useState(defaultAvatarSeed) 16 const [seedOpen, setSeedOpen] = useState(false) 17 18 const hasName = name.trim().length > 0 19 20 const randomizeSeed = useCallback(() => { 21 setAvatarSeed(Math.random().toString(36).slice(2, 10)) 22 }, []) 23 24 const handleSubmit = (e: React.FormEvent) => { 25 e.preventDefault() 26 if (!hasName) return 27 onContinue(name.trim().toLowerCase(), avatarSeed.trim() || defaultAvatarSeed) 28 } 29 30 return ( 31 <StepShell> 32 {/* Avatar hero */} 33 <div className="flex justify-center mb-8"> 34 <div className="relative group"> 35 <div 36 className="absolute inset-[-12px] rounded-full transition-all duration-700" 37 style={{ 38 background: hasName 39 ? 'conic-gradient(from 0deg, rgba(99,102,241,0.15), rgba(236,72,153,0.1), rgba(52,211,153,0.1), rgba(99,102,241,0.15))' 40 : 'conic-gradient(from 0deg, rgba(99,102,241,0.06), transparent, rgba(99,102,241,0.06))', 41 animation: 'sparkle-spin 8s linear infinite', 42 filter: 'blur(8px)', 43 }} 44 /> 45 <div 46 className="absolute inset-[-4px] rounded-full transition-all duration-500" 47 style={{ 48 border: '1px solid', 49 borderColor: hasName ? 'rgba(99,102,241,0.2)' : 'rgba(255,255,255,0.06)', 50 }} 51 /> 52 <div className="relative"> 53 <AgentAvatar seed={avatarSeed || null} name={name || '?'} size={96} /> 54 </div> 55 56 <button 57 type="button" 58 onClick={randomizeSeed} 59 className="absolute -bottom-1 -right-1 w-7 h-7 rounded-full bg-surface border border-white/[0.08] 60 flex items-center justify-center cursor-pointer 61 hover:bg-white/[0.08] hover:border-accent-bright/30 active:scale-90 62 transition-all duration-200 z-10" 63 title="Randomize avatar" 64 > 65 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-text-3"> 66 <rect x="2" y="2" width="20" height="20" rx="3" /> 67 <circle cx="8" cy="8" r="1.5" fill="currentColor" stroke="none" /> 68 <circle cx="16" cy="8" r="1.5" fill="currentColor" stroke="none" /> 69 <circle cx="8" cy="16" r="1.5" fill="currentColor" stroke="none" /> 70 <circle cx="16" cy="16" r="1.5" fill="currentColor" stroke="none" /> 71 </svg> 72 </button> 73 </div> 74 </div> 75 76 <h1 className="font-display text-[36px] font-800 leading-[1.05] tracking-[-0.04em] mb-2"> 77 Welcome 78 </h1> 79 <p className="text-[15px] text-text-2 mb-8"> 80 What should we call you? 81 </p> 82 83 <form onSubmit={handleSubmit} className="flex flex-col items-center gap-5"> 84 <input 85 type="text" 86 value={name} 87 onChange={(e) => setName(e.target.value)} 88 placeholder="Your name" 89 autoFocus 90 className="w-full max-w-[300px] px-6 py-4 rounded-[16px] border border-white/[0.08] bg-surface 91 text-text text-[18px] text-center font-display font-600 outline-none 92 transition-all duration-200 placeholder:text-text-3/70 93 focus:border-accent-bright/30 focus:shadow-[0_0_30px_rgba(99,102,241,0.1)]" 94 /> 95 96 {!seedOpen ? ( 97 <button 98 type="button" 99 onClick={() => setSeedOpen(true)} 100 className="bg-transparent border-none text-[12px] text-text-3 cursor-pointer hover:text-text-2 transition-colors" 101 > 102 Customize avatar seed 103 </button> 104 ) : ( 105 <div className="flex items-center gap-2"> 106 <input 107 type="text" 108 value={avatarSeed} 109 onChange={(e) => setAvatarSeed(e.target.value)} 110 placeholder="Avatar seed" 111 className="w-[160px] px-3 py-2 rounded-[10px] border border-white/[0.08] bg-surface 112 text-text text-[13px] text-center outline-none transition-all 113 focus:border-accent-bright/30" 114 /> 115 <button 116 type="button" 117 onClick={randomizeSeed} 118 className="px-3 py-2 rounded-[10px] border border-white/[0.08] bg-transparent text-text-3 text-[12px] font-600 119 cursor-pointer transition-all hover:bg-white/[0.04] shrink-0" 120 > 121 Randomize 122 </button> 123 </div> 124 )} 125 126 <div className="flex items-center gap-3"> 127 <button 128 type="button" 129 onClick={onSkip} 130 className="px-6 py-3.5 rounded-[14px] border border-white/[0.08] bg-transparent text-text-2 text-[14px] 131 font-display font-500 cursor-pointer hover:bg-white/[0.03] transition-all duration-200" 132 > 133 Skip for now 134 </button> 135 <button 136 type="submit" 137 disabled={!hasName} 138 className="px-10 py-3.5 rounded-[14px] border-none bg-accent-bright text-white text-[15px] font-display font-600 139 cursor-pointer hover:brightness-110 active:scale-[0.97] transition-all duration-200 140 shadow-[0_6px_28px_rgba(99,102,241,0.3)] disabled:opacity-30" 141 > 142 Continue 143 </button> 144 </div> 145 </form> 146 </StepShell> 147 ) 148 }