Files
IdP/apps/frontend/components/id/oauth-client-one-tap-section.tsx
2026-07-01 15:23:38 +03:00

146 lines
5.5 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.
'use client';
import { useMemo, useState } from 'react';
import { Copy, ExternalLink } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { OAuthClient } from '@/lib/api';
import { buildOneTapExamples } from '@/lib/one-tap-examples';
import { OAuthEndpoints } from '@/lib/oauth-url';
function SnippetBlock({ code, onCopy }: { code: string; onCopy: (value: string, label: string) => void }) {
return (
<div className="relative rounded-2xl bg-[#111827] p-4 text-xs text-[#e5e7eb]">
<Button
type="button"
variant="ghost"
size="icon"
className="absolute right-2 top-2 h-8 w-8 text-white hover:bg-white/10 hover:text-white"
aria-label="Скопировать код"
onClick={() => onCopy(code, 'Код интеграции')}
>
<Copy className="h-4 w-4" />
</Button>
<pre className="max-h-72 overflow-auto whitespace-pre-wrap break-all pr-10">{code}</pre>
</div>
);
}
export function OAuthClientOneTapSection({
client,
endpoints,
frontendBase,
projectName,
onCopy
}: {
client: OAuthClient;
endpoints: OAuthEndpoints;
frontendBase: string;
projectName: string;
onCopy: (value: string, label: string) => void;
}) {
const primaryRedirect = client.redirectUris[0] ?? `${frontendBase}/auth/callback`;
const examples = useMemo(
() =>
buildOneTapExamples(
{
apiBase: endpoints.issuer,
frontendBase,
widgetUrl: endpoints.widgetUrl,
fedcmConfigUrl: endpoints.fedcmConfigUrl,
webIdentityUrl: endpoints.webIdentityUrl,
fedcmDiscoverUrl: endpoints.fedcmDiscoverUrl,
projectName
},
client.clientId,
primaryRedirect
),
[client.clientId, endpoints, frontendBase, primaryRedirect, projectName]
);
const [activeTab, setActiveTab] = useState(examples[0]?.id ?? 'widget-script');
return (
<section className="space-y-4 rounded-2xl border border-[#eceef4] p-4">
<div>
<h3 className="font-medium">One Tap Login</h3>
<p className="mt-1 text-xs leading-relaxed text-[#667085]">
FedCM (Chrome/Edge) и виджет <code className="rounded bg-[#f4f5f8] px-1 py-0.5">sso-widget.js</code> с popup fallback.
Убедитесь, что в настройках указаны <strong>PUBLIC_API_URL</strong> и <strong>PUBLIC_FRONTEND_URL</strong>.
</p>
</div>
<div className="grid gap-2 sm:grid-cols-2">
<CopyRowCompact label="FedCM config" value={endpoints.fedcmConfigUrl} onCopy={onCopy} />
<CopyRowCompact label="Web identity" value={endpoints.webIdentityUrl} onCopy={onCopy} />
<CopyRowCompact label="Виджет" value={endpoints.widgetUrl} onCopy={onCopy} />
<CopyRowCompact label="Discover" value={endpoints.fedcmDiscoverUrl} onCopy={onCopy} />
</div>
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className="mb-2 flex h-auto max-w-full flex-wrap gap-1">
{examples.map((example) => (
<TabsTrigger key={example.id} value={example.id} className="text-xs">
{example.label}
</TabsTrigger>
))}
</TabsList>
{examples.map((example) => (
<TabsContent key={example.id} value={example.id}>
<SnippetBlock code={example.code} onCopy={onCopy} />
</TabsContent>
))}
</Tabs>
<div className="rounded-xl bg-[#eef4ff] px-4 py-3 text-xs text-[#1d4ed8]">
<p className="font-medium">Проверка FedCM</p>
<ol className="mt-2 list-decimal space-y-1 pl-4">
<li>Войдите на IdP ({frontendBase}) в этом браузере.</li>
<li>Откройте сайт клиента с виджетом Chrome покажет нативный диалог или плашку One Tap.</li>
<li>
<code>login_url</code> в config.json должен быть на том же домене, что и config (
<code>{endpoints.issuer}/auth/login</code>), не на SSO-домене.
</li>
<li>
<code>configURL</code> для FedCM: <code>{endpoints.fedcmConfigUrl}</code> (не{' '}
<code>{frontendBase}/idp-api/fedcm/config.json</code>).
</li>
<li>
При ошибке NetworkError проверьте, что <code>PUBLIC_API_URL</code> = <code>{endpoints.issuer}</code>.
</li>
</ol>
<a
href={`${frontendBase.replace(/\/$/, '')}/sso-widget.js`}
target="_blank"
rel="noreferrer"
className="mt-2 inline-flex items-center gap-1 font-medium hover:underline"
>
Открыть sso-widget.js
<ExternalLink className="h-3.5 w-3.5" />
</a>
</div>
</section>
);
}
function CopyRowCompact({
label,
value,
onCopy
}: {
label: string;
value: string;
onCopy: (value: string, label: string) => void;
}) {
return (
<div className="rounded-xl bg-[#f4f5f8] p-3">
<div className="mb-1 flex items-center justify-between gap-2">
<span className="text-xs font-medium">{label}</span>
<Button variant="ghost" size="icon" className="h-7 w-7" aria-label={`Скопировать ${label}`} onClick={() => onCopy(value, label)}>
<Copy className="h-3.5 w-3.5" />
</Button>
</div>
<code className="block break-all text-[11px] text-[#667085]">{value}</code>
</div>
);
}