/ src / components / auth / setup-wizard / shared.tsx
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  }