import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react' import type { ReactNode } from 'react' import Swal from 'sweetalert2' import type { SiteData } from '../api' import { useSiteData } from '../api' import { adminApi, getToken, HttpError, setToken } from './adminClient' export type SaveStatus = 'idle' | 'saving' | 'error' interface AdminContextValue { isAuthenticated: boolean isEditMode: boolean toggleEditMode: () => void login: (username: string, password: string) => Promise logout: () => void saveStatus: SaveStatus /** اجرای یک عملیات ذخیره — وضعیت نوار ادمین را به‌روز می‌کند و خطاها را با دکمه تلاش دوباره نشان می‌دهد */ runSave: (action: () => Promise) => Promise } const AdminContext = createContext(null) export function useAdmin(): AdminContextValue { const ctx = useContext(AdminContext) if (!ctx) throw new Error('useAdmin باید داخل AdminProvider استفاده شود') return ctx } function showSaveError(error: unknown): void { const message = error instanceof HttpError ? error.message : 'ارتباط با سرور برقرار نشد — اتصال اینترنت را بررسی کنید' Swal.fire({ icon: 'error', title: 'ذخیره نشد', text: message, background: '#1e1a16', color: '#F5F3EF', iconColor: '#d5874c', confirmButtonText: 'باشه', timer: 4000, timerProgressBar: true, buttonsStyling: false, customClass: { popup: 'font-pinar border border-ivory/10 rounded-none', title: 'font-pinar font-bold text-lg text-ivory', htmlContainer: 'font-pinar font-light text-sm text-ivory/70', confirmButton: 'bg-copper text-char font-bold px-8 py-2.5', }, }) } export function AdminProvider({ children }: { children: ReactNode }) { const [isAuthenticated, setIsAuthenticated] = useState(() => !!getToken()) const [isEditMode, setIsEditMode] = useState( () => sessionStorage.getItem('aramesh_edit_mode') === '1', ) const [saveStatus, setSaveStatus] = useState('idle') const { reload } = useSiteData() const toggleEditMode = useCallback(() => { setIsEditMode((v) => { sessionStorage.setItem('aramesh_edit_mode', v ? '0' : '1') return !v }) }, []) const login = useCallback(async (username: string, password: string) => { await adminApi.login(username, password) setIsAuthenticated(true) // داده سایت دوباره بارگذاری می‌شود تا پیش‌نویس مقاله‌ها هم ادغام شوند reload() }, [reload]) const logout = useCallback(() => { setToken(null) setIsAuthenticated(false) setIsEditMode(false) sessionStorage.removeItem('aramesh_edit_mode') // داده سایت دوباره بارگذاری می‌شود تا پیش‌نویس‌ها از درخت داده حذف شوند reload() }, [reload]) const runSave = useCallback( async (action: () => Promise): Promise => { setSaveStatus('saving') try { await action() setSaveStatus('idle') return true } catch (error) { setSaveStatus('error') showSaveError(error) if (error instanceof HttpError && error.status === 401) { logout() reload() } return false } }, [logout, reload], ) // در حالت ویرایش، کلیک روی لینک‌ها مسیریابی نمی‌کند تا ویرایش متن ممکن باشد useEffect(() => { if (!isEditMode) return const onClick = (e: MouseEvent) => { const anchor = (e.target as HTMLElement | null)?.closest('a') // لینک‌های خود لایه مدیریت (مثل «نوشتن مقاله جدید») کار می‌کنند if (anchor && !anchor.dataset.adminLink) e.preventDefault() } document.addEventListener('click', onClick, true) return () => document.removeEventListener('click', onClick, true) }, [isEditMode]) // در حین ذخیره‌سازی، از بسته‌شدن تصادفی صفحه جلوگیری می‌شود useEffect(() => { if (saveStatus !== 'saving') return const guard = (e: BeforeUnloadEvent) => { e.preventDefault() } window.addEventListener('beforeunload', guard) return () => window.removeEventListener('beforeunload', guard) }, [saveStatus]) const value = useMemo( () => ({ isAuthenticated, isEditMode, toggleEditMode, login, logout, saveStatus, runSave, }), [isAuthenticated, isEditMode, toggleEditMode, login, logout, saveStatus, runSave], ) return {children} } /** * پرکاربردترین کمک‌ساز ویرایش: یک فیلد واحد را PATCH می‌کند و پس از موفقیت * همان تغییر را به‌صورت خوش‌بینانه در درخت داده سایت اعمال می‌کند. */ export function useFieldSaver() { const { runSave } = useAdmin() const { updateSite } = useSiteData() return useCallback( ( resource: string, id: number, changes: Record, apply: (data: SiteData) => void, ): Promise => runSave(async () => { await adminApi.patch(resource, id, changes) updateSite(apply) }), [runSave, updateSite], ) }