Files
DriverVault/Web App/web/src/auth.js
T
tajniak81andClaude Opus 5 e373497958 Settings: fold Users and Organization into the Settings tabs
The left rail is back to the three places you actually go — Garage,
Charging, Settings — and user management moves inside Settings as an
admin-only tab, next to a new Organization tab that used to be a card
buried in the personal settings. Tab order is Personal settings, Users,
Organization, Integrations. /admin redirects to /settings?tab=users so
old links keep working, and ?tab= picks the starting tab in general.

AdminUsers moves from views/ to components/ since it is a panel now, not
a route, and its page header becomes a section header like its
neighbours. The personal panel was split in two around the integrations
markup, which left no gap between the Profile and Privacy cards; it is
one block again.

Creating a user gets an organization picker for superadmins, defaulting
to "no organization" so an org-less account stays a deliberate choice.
Admins see no picker: the server pins their members to their own org
regardless, which users_test.go now covers along with both superadmin
paths.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 17:38:54 +02:00

60 lines
2.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Reactive auth state shared across the app. Token + user are persisted to
// localStorage so a refresh keeps the session. The actual network calls live in
// api.js (which reads the token from localStorage on each request).
import { reactive, computed } from "vue";
import { api, TOKEN_KEY, USER_KEY } from "./api";
import { applyProfilePrefs } from "./prefs";
export const state = reactive({
token: localStorage.getItem(TOKEN_KEY) || "",
user: JSON.parse(localStorage.getItem(USER_KEY) || "null"),
// Full settings-panel profile (bio, theme, locale, ...), fetched separately
// from /api/me since the login response only carries id/email/name.
profile: null,
});
export const isAuthenticated = computed(() => !!state.token);
// Roles that may manage users. A superadmin is an admin that also spans every
// organization; the API Server enforces the difference, the UI just needs to
// know whether to offer the Settings Users tab at all.
const MANAGER_ROLES = ["admin", "superadmin"];
// Admin gate for the UI. Driven by the full profile (fetched from /api/me),
// which always reflects the current role from the DB — so a promotion/demotion
// takes effect on the next profile refresh without needing a re-login.
export const isAdmin = computed(
() => MANAGER_ROLES.includes(state.profile?.role) || MANAGER_ROLES.includes(state.user?.role)
);
export async function login(email, password) {
// The API Server proxies login to PocketBase and relays its response
// verbatim, so the user is under `record` (PocketBase's name) and the token
// is PocketBase's own — this app no longer holds a server-minted JWT.
const res = await api.login(email, password);
state.token = res.token;
state.user = res.record;
localStorage.setItem(TOKEN_KEY, res.token);
localStorage.setItem(USER_KEY, JSON.stringify(res.record));
await refreshProfile();
return res;
}
export function logout() {
state.token = "";
state.user = null;
state.profile = null;
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(USER_KEY);
}
// Fetches the full profile (used for Settings + to apply appearance prefs).
// Safe to call on app boot when a token already exists from a previous visit.
export async function refreshProfile() {
if (!state.token) return null;
const profile = await api.getMe();
state.profile = profile;
applyProfilePrefs(profile);
return profile;
}