136 lines
4.0 KiB
JavaScript
136 lines
4.0 KiB
JavaScript
import { computed, reactive, watch } from "vue";
|
|
import { auth, authFetch } from "../auth";
|
|
|
|
const state = reactive({
|
|
loading: false,
|
|
loaded: false,
|
|
status: "",
|
|
onboardingUrl: "/onboarding",
|
|
dashboardUrl: "/apps",
|
|
accountUrl: "/account",
|
|
accessAllowed: true,
|
|
error: "",
|
|
});
|
|
|
|
let watcherStarted = false;
|
|
let requestToken = 0;
|
|
|
|
function normalizeInternalUrl(value, fallback) {
|
|
if (typeof value !== "string" || !value.trim()) return fallback;
|
|
try {
|
|
const url = new URL(value, window.location.origin);
|
|
if (url.origin === window.location.origin) return `${url.pathname}${url.search}${url.hash}`;
|
|
return value;
|
|
} catch {
|
|
return value.startsWith("/") ? value : fallback;
|
|
}
|
|
}
|
|
|
|
function resetState() {
|
|
state.loading = false;
|
|
state.loaded = false;
|
|
state.status = "";
|
|
state.onboardingUrl = "/onboarding";
|
|
state.dashboardUrl = "/apps";
|
|
state.accountUrl = "/account";
|
|
state.accessAllowed = true;
|
|
state.error = "";
|
|
}
|
|
|
|
async function refreshMemberState() {
|
|
if (!auth.ready || !auth.authenticated) {
|
|
resetState();
|
|
return;
|
|
}
|
|
|
|
const token = (requestToken += 1);
|
|
state.loading = true;
|
|
state.error = "";
|
|
|
|
try {
|
|
const resp = await authFetch("/api/account/member-state", {
|
|
headers: { Accept: "application/json" },
|
|
cache: "no-store",
|
|
});
|
|
const data = await resp.json().catch(() => ({}));
|
|
if (token !== requestToken) return;
|
|
if (resp.status === 403) {
|
|
state.loaded = true;
|
|
state.status = "restricted";
|
|
state.accessAllowed = false;
|
|
state.onboardingUrl = "/request-access";
|
|
state.dashboardUrl = "/apps";
|
|
state.accountUrl = "/account";
|
|
return;
|
|
}
|
|
if (!resp.ok) throw new Error(data.error || `status ${resp.status}`);
|
|
state.loaded = true;
|
|
state.accessAllowed = true;
|
|
state.status = data.status || "unknown";
|
|
state.onboardingUrl = normalizeInternalUrl(data.onboarding_url, "/onboarding");
|
|
state.dashboardUrl = normalizeInternalUrl(data.dashboard_url, "/apps");
|
|
state.accountUrl = normalizeInternalUrl(data.account_url, "/account");
|
|
} catch (err) {
|
|
if (token !== requestToken) return;
|
|
state.loaded = true;
|
|
state.status = "unknown";
|
|
state.accessAllowed = true;
|
|
state.error = err?.message || "Member state unavailable";
|
|
} finally {
|
|
if (token === requestToken) state.loading = false;
|
|
}
|
|
}
|
|
|
|
function startWatcher() {
|
|
if (watcherStarted) return;
|
|
watcherStarted = true;
|
|
watch(
|
|
() => [auth.ready, auth.authenticated, auth.token],
|
|
() => {
|
|
refreshMemberState();
|
|
},
|
|
{ immediate: true },
|
|
);
|
|
}
|
|
|
|
function memberActionForState() {
|
|
if (!auth.enabled) return null;
|
|
if (!auth.ready) {
|
|
return { kind: "loading", label: "Checking Session", to: "", secondary: "" };
|
|
}
|
|
if (!auth.authenticated) {
|
|
return { kind: "signed-out", label: "Sign In", to: "", secondary: "/request-access" };
|
|
}
|
|
if (auth.emailVerified === false) {
|
|
return {
|
|
kind: "verify",
|
|
label: "Continue Verification",
|
|
to: normalizeInternalUrl(auth.accountUrl, "/request-access"),
|
|
secondary: state.accountUrl,
|
|
};
|
|
}
|
|
if (state.loading && !state.loaded) {
|
|
return { kind: "loading", label: "Checking Account", to: "", secondary: state.accountUrl };
|
|
}
|
|
if (["pending_email_verification", "pending"].includes(state.status)) {
|
|
return { kind: "pending", label: "Check Access Status", to: "/request-access", secondary: state.accountUrl };
|
|
}
|
|
if (["accounts_building", "awaiting_onboarding"].includes(state.status)) {
|
|
return { kind: "onboarding", label: "Continue Onboarding", to: state.onboardingUrl, secondary: state.accountUrl };
|
|
}
|
|
if (state.status === "restricted" || !state.accessAllowed) {
|
|
return { kind: "restricted", label: "Account Status", to: "/request-access", secondary: state.accountUrl };
|
|
}
|
|
return { kind: "ready", label: "Open Services", to: state.dashboardUrl, secondary: state.accountUrl };
|
|
}
|
|
|
|
export function useMemberAccessState() {
|
|
startWatcher();
|
|
return {
|
|
auth,
|
|
memberState: state,
|
|
memberAction: computed(memberActionForState),
|
|
refreshMemberState,
|
|
};
|
|
}
|