shared.tsx
1 import type { ConfiguredProvider } from './types' 2 import { OPENCLAW_USE_CASE_LABELS, OPENCLAW_EXPOSURE_LABELS } from './types' 3 import { formatEndpointHost } from './utils' 4 5 export function SparkleIcon() { 6 return ( 7 <div className="flex justify-center mb-6"> 8 <div className="relative w-12 h-12"> 9 <svg 10 width="48" 11 height="48" 12 viewBox="0 0 48 48" 13 fill="none" 14 className="text-accent-bright" 15 style={{ animation: 'sparkle-spin 8s linear infinite' }} 16 > 17 <path 18 d="M24 4L27.5 18.5L42 24L27.5 29.5L24 44L20.5 29.5L6 24L20.5 18.5L24 4Z" 19 fill="currentColor" 20 opacity="0.9" 21 /> 22 </svg> 23 <div className="absolute inset-0 blur-xl bg-accent-bright/20" /> 24 </div> 25 </div> 26 ) 27 } 28 29 export function StepShell({ wide, children }: { wide?: boolean; children: React.ReactNode }) { 30 return ( 31 <div 32 className={`relative ${wide ? 'max-w-[920px]' : 'max-w-[760px]'} w-full text-center`} 33 style={{ animation: 'spring-in 0.5s var(--ease-spring, cubic-bezier(0.16, 1, 0.3, 1)) both' }} 34 > 35 {children} 36 </div> 37 ) 38 } 39 40 export function SkipLink({ onClick, label }: { onClick: () => void; label?: string }) { 41 return ( 42 <button 43 onClick={onClick} 44 className="mt-8 text-[13px] text-text-3 hover:text-text-2 transition-colors cursor-pointer bg-transparent border-none" 45 > 46 {label || 'Skip setup for now'} 47 </button> 48 ) 49 } 50 51 export function ConfiguredProviderChips({ 52 providers, 53 onRemove, 54 }: { 55 providers: ConfiguredProvider[] 56 onRemove?: (id: string) => void 57 }) { 58 if (providers.length === 0) return null 59 return ( 60 <div className="flex flex-wrap gap-2 justify-center mb-6"> 61 {providers.map((cp) => ( 62 <span 63 key={cp.id} 64 className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-emerald-500/10 border border-emerald-500/25 text-emerald-300 text-[12px] font-500" 65 > 66 <span className="w-1.5 h-1.5 rounded-full bg-emerald-400" /> 67 {cp.name} 68 <span className="text-emerald-300/70"> 69 {formatEndpointHost(cp.endpoint) 70 ? `· ${formatEndpointHost(cp.endpoint)}` 71 : ''} 72 {cp.setupProvider === 'openclaw' && cp.deployment?.useCase 73 ? ` · ${OPENCLAW_USE_CASE_LABELS[cp.deployment.useCase]}` 74 : ''} 75 {cp.setupProvider === 'openclaw' && cp.deployment?.exposure 76 ? ` · ${OPENCLAW_EXPOSURE_LABELS[cp.deployment.exposure]}` 77 : ''} 78 {cp.defaultModel ? ` · ${cp.defaultModel}` : ''} 79 </span> 80 {onRemove && ( 81 <button 82 type="button" 83 onClick={() => onRemove(cp.id)} 84 className="ml-0.5 text-emerald-300/50 hover:text-red-300 transition-colors bg-transparent border-none cursor-pointer p-0 leading-none" 85 title={`Remove ${cp.name}`} 86 > 87 <svg width="12" height="12" viewBox="0 0 12 12" fill="none"> 88 <path d="M3 3L9 9M9 3L3 9" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" /> 89 </svg> 90 </button> 91 )} 92 </span> 93 ))} 94 </div> 95 ) 96 }