← back to Open Seo
src/client/features/domain/useDomainFilterPreferences.ts
158 lines
import { useCallback, useEffect, useState } from "react";
import {
EMPTY_DOMAIN_FILTERS,
type KeywordsFilterValues,
type PagesFilterValues,
} from "@/client/features/domain/types";
import {
KEYWORD_FILTER_FIELDS,
PAGE_FILTER_FIELDS,
} from "@/client/features/domain/domainFilterUtils";
const STORAGE_KEY_PREFIX = "domain-overview-filter-defaults:";
type DomainFilterPreferenceTab = "keywords" | "pages";
type FilterValues = Record<string, string>;
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
function hasAnyFilter(values: FilterValues): boolean {
return Object.values(values).some((value) => value.trim() !== "");
}
function getStorageKey(tab: DomainFilterPreferenceTab, scope: string): string {
return `${STORAGE_KEY_PREFIX}${scope}:${tab}`;
}
function loadFromStorage<T extends FilterValues>(
tab: DomainFilterPreferenceTab,
scope: string,
fallback: T,
): T {
const result = { ...fallback };
if (typeof window === "undefined") return result;
try {
const raw = window.localStorage.getItem(getStorageKey(tab, scope));
if (!raw) return result;
const parsed: unknown = JSON.parse(raw);
if (!isRecord(parsed)) return result;
for (const key in fallback) {
const value = parsed[key];
if (typeof value === "string") {
Object.assign(result, { [key]: value });
}
}
} catch {
// localStorage can be unavailable in private browsing or strict modes.
}
return result;
}
function saveToStorage(
tab: DomainFilterPreferenceTab,
scope: string,
values: FilterValues,
) {
if (typeof window === "undefined") return;
try {
const key = getStorageKey(tab, scope);
if (hasAnyFilter(values)) {
window.localStorage.setItem(key, JSON.stringify(values));
} else {
window.localStorage.removeItem(key);
}
} catch {
// storage full or unavailable
}
}
function clearStorage(tab: DomainFilterPreferenceTab, scope: string) {
if (typeof window === "undefined") return;
try {
window.localStorage.removeItem(getStorageKey(tab, scope));
} catch {
// storage unavailable
}
}
function emptyPageFilters(): PagesFilterValues {
return {
include: EMPTY_DOMAIN_FILTERS.include,
exclude: EMPTY_DOMAIN_FILTERS.exclude,
minTraffic: EMPTY_DOMAIN_FILTERS.minTraffic,
maxTraffic: EMPTY_DOMAIN_FILTERS.maxTraffic,
minVol: EMPTY_DOMAIN_FILTERS.minVol,
maxVol: EMPTY_DOMAIN_FILTERS.maxVol,
};
}
function loadKeywordFilters(scope: string): KeywordsFilterValues {
return loadFromStorage("keywords", scope, { ...EMPTY_DOMAIN_FILTERS });
}
function loadPageFilters(scope: string): PagesFilterValues {
return loadFromStorage("pages", scope, emptyPageFilters());
}
export function useDomainKeywordFilterPreferences(scope: string) {
const [filters, setFilters] = useState<KeywordsFilterValues>(() =>
loadKeywordFilters(scope),
);
useEffect(() => {
setFilters(loadKeywordFilters(scope));
}, [scope]);
const save = useCallback(
(values: KeywordsFilterValues) => {
const next = { ...EMPTY_DOMAIN_FILTERS };
for (const key of KEYWORD_FILTER_FIELDS) next[key] = values[key];
saveToStorage("keywords", scope, next);
setFilters(next);
},
[scope],
);
const clear = useCallback(() => {
clearStorage("keywords", scope);
setFilters({ ...EMPTY_DOMAIN_FILTERS });
}, [scope]);
return { filters, save, clear };
}
export function useDomainPageFilterPreferences(scope: string) {
const [filters, setFilters] = useState<PagesFilterValues>(() =>
loadPageFilters(scope),
);
useEffect(() => {
setFilters(loadPageFilters(scope));
}, [scope]);
const save = useCallback(
(values: PagesFilterValues) => {
const next = emptyPageFilters();
for (const key of PAGE_FILTER_FIELDS) next[key] = values[key];
saveToStorage("pages", scope, next);
setFilters(next);
},
[scope],
);
const clear = useCallback(() => {
clearStorage("pages", scope);
setFilters(emptyPageFilters());
}, [scope]);
return { filters, save, clear };
}