48 lines
1.8 KiB
TypeScript
48 lines
1.8 KiB
TypeScript
|
|
import { useQuery } from '@tanstack/react-query';
|
||
|
|
import { useTranslation } from 'react-i18next';
|
||
|
|
|
||
|
|
import { fetchOAuthProviders } from '@/api/oauth';
|
||
|
|
import { Card } from '@/components/ui/primitives';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Вход через внешние провайдеры (Фаза 7). Раздел только показывает, какие
|
||
|
|
* провайдеры включены на инстансе: привязка происходит при входе по
|
||
|
|
* подтверждённому email, а отвязка — действие оператора/администратора.
|
||
|
|
*/
|
||
|
|
export function LinkedAccountsSection() {
|
||
|
|
const { t } = useTranslation();
|
||
|
|
const providers = useQuery({
|
||
|
|
queryKey: ['auth', 'oauth', 'providers'],
|
||
|
|
queryFn: ({ signal }) => fetchOAuthProviders(signal),
|
||
|
|
staleTime: 5 * 60 * 1000,
|
||
|
|
});
|
||
|
|
const enabled = providers.data?.providers ?? [];
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Card>
|
||
|
|
<h2 className="text-lg font-semibold">{t('settings.security.oauthTitle')}</h2>
|
||
|
|
<p className="mt-1 text-sm text-fg-muted">{t('settings.security.oauthDescription')}</p>
|
||
|
|
|
||
|
|
{enabled.length === 0 ? (
|
||
|
|
<p className="mt-3 text-sm text-fg-muted" data-testid="oauth-none">
|
||
|
|
{t('settings.security.oauthDisabled')}
|
||
|
|
</p>
|
||
|
|
) : (
|
||
|
|
<ul className="mt-3 flex flex-col gap-2" data-testid="oauth-providers">
|
||
|
|
{enabled.map((provider) => (
|
||
|
|
<li
|
||
|
|
key={provider.id}
|
||
|
|
className="flex items-center justify-between rounded-[var(--radius-md)] border border-border/60 bg-surface-2/60 p-3 text-sm"
|
||
|
|
>
|
||
|
|
<span>{provider.name}</span>
|
||
|
|
<span className="text-xs text-fg-muted">
|
||
|
|
{t('settings.security.oauthLinkedByEmail')}
|
||
|
|
</span>
|
||
|
|
</li>
|
||
|
|
))}
|
||
|
|
</ul>
|
||
|
|
)}
|
||
|
|
</Card>
|
||
|
|
);
|
||
|
|
}
|