'use client'; import { useEffect, useMemo, useState, type CSSProperties, type ReactNode } from 'react'; import { fetchPublicSettingsClient } from '@/lib/api'; import { buildOneTapUrls, type OneTapUrls } from '@/lib/one-tap-examples'; import { resolveFrontendBase, resolveOAuthApiBase } from '@/lib/oauth-url'; import { CodeBlock } from '@/components/code-block'; import { cn } from '@/lib/utils'; import { buildButtonSnippet, DEFAULT_BUILDER_OPTIONS, ICON_OPTIONS, resolveButtonStyle, SIZE_OPTIONS, THEME_OPTIONS, VIEW_OPTIONS, type ButtonBuilderOptions } from '@/lib/one-tap-builder'; type ColorKey = 'bg' | 'bgHover' | 'border' | 'borderHover' | 'text'; const COLOR_FIELDS: Array<{ key: ColorKey; label: string }> = [ { key: 'bg', label: 'Цвет фона' }, { key: 'bgHover', label: 'Фон при наведении' }, { key: 'border', label: 'Цвет обводки' }, { key: 'borderHover', label: 'Обводка при наведении' }, { key: 'text', label: 'Цвет текста' } ]; function Field({ label, children }: { label: string; children: ReactNode }) { return ( ); } const inputClass = 'h-9 w-full rounded-lg border border-zinc-300 bg-white px-3 text-sm text-zinc-900 outline-none transition focus:border-zinc-400 focus:ring-2 focus:ring-zinc-200 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100 dark:focus:ring-zinc-700'; export function OneTapButtonBuilder() { const [apiBase, setApiBase] = useState('http://localhost:3000'); const [frontendBase, setFrontendBase] = useState('http://localhost:3002'); const [projectName, setProjectName] = useState('MVK ID'); const [options, setOptions] = useState(DEFAULT_BUILDER_OPTIONS); const [hovered, setHovered] = useState(false); const [status, setStatus] = useState(null); useEffect(() => { void fetchPublicSettingsClient() .then((settings) => { setApiBase(resolveOAuthApiBase(settings)); setFrontendBase(resolveFrontendBase(settings)); if (settings.PROJECT_NAME?.trim()) { const name = settings.PROJECT_NAME.trim(); setProjectName(name); setOptions((prev) => prev.providerName === DEFAULT_BUILDER_OPTIONS.providerName ? { ...prev, providerName: name } : prev ); } }) .catch(() => undefined); }, []); const urls: OneTapUrls = useMemo( () => buildOneTapUrls(apiBase, frontendBase, projectName), [apiBase, frontendBase, projectName] ); const snippet = useMemo(() => buildButtonSnippet(options, urls), [options, urls]); const style = useMemo(() => resolveButtonStyle(options), [options]); function update(key: K, value: ButtonBuilderOptions[K]) { setOptions((prev) => ({ ...prev, [key]: value })); } function toggleColor(key: ColorKey, enabled: boolean) { setOptions((prev) => ({ ...prev, [key]: enabled ? (prev[key] || style.palette[key] || '#ffffff') : '' })); } const buttonStyle: CSSProperties = { height: style.preset.height, fontSize: style.preset.font, gap: style.preset.gap, padding: style.iconOnly ? 0 : `0 ${style.preset.padX}px`, width: style.iconOnly ? style.preset.height : 'auto', borderRadius: style.radius, background: hovered ? style.bgHover : style.bg, color: style.text, border: `1px solid ${hovered ? style.borderHover : style.border}`, boxShadow: '0 8px 24px rgba(31,36,48,.12)', transition: 'all .18s ease' }; const badgeStyle: CSSProperties = { minWidth: style.preset.badge, height: style.preset.badge, padding: `0 ${Math.round(style.preset.badge / 4)}px`, fontSize: Math.round(style.preset.font * 0.85), background: style.palette.badgeBg, color: style.palette.badgeColor }; function tryLogin() { if (typeof window === 'undefined') return; const params = new URLSearchParams({ client_id: options.clientId || 'YOUR_CLIENT_ID', redirect_uri: options.redirectUri, response_type: 'code', scope: 'openid profile email', display: 'popup', popup_origin: window.location.origin, state: 'preview_' + Math.random().toString(36).slice(2) }); const url = `${frontendBase}/auth/oauth/authorize?${params.toString()}`; const popup = window.open(url, 'mvkid_preview', 'popup,width=480,height=640'); if (!popup) { setStatus('Браузер заблокировал popup — разрешите всплывающие окна для теста.'); return; } setStatus('Открыт popup авторизации. После входа токен придёт через postMessage.'); function onMessage(event: MessageEvent) { if (event.data?.type !== 'lendry-sso-onetap') return; window.removeEventListener('message', onMessage); setStatus(`Получен ответ: метод ${event.data.method ?? 'popup'}.`); } window.addEventListener('message', onMessage); } return (
{/* Левая колонка: превью + код */}
{status ? (

{status}

) : null}
{/* Правая колонка: параметры */}
update('clientId', event.target.value)} placeholder="YOUR_CLIENT_ID" /> update('redirectUri', event.target.value)} placeholder="https://app.example.com/auth/callback" /> update('providerName', event.target.value)} placeholder={projectName} />
update('radius', Number(event.target.value))} className="w-full accent-zinc-900 dark:accent-zinc-100" />

CSS-цвета (необязательно — иначе берётся цвет темы)

{COLOR_FIELDS.map((field) => { const enabled = Boolean(options[field.key]); const fallback = style.palette[field.key] || '#ffffff'; const value = options[field.key] || fallback; return (
toggleColor(field.key, event.target.checked)} className="h-4 w-4 accent-zinc-900 dark:accent-zinc-100" /> update(field.key, event.target.value)} className={cn('h-8 w-10 cursor-pointer rounded border border-zinc-300 bg-transparent dark:border-zinc-700', !enabled && 'opacity-40')} /> {field.label} {enabled ? ( {options[field.key]} ) : null}
); })}
); }