Web App: two sites, one tab — a server you can switch
Two locations means two full stacks, and until now the app could only ever see the one that served it. The login screen's "Server settings" could point it elsewhere, but as a global swap: it replaced the server rather than adding one, and forgot the session you already had. There is now a server picker at the bottom of the app rail, after signing in rather than on the login screen. It names the server you are reading — which matters most when both sites look identical — and switches in a click. Each server is its own PocketBase with its own users, so nothing is federated: you sign into each one once and a token is kept per server. The home server is the one that served the app, reached same-origin through the BFF proxy; any other is added by address and called straight from the browser, which asks nothing new of the network — an API Server the phone app can reach is internet-facing already. Two things the switch turned out to need: Views load on mount, so swapping the session alone left B's user looking at A's cars. The RouterView is keyed on the active server and a switch returns to the garage, since record ids belong to the server that issued them. A request now carries the id of the server it went out to, and a 401 clears that session rather than whatever is active when the answer lands. A page fires half a dozen calls at once: the first rejection used to switch away and the rest then cleared the session of the server it had switched to, turning one stale remote token into a full sign-out. A remote session expiring falls back to the home server with the entry left in place to sign into again. Only Log out clears them all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
00d4c17392
commit
f521d2b220
11 files changed
+757
-131
No files matched your search
+18
-3
@@ -1,11 +1,13 @@
|
||||
<script setup>
|
||||
import { onMounted, onBeforeUnmount, computed, ref } from "vue";
|
||||
import { onMounted, onBeforeUnmount, computed, ref, watch } from "vue";
|
||||
import { RouterView, RouterLink, useRouter, useRoute } from "vue-router";
|
||||
import { state, isAuthenticated, logout, refreshProfile } from "./auth";
|
||||
import { prefs, applyProfilePrefs } from "./prefs";
|
||||
import { servers } from "./servers";
|
||||
import { api } from "./api";
|
||||
import { t } from "./i18n";
|
||||
import Logo from "./components/Logo.vue";
|
||||
import ServerSwitcher from "./components/ServerSwitcher.vue";
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
@@ -40,6 +42,17 @@ function toggleDragLock() {
|
||||
if (isAuthenticated.value) api.updateMe({ dragLocked: next }).catch(() => {});
|
||||
}
|
||||
|
||||
// Switching servers puts you in front of a different garage. Record ids belong
|
||||
// to the server that issued them, so nothing on screen survives the change —
|
||||
// hence back to the garage every time, and a keyed RouterView below so the view
|
||||
// remounts and re-fetches even when the route itself doesn't change.
|
||||
watch(
|
||||
() => servers.activeId,
|
||||
() => {
|
||||
if (route.name !== "login") router.replace({ name: "dashboard" });
|
||||
}
|
||||
);
|
||||
|
||||
const userInitial = computed(() =>
|
||||
(state.user?.name || state.user?.email || "?").charAt(0).toUpperCase()
|
||||
);
|
||||
@@ -102,8 +115,10 @@ onBeforeUnmount(() => themeObserver?.disconnect());
|
||||
</RouterLink>
|
||||
</nav>
|
||||
|
||||
<!-- Footer: drag lock + theme toggle + user + logout -->
|
||||
<!-- Footer: server + drag lock + theme toggle + user + logout -->
|
||||
<div class="mt-auto flex flex-col gap-2 border-t border-white/10 pt-3">
|
||||
<ServerSwitcher />
|
||||
|
||||
<button
|
||||
class="flex items-center gap-3 rounded-control px-2.5 py-2 text-[15px] font-medium transition-colors hover:bg-white/5 hover:text-white md:px-3"
|
||||
:class="prefs.dragLocked ? 'text-white/80' : 'text-white/60'"
|
||||
@@ -141,7 +156,7 @@ onBeforeUnmount(() => themeObserver?.disconnect());
|
||||
<!-- Main content -->
|
||||
<main class="min-w-0 flex-1 px-5 py-6 md:px-8 lg:px-10">
|
||||
<div class="mx-auto max-w-5xl">
|
||||
<RouterView />
|
||||
<RouterView :key="servers.activeId" />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user