'use client'; import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import Link from 'next/link'; import { useRouter, useSearchParams } from 'next/navigation'; import { CheckCircle2, Loader2, ShieldCheck } from 'lucide-react'; import { BrandLogo } from '@/components/id/brand-logo'; import { useAuth } from '@/components/id/auth-provider'; import { Button } from '@/components/ui/button'; import { approveOAuthAuthorization, checkOAuthConsent, fetchAuthSession, type OAuthConsentCheckResponse } from '@/lib/api'; function buildOAuthQuery(searchParams: URLSearchParams) { const params = new URLSearchParams(); searchParams.forEach((value, key) => { if (key !== 'userId') params.set(key, value); }); return params; } function OAuthAuthorizeContent() { const searchParams = useSearchParams(); const router = useRouter(); const { user, token, isPinLocked, isLoading } = useAuth(); const [submitting, setSubmitting] = useState(false); const [checkingConsent, setCheckingConsent] = useState(false); const [error, setError] = useState(null); const [sessionChecked, setSessionChecked] = useState(false); const [consentInfo, setConsentInfo] = useState(null); const autoApproveStartedRef = useRef(false); const oauthQuery = useMemo(() => buildOAuthQuery(searchParams), [searchParams]); const clientId = searchParams.get('client_id') ?? searchParams.get('clientId'); const redirectUri = searchParams.get('redirect_uri') ?? searchParams.get('redirectUri'); const scope = searchParams.get('scope') ?? 'openid profile'; const state = searchParams.get('state'); const returnUrl = useMemo(() => `/auth/oauth/authorize?${oauthQuery.toString()}`, [oauthQuery]); const clientLabel = consentInfo?.client?.name ?? clientId ?? 'Приложение'; useEffect(() => { if (!searchParams.has('userId')) return; router.replace(returnUrl); }, [returnUrl, router, searchParams]); useEffect(() => { if (isLoading) return; if (!clientId || !redirectUri) return; if (isPinLocked) return; if (user && token) return; router.replace(`/auth/login?redirect=${encodeURIComponent(returnUrl)}`); }, [clientId, isLoading, isPinLocked, redirectUri, returnUrl, router, token, user]); useEffect(() => { if (isLoading || isPinLocked || !token || !user) { setSessionChecked(false); return; } let cancelled = false; void fetchAuthSession(token) .then(() => { if (!cancelled) setSessionChecked(true); }) .catch(() => { if (!cancelled) { setSessionChecked(false); router.replace(`/auth/login?redirect=${encodeURIComponent(returnUrl)}`); } }); return () => { cancelled = true; }; }, [isLoading, isPinLocked, returnUrl, router, token, user]); const approve = useCallback(async () => { if (!token || !user || isPinLocked) return; setSubmitting(true); setError(null); try { const data = await approveOAuthAuthorization(oauthQuery, token); if (!data.redirectUrl) { throw new Error('Сервер не вернул redirect URL'); } window.location.href = data.redirectUrl; } catch (err) { setError(err instanceof Error ? err.message : 'Ошибка OAuth авторизации'); autoApproveStartedRef.current = false; } finally { setSubmitting(false); } }, [isPinLocked, oauthQuery, token, user]); useEffect(() => { if (!token || !clientId || !sessionChecked || isPinLocked) return; let cancelled = false; setCheckingConsent(true); void checkOAuthConsent(oauthQuery, token) .then((result) => { if (cancelled) return; setConsentInfo(result); if (result.granted && !autoApproveStartedRef.current) { autoApproveStartedRef.current = true; void approve(); } }) .catch((err) => { if (!cancelled) { setError(err instanceof Error ? err.message : 'Не удалось проверить согласие'); } }) .finally(() => { if (!cancelled) setCheckingConsent(false); }); return () => { cancelled = true; }; }, [approve, clientId, isPinLocked, oauthQuery, sessionChecked, token]); const cancel = useCallback(() => { if (!redirectUri) { router.push('/'); return; } try { const url = new URL(redirectUri); url.searchParams.set('error', 'access_denied'); url.searchParams.set('error_description', 'Пользователь отклонил запрос'); if (state) url.searchParams.set('state', state); window.location.href = url.toString(); } catch { router.push('/'); } }, [redirectUri, router, state]); const authReady = Boolean(user && token && !isPinLocked && sessionChecked); const scopeItems = consentInfo?.requestedScopes ?? []; if (isLoading || (clientId && redirectUri && !authReady && !isPinLocked)) { return (
); } if (isPinLocked) { return (

Подтвердите PIN-код, чтобы продолжить авторизацию приложения.

); } if (!clientId || !redirectUri) { return (

Некорректный OAuth запрос

Отсутствуют обязательные параметры client_id и redirect_uri.

На главную
); } if (checkingConsent || (consentInfo?.granted && submitting)) { return (

Продолжаем вход в {clientLabel}…

); } return (

Разрешить доступ?

Приложение {clientLabel} запрашивает доступ к данным вашего аккаунта Lendry ID.

Пользователь: {user?.displayName}

Запрашиваемые данные:

    {scopeItems.length > 0 ? ( scopeItems.map((item) => (
  • {item.name}
    {item.description ?
    {item.description}
    : null}
  • )) ) : (
  • {scope}
  • )}

После подтверждения доступ сохранится, и повторно спрашивать не будем. Отозвать его можно в разделе «Данные → Доступы к данным».

{error ?

{error}

: null}
); } export default function OAuthAuthorizePage() { return ( } > ); }