// Клиентский помощник авторизации (браузер). // Токен храним в localStorage; редирект Яндекса приносит его в hash: /club#token=...&refresh=... 'use client'; import { API_BASE } from './api'; const TOKEN_KEY = 'nika_token'; const REFRESH_KEY = 'nika_refresh'; export interface AuthUser { id: string; email: string; name: string; role: 'owner' | 'admin' | 'buyer' | 'subscriber'; telegram_id?: number | null; club_tier?: string | null; avatar_url?: string | null; created_at?: string; } export function getToken(): string | null { if (typeof window === 'undefined') return null; return window.localStorage.getItem(TOKEN_KEY); } export function setTokens(token: string, refresh?: string | null) { if (typeof window === 'undefined') return; window.localStorage.setItem(TOKEN_KEY, token); if (refresh) window.localStorage.setItem(REFRESH_KEY, refresh); } export function clearTokens() { if (typeof window === 'undefined') return; window.localStorage.removeItem(TOKEN_KEY); window.localStorage.removeItem(REFRESH_KEY); } // Разбирает токен из hash после редиректа OAuth. Возвращает true, если токен найден и сохранён. export function consumeHashToken(): boolean { if (typeof window === 'undefined') return false; const hash = window.location.hash.replace(/^#/, ''); if (!hash) return false; const params = new URLSearchParams(hash); const token = params.get('token'); if (!token) return false; setTokens(token, params.get('refresh')); // Чистим hash, чтобы токен не «висел» в адресной строке. history.replaceState(null, '', window.location.pathname + window.location.search); return true; } // Авторизованный fetch к нашему API. Кидает объект { status, message } при ошибке. export async function authFetch( path: string, init: RequestInit = {}, ): Promise { const token = getToken(); const res = await fetch(`${API_BASE}${path.startsWith('/') ? path : `/${path}`}`, { ...init, headers: { Accept: 'application/json', ...(init.body ? { 'Content-Type': 'application/json' } : {}), ...(token ? { Authorization: `Bearer ${token}` } : {}), ...(init.headers || {}), }, }); let data: unknown = null; try { data = await res.json(); } catch { /* пустое тело */ } if (!res.ok) { const message = (data && typeof data === 'object' && 'error' in data && (data as { error: string }).error) || `${res.status} ${res.statusText}`; throw { status: res.status, message } as { status: number; message: string }; } return data as T; } // Текущий пользователь (или null, если не авторизован / токен протух). export async function fetchMe(): Promise { if (!getToken()) return null; try { const { user } = await authFetch<{ user: AuthUser }>('/auth/me'); return user; } catch { return null; } } // Ссылка на старт входа через Яндекс (полный путь до backend). export function yandexLoginUrl(): string { return `${API_BASE}/auth/yandex`; } export function isAdmin(user: AuthUser | null): boolean { return !!user && (user.role === 'admin' || user.role === 'owner'); }