187 lines
5.7 KiB
TypeScript
187 lines
5.7 KiB
TypeScript
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>`;
|
||
}
|