Files
IdP/apps/docs/lib/one-tap-builder.ts
2026-06-29 16:23:40 +03:00

187 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { OneTapUrls } from '@/lib/one-tap-examples';
export type ButtonSize = 's' | 'm' | 'l' | 'xl';
export type ButtonTheme = 'light' | 'dark';
export type ButtonView = 'main' | 'icon';
export type ButtonIcon = 'id' | 'none';
export interface ButtonBuilderOptions {
clientId: string;
providerName: string;
redirectUri: string;
size: ButtonSize;
theme: ButtonTheme;
view: ButtonView;
radius: number;
icon: ButtonIcon;
// Пустая строка = использовать цвет темы по умолчанию.
bg: string;
bgHover: string;
border: string;
borderHover: string;
text: string;
}
export interface SizePreset {
height: number;
font: number;
padX: number;
gap: number;
badge: number;
radius: number;
}
// Должно совпадать с SIZE_PRESETS в apps/frontend/public/sso-widget.js.
export const SIZE_PRESETS: Record<ButtonSize, SizePreset> = {
s: { height: 32, font: 13, padX: 12, gap: 8, badge: 20, radius: 16 },
m: { height: 40, font: 14, padX: 16, gap: 10, badge: 24, radius: 20 },
l: { height: 44, font: 15, padX: 18, gap: 10, badge: 28, radius: 22 },
xl: { height: 52, font: 16, padX: 22, gap: 12, badge: 32, radius: 26 }
};
export interface ButtonPalette {
bg: string;
bgHover: string;
border: string;
borderHover: string;
text: string;
badgeBg: string;
badgeColor: string;
}
// Должно совпадать с themePalette в apps/frontend/public/sso-widget.js.
export function themePalette(theme: ButtonTheme): ButtonPalette {
if (theme === 'dark') {
return {
bg: '#1f2430',
bgHover: '#2a3040',
border: 'transparent',
borderHover: 'transparent',
text: '#ffffff',
badgeBg: '#ffffff',
badgeColor: '#1f2430'
};
}
return {
bg: '#ffffff',
bgHover: '#f6f8fb',
border: '#e4e8ef',
borderHover: '#d4dae6',
text: '#1f2430',
badgeBg: '#111111',
badgeColor: '#ffffff'
};
}
export const SIZE_OPTIONS: Array<{ value: ButtonSize; label: string }> = [
{ value: 's', label: 'S — 32px' },
{ value: 'm', label: 'M — 40px' },
{ value: 'l', label: 'L — 44px' },
{ value: 'xl', label: 'XL — 52px' }
];
export const THEME_OPTIONS: Array<{ value: ButtonTheme; label: string }> = [
{ value: 'light', label: 'Светлая' },
{ value: 'dark', label: 'Тёмная' }
];
export const VIEW_OPTIONS: Array<{ value: ButtonView; label: string }> = [
{ value: 'main', label: 'Кнопка с текстом' },
{ value: 'icon', label: 'Только иконка' }
];
export const ICON_OPTIONS: Array<{ value: ButtonIcon; label: string }> = [
{ value: 'id', label: 'Значок ID' },
{ value: 'none', label: 'Без значка' }
];
export const DEFAULT_BUILDER_OPTIONS: ButtonBuilderOptions = {
clientId: 'YOUR_CLIENT_ID',
providerName: 'MVK ID',
redirectUri: 'https://app.example.com/auth/callback',
size: 'xl',
theme: 'light',
view: 'main',
radius: 26,
icon: 'id',
bg: '',
bgHover: '',
border: '',
borderHover: '',
text: ''
};
export interface ResolvedButtonStyle {
preset: SizePreset;
palette: ButtonPalette;
bg: string;
bgHover: string;
border: string;
borderHover: string;
text: string;
radius: number;
iconOnly: boolean;
showBadge: boolean;
}
export function resolveButtonStyle(options: ButtonBuilderOptions): ResolvedButtonStyle {
const preset = SIZE_PRESETS[options.size] ?? SIZE_PRESETS.xl;
const palette = themePalette(options.theme === 'dark' ? 'dark' : 'light');
const radius = Number.isFinite(options.radius) ? options.radius : preset.radius;
return {
preset,
palette,
bg: options.bg || palette.bg,
bgHover: options.bgHover || palette.bgHover,
border: options.border || palette.border,
borderHover: options.borderHover || palette.borderHover,
text: options.text || palette.text,
radius,
iconOnly: options.view === 'icon',
showBadge: options.icon !== 'none'
};
}
export function buildButtonSnippet(options: ButtonBuilderOptions, urls: OneTapUrls): string {
const providerName = options.providerName || urls.projectName;
const lines: string[] = [
` src="${urls.widgetUrl}"`,
` data-client-id="${options.clientId || 'YOUR_CLIENT_ID'}"`,
` data-idp-url="${urls.apiBase}"`,
` data-idp-frontend-url="${urls.frontendBase}"`,
` data-provider-name="${providerName}"`,
` data-redirect-uri="${options.redirectUri}"`,
` data-button-container="mvkid-button"`,
` data-button-size="${options.size}"`,
` data-button-theme="${options.theme}"`,
` data-button-view="${options.view}"`,
` data-button-radius="${options.radius}"`,
` data-button-icon="${options.icon}"`
];
if (options.bg) lines.push(` data-button-bg="${options.bg}"`);
if (options.bgHover) lines.push(` data-button-bg-hover="${options.bgHover}"`);
if (options.border) lines.push(` data-button-border="${options.border}"`);
if (options.borderHover) lines.push(` data-button-border-hover="${options.borderHover}"`);
if (options.text) lines.push(` data-button-text="${options.text}"`);
lines.push(' data-on-success="onMvkIdLogin"');
return `<!-- 1) Контейнер, в котором появится кнопка -->
<div id="mvkid-button"></div>
<!-- 2) Подключение виджета ${providerName} -->
<script
${lines.join('\n')}
></script>
<script>
function onMvkIdLogin(payload) {
// payload.token — id_token (FedCM) или токен из popup
// payload.method — 'fedcm' | 'popup'
console.log('${providerName}: вход через', payload.method, payload.token);
// Отправьте токен на ваш backend для проверки и создания сессии
}
</script>`;
}