Run the SPA without a backend (local mode)
The app can now operate backend-agnostically: a production build still asks on first start, but /setup also offers 'Continue without a backend' (stored as the sentinel 'none'), and the shell adapts: - Local mode shows only the e621-facing pages: Online (search, post view, favorites, blacklist editor, direct downloads) and Pools. Library, uploads, duplicates, stats, users, follows and similarity are hidden from the nav and palette and render a 'backend needed' state when reached directly; /detail/<e621 id> still works while /detail/J-x asks for a backend. - e621 credentials are stored in this browser (j621.e621) and the Account page becomes a credentials-only screen; the store reads/writes locally instead of /api/auth/e621/. - The header replaces the status pill and login/user area with an e621 credentials button and a 'Setup Backend' button; the footer shows 'Local mode — e621 features only' with the same entry point. - In-library lookups (badges/browse markers) are skipped without a backend; 'Download to client' links straight to the e621 file instead of the backend proxy; follow buttons and palette follow toggles are hidden; api() fails fast with a clear message if something slips through. Mode logic lives in lib/backend.ts (URL / '' same-origin / 'none') with its matrix verified in Node; tsc, oxlint and the build are clean.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
import { api, getToken, setToken } from "@/lib/api";
|
||||
import { hasBackend } from "@/lib/backend";
|
||||
import { applyPreferences } from "@/lib/preferences";
|
||||
import { queryClient } from "@/lib/queryClient";
|
||||
import type { User } from "@/lib/types";
|
||||
@@ -58,6 +59,11 @@ export const useAuth = create<AuthState>((set) => ({
|
||||
},
|
||||
|
||||
loadUser: async () => {
|
||||
if (!hasBackend()) {
|
||||
// Local mode: no account, and the e621 features work on their own.
|
||||
set({ user: null, ready: true });
|
||||
return;
|
||||
}
|
||||
if (!getToken()) {
|
||||
set({ user: null, ready: true });
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { create } from "zustand";
|
||||
|
||||
import { api } from "@/lib/api";
|
||||
import { hasBackend } from "@/lib/backend";
|
||||
import {
|
||||
effectiveCredentials,
|
||||
updateBlacklist,
|
||||
@@ -9,6 +10,43 @@ import {
|
||||
type E621User,
|
||||
} from "@/lib/e621";
|
||||
|
||||
/** Standalone mode keeps credentials in this browser only. */
|
||||
const LOCAL_CREDENTIALS_KEY = "j621.e621";
|
||||
|
||||
function readLocalCredentials(): E621Credentials | null {
|
||||
try {
|
||||
const raw = window.localStorage.getItem(LOCAL_CREDENTIALS_KEY);
|
||||
if (!raw) return null;
|
||||
const parsed = JSON.parse(raw) as Partial<E621Credentials>;
|
||||
const username = String(parsed.username ?? "");
|
||||
const api_key = String(parsed.api_key ?? "");
|
||||
return {
|
||||
username,
|
||||
api_key,
|
||||
base_url: String(parsed.base_url ?? "https://e621.net"),
|
||||
configured: Boolean(username && api_key),
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function writeLocalCredentials(values: {
|
||||
username: string;
|
||||
api_key: string;
|
||||
base_url: string;
|
||||
}): E621Credentials {
|
||||
const credentials: E621Credentials = {
|
||||
...values,
|
||||
configured: Boolean(values.username && values.api_key),
|
||||
};
|
||||
window.localStorage.setItem(
|
||||
LOCAL_CREDENTIALS_KEY,
|
||||
JSON.stringify(credentials),
|
||||
);
|
||||
return credentials;
|
||||
}
|
||||
|
||||
interface E621State {
|
||||
credentials: E621Credentials | null;
|
||||
profile: E621User | null;
|
||||
@@ -35,6 +73,11 @@ export const useE621 = create<E621State>((set, get) => ({
|
||||
|
||||
load: async () => {
|
||||
set({ loading: true });
|
||||
if (!hasBackend()) {
|
||||
set({ credentials: readLocalCredentials(), ready: true, loading: false });
|
||||
void get().loadBlacklist();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const credentials = await api<E621Credentials>("/api/auth/e621/");
|
||||
set({ credentials, ready: true, loading: false });
|
||||
@@ -71,6 +114,12 @@ export const useE621 = create<E621State>((set, get) => ({
|
||||
},
|
||||
|
||||
save: async (values) => {
|
||||
if (!hasBackend()) {
|
||||
const credentials = writeLocalCredentials(values);
|
||||
set({ credentials });
|
||||
void get().loadBlacklist();
|
||||
return credentials;
|
||||
}
|
||||
const credentials = await api<E621Credentials>("/api/auth/e621/", {
|
||||
method: "PUT",
|
||||
json: values,
|
||||
|
||||
Reference in New Issue
Block a user