Files
clinicpro/assets/admin/hooks/useUrlState.ts
T
hamed 6876135a53 feat: add BlogBodySanitizer for HTML sanitization on article save
- Implemented BlogBodySanitizer to clean HTML content before saving articles, ensuring security against XSS attacks.
- Added tests for BlogBodySanitizer to verify that unsafe tags and attributes are stripped from the content.
- Introduced ApiLeastPrivilegeTest to ensure that unauthorized users cannot access sensitive API routes, maintaining strict access control.
2026-08-07 21:13:38 +03:30

53 lines
2.5 KiB
TypeScript

import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useSearchParams } from 'react-router';
/**
* وضعیت لیست (جستجو، فیلترها، صفحه، تب) در query string زندگی می‌کند، نه در state
* کامپوننت. دلیلش «بازگشت»‌ است: `navigate(-1)` همان URL قبلی را برمی‌گرداند، پس هر
* چیزی که در URL باشد سرِ جایش برمی‌گردد و هر چیزی که در state باشد با remount می‌پرد.
* سود جانبی: رفرش و اشتراک لینک هم همان نما را می‌دهد.
*
* مقدارِ برابرِ پیش‌فرض از URL حذف می‌شود تا آدرس تمیز بماند.
*/
export function useUrlState<T extends Record<string, string>>(defaults: T) {
const [params, setParams] = useSearchParams();
const state = useMemo(() => {
const out = { ...defaults };
(Object.keys(defaults) as (keyof T)[]).forEach((k) => {
const raw = params.get(String(k));
if (raw !== null) out[k] = raw as T[keyof T];
});
return out;
}, [params, defaults]);
// `setSearchParams` مثل setState صف نمی‌شود؛ دو فراخوانی در یک handler، اگر هر دو از
// `params` فعلی شروع کنند، همدیگر را پاک می‌کنند. این ref آخرین مقدارِ نوشته‌شده را
// نگه می‌دارد تا فراخوانی‌های پشت‌سرهم روی هم انباشته شوند.
const latest = useRef(params);
useEffect(() => { latest.current = params; }, [params]);
/**
* چند کلید را با هم می‌نویسد. `replace` پیش‌فرض است تا تایپ در فیلد جستجو
* تاریخچه را پر نکند و یک بار «بازگشت» کاربر را از صفحه بیرون ببرد.
*/
const setState = useCallback((patch: Partial<T>, opts?: { push?: boolean }) => {
const next = new URLSearchParams(latest.current);
Object.entries(patch).forEach(([k, v]) => {
const value = v === undefined ? '' : String(v);
if (value === '' || value === defaults[k as keyof T]) next.delete(k);
else next.set(k, value);
});
latest.current = next;
setParams(next, { replace: !opts?.push });
}, [setParams, defaults]);
return [state, setState] as const;
}
/** عدد صفحه از URL — همیشه ≥ ۱. */
export function pageOf(value: string): number {
return Math.max(1, Number(value) || 1);
}