'use client' import React, { useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' import { useRouter, useSearchParams } from 'next/navigation' import Button from '@/app/components/base/button' import Avatar from '@/app/components/base/avatar' import Loading from '@/app/components/base/loading' import Toast from '@/app/components/base/toast' import { useLanguage } from '@/app/components/header/account-setting/model-provider-page/hooks' import { useAppContext } from '@/context/app-context' import { useAuthorizeOAuthApp, useOAuthAppInfo } from '@/service/use-oauth' import { RiAccountCircleLine, RiGlobalLine, RiInfoCardLine, RiMailLine, RiTranslate2, } from '@remixicon/react' import dayjs from 'dayjs' import { useIsLogin } from '@/service/use-common' import { OAUTH_AUTHORIZE_PENDING_KEY, OAUTH_AUTHORIZE_PENDING_TTL, REDIRECT_URL_KEY, } from './constants' function setItemWithExpiry(key: string, value: string, ttl: number) { const item = { value, expiry: dayjs().add(ttl, 'seconds').unix(), } localStorage.setItem(key, JSON.stringify(item)) } function buildReturnUrl(pathname: string, search: string) { try { const base = `${globalThis.location.origin}${pathname}${search}` return base } catch { return pathname + search } } export default function OAuthAuthorize() { const { t } = useTranslation() const SCOPE_INFO_MAP: Record, label: string }> = { 'read:name': { icon: RiInfoCardLine, label: t('oauth.scopes.name'), }, 'read:email': { icon: RiMailLine, label: t('oauth.scopes.email'), }, 'read:avatar': { icon: RiAccountCircleLine, label: t('oauth.scopes.avatar'), }, 'read:interface_language': { icon: RiTranslate2, label: t('oauth.scopes.languagePreference'), }, 'read:timezone': { icon: RiGlobalLine, label: t('oauth.scopes.timezone'), }, } const router = useRouter() const language = useLanguage() const searchParams = useSearchParams() const client_id = decodeURIComponent(searchParams.get('client_id') || '') const redirect_uri = decodeURIComponent(searchParams.get('redirect_uri') || '') const { userProfile } = useAppContext() const { data: authAppInfo, isLoading: isOAuthLoading, isError } = useOAuthAppInfo(client_id, redirect_uri) const { mutateAsync: authorize, isPending: authorizing } = useAuthorizeOAuthApp() const hasNotifiedRef = useRef(false) const { isLoading: isIsLoginLoading, data: loginData } = useIsLogin() const isLoggedIn = loginData?.logged_in const isLoading = isOAuthLoading || isIsLoginLoading const onLoginSwitchClick = () => { try { const returnUrl = buildReturnUrl('/account/oauth/authorize', `?client_id=${encodeURIComponent(client_id)}&redirect_uri=${encodeURIComponent(redirect_uri)}`) setItemWithExpiry(OAUTH_AUTHORIZE_PENDING_KEY, returnUrl, OAUTH_AUTHORIZE_PENDING_TTL) router.push(`/signin?${REDIRECT_URL_KEY}=${encodeURIComponent(returnUrl)}`) } catch { router.push('/signin') } } const onAuthorize = async () => { if (!client_id || !redirect_uri) return try { const { code } = await authorize({ client_id }) const url = new URL(redirect_uri) url.searchParams.set('code', code) globalThis.location.href = url.toString() } catch (err: any) { Toast.notify({ type: 'error', message: `${t('oauth.error.authorizeFailed')}: ${err.message}`, }) } } useEffect(() => { const invalidParams = !client_id || !redirect_uri if ((invalidParams || isError) && !hasNotifiedRef.current) { hasNotifiedRef.current = true Toast.notify({ type: 'error', message: invalidParams ? t('oauth.error.invalidParams') : t('oauth.error.authAppInfoFetchFailed'), duration: 0, }) } }, [client_id, redirect_uri, isError]) if (isLoading) { return (
) } return (
{authAppInfo?.app_icon && (
app icon
)}
{isLoggedIn &&
{t('oauth.connect')}
}
{authAppInfo?.app_label[language] || authAppInfo?.app_label?.en_US || t('oauth.unknownApp')}
{!isLoggedIn &&
{t('oauth.tips.notLoggedIn')}
}
{isLoggedIn ? `${authAppInfo?.app_label[language] || authAppInfo?.app_label?.en_US || t('oauth.unknownApp')} ${t('oauth.tips.loggedIn')}` : t('oauth.tips.needLogin')}
{isLoggedIn && userProfile && (
{userProfile.name}
{userProfile.email}
)} {isLoggedIn && Boolean(authAppInfo?.scope) && (
{authAppInfo!.scope.split(/\s+/).filter(Boolean).map((scope: string) => { const Icon = SCOPE_INFO_MAP[scope] return (
{Icon ? : } {Icon.label}
) })}
)}
{!isLoggedIn ? ( ) : ( <> )}
{t('oauth.tips.common')}
) }