Adds a preferences JSON field on the user plus GET/POST /api/auth/preferences/ (merge semantics, validated keys), surfaced in /auth/me/ and typed on the frontend. The Account page gains a Browsing preferences card: landing page, default rating filter, default sort, items per page and thumbnail size. Signed-in users also sync these while browsing (the Library sidebar's rating/sort/per-page controls and the new thumbnail slider), debounced; on load the account's values seed the local UI state, so settings follow the user across browsers. Guests keep the existing localStorage behaviour. The thumbnail size drives the media grids (Library, Online, pool detail) between 140 and 320px columns. Verified the API against the dev server: merge keeps untouched keys, invalid values 400, values round-trip through /auth/me/.
75 lines
2.0 KiB
TypeScript
75 lines
2.0 KiB
TypeScript
import { create } from "zustand";
|
|
|
|
import { api, getToken, setToken } from "@/lib/api";
|
|
import { applyPreferences } from "@/lib/preferences";
|
|
import { queryClient } from "@/lib/queryClient";
|
|
import type { User } from "@/lib/types";
|
|
import { useE621 } from "@/store/e621";
|
|
|
|
interface AuthState {
|
|
user: User | null;
|
|
ready: boolean;
|
|
login: (username: string, password: string) => Promise<void>;
|
|
register: (username: string, password: string) => Promise<void>;
|
|
logout: () => Promise<void>;
|
|
loadUser: () => Promise<void>;
|
|
}
|
|
|
|
export const useAuth = create<AuthState>((set) => ({
|
|
user: null,
|
|
ready: false,
|
|
|
|
login: async (username, password) => {
|
|
const data = await api<{ token: string }>("/api/auth/token/", {
|
|
method: "POST",
|
|
json: { username, password },
|
|
});
|
|
setToken(data.token);
|
|
const user = await api<User>("/api/auth/me/");
|
|
// Never show the previous account's cached data to this one.
|
|
queryClient.clear();
|
|
useE621.getState().clear();
|
|
applyPreferences(user.preferences);
|
|
set({ user, ready: true });
|
|
},
|
|
|
|
register: async (username, password) => {
|
|
const data = await api<{ token: string; user: User }>("/api/auth/register/", {
|
|
method: "POST",
|
|
json: { username, password },
|
|
});
|
|
setToken(data.token);
|
|
queryClient.clear();
|
|
useE621.getState().clear();
|
|
applyPreferences(data.user.preferences);
|
|
set({ user: data.user, ready: true });
|
|
},
|
|
|
|
logout: async () => {
|
|
try {
|
|
await api("/api/auth/logout/", { method: "POST" });
|
|
} catch {
|
|
// The token may already be invalid; clearing it locally is enough.
|
|
}
|
|
setToken(null);
|
|
queryClient.clear();
|
|
useE621.getState().clear();
|
|
set({ user: null, ready: true });
|
|
},
|
|
|
|
loadUser: async () => {
|
|
if (!getToken()) {
|
|
set({ user: null, ready: true });
|
|
return;
|
|
}
|
|
try {
|
|
const user = await api<User>("/api/auth/me/");
|
|
applyPreferences(user.preferences);
|
|
set({ user, ready: true });
|
|
} catch {
|
|
setToken(null);
|
|
set({ user: null, ready: true });
|
|
}
|
|
},
|
|
}));
|