fix: getApiBase() across all 16 rApp modules for subdomain routing

All modules had getApiBase() matching wrong module names (e.g. /vote
instead of /rvote) and requiring /{space}/ prefix in the URL path.
On subdomains like jeff.rspace.online, the browser URL is /rfunds/...
not /jeff/rfunds/..., so the regex never matched.

New pattern: /^(\/[^/]+)?\/rmodule/ handles both:
- Subdomain: /rfunds/... → base = /rfunds
- Direct: /demo/rfunds/... → base = /demo/rfunds

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-02 21:01:06 -08:00
parent cb5952c770
commit b6ddd4a833
16 changed files with 41 additions and 43 deletions

View File

@ -354,8 +354,8 @@ class FolkCalendarView extends HTMLElement {
private getApiBase(): string {
// When on the rcal page directly, extract from URL
const match = window.location.pathname.match(/^\/([^/]+)\/rcal/);
if (match) return `/${match[1]}/rcal`;
const match = window.location.pathname.match(/^(\/[^/]+)?\/rcal/);
if (match) return match[0];
// When embedded as a canvas shape, use the space attribute
if (this.space) return `/${this.space}/rcal`;
return "";

View File

@ -184,8 +184,8 @@ class FolkCartShop extends HTMLElement {
private getApiBase(): string {
const path = window.location.pathname;
const parts = path.split("/").filter(Boolean);
return parts.length >= 2 ? `/${parts[0]}/cart` : "/demo/cart";
const match = path.match(/^(\/[^/]+)?\/rcart/);
return match ? match[0] : "/rcart";
}
private async loadData() {

View File

@ -43,8 +43,8 @@ class FolkChoicesDashboard extends HTMLElement {
private getApiBase(): string {
const path = window.location.pathname;
const parts = path.split("/").filter(Boolean);
return parts.length >= 2 ? `/${parts[0]}/choices` : "/demo/choices";
const match = path.match(/^(\/[^/]+)?\/rchoices/);
return match ? match[0] : "/rchoices";
}
private async loadChoices() {

View File

@ -161,8 +161,8 @@ class FolkFileBrowser extends HTMLElement {
private getApiBase(): string {
const path = window.location.pathname;
const match = path.match(/^\/([^/]+)\/files/);
return match ? `/${match[1]}/files` : "";
const match = path.match(/^(\/[^/]+)?\/rfiles/);
return match ? match[0] : "";
}
private formatSize(bytes: number): string {

View File

@ -43,8 +43,8 @@ class FolkForumDashboard extends HTMLElement {
private getApiBase(): string {
const path = window.location.pathname;
const match = path.match(/^\/([^/]+)\/forum/);
return match ? `/${match[1]}/forum` : "";
const match = path.match(/^(\/[^/]+)?\/rforum/);
return match ? match[0] : "";
}
private getAuthHeaders(): Record<string, string> {

View File

@ -132,8 +132,9 @@ class FolkFundsApp extends HTMLElement {
private getApiBase(): string {
const path = window.location.pathname;
const match = path.match(/^\/([^/]+)\/funds/);
return match ? `/${match[1]}/funds` : "";
// Subdomain: /rfunds/... or Direct: /{space}/rfunds/...
const match = path.match(/^(\/[^/]+)?\/rfunds/);
return match ? `${match[0]}` : "";
}
private async loadFlows() {
@ -221,7 +222,7 @@ class FolkFundsApp extends HTMLElement {
// ─── Landing page ──────────────────────────────────────
private renderLanding(): string {
const demoUrl = this.getApiBase() ? `${this.getApiBase().replace(/\/funds$/, "")}/funds/demo` : "/demo";
const demoUrl = this.getApiBase() ? `${this.getApiBase()}/demo` : "/rfunds/demo";
const authed = isAuthenticated();
const username = getUsername();
@ -325,8 +326,8 @@ class FolkFundsApp extends HTMLElement {
private renderFlowCard(f: FlowSummary): string {
const detailUrl = this.getApiBase()
? `${this.getApiBase().replace(/\/funds$/, "")}/funds/flow/${encodeURIComponent(f.id)}`
: `/flow/${encodeURIComponent(f.id)}`;
? `${this.getApiBase()}/flow/${encodeURIComponent(f.id)}`
: `/rfunds/flow/${encodeURIComponent(f.id)}`;
const value = f.totalValue != null ? `$${Math.floor(f.totalValue).toLocaleString()}` : "";
return `
@ -345,8 +346,8 @@ class FolkFundsApp extends HTMLElement {
private renderDetail(): string {
const backUrl = this.getApiBase()
? `${this.getApiBase().replace(/\/funds$/, "")}/funds/`
: "/";
? `${this.getApiBase()}/`
: "/rfunds/";
return `
<div class="funds-detail">
@ -2268,8 +2269,8 @@ class FolkFundsApp extends HTMLElement {
// Navigate to the new flow
if (flowId) {
const detailUrl = this.getApiBase()
? `${this.getApiBase().replace(/\/funds$/, "")}/funds/flow/${encodeURIComponent(flowId)}`
: `/flow/${encodeURIComponent(flowId)}`;
? `${this.getApiBase()}/flow/${encodeURIComponent(flowId)}`
: `/rfunds/flow/${encodeURIComponent(flowId)}`;
window.location.href = detailUrl;
return;
}

View File

@ -774,8 +774,8 @@ class FolkMapViewer extends HTMLElement {
private getApiBase(): string {
const path = window.location.pathname;
const match = path.match(/^\/([^/]+)\/maps/);
return match ? `/${match[1]}/maps` : "";
const match = path.match(/^(\/[^/]+)?\/rmaps/);
return match ? match[0] : "";
}
private async checkSyncHealth() {

View File

@ -107,8 +107,8 @@ class FolkGraphViewer extends HTMLElement {
private getApiBase(): string {
const path = window.location.pathname;
const match = path.match(/^\/([^/]+)\/network/);
return match ? `/${match[1]}/network` : "";
const match = path.match(/^(\/[^/]+)?\/rnetwork/);
return match ? match[0] : "";
}
private async loadData() {

View File

@ -633,8 +633,8 @@ Gear: EUR 400 (10%)</code></pre><p><em>Maya is tracking expenses in rF
private getApiBase(): string {
const path = window.location.pathname;
const match = path.match(/^\/([^/]+)\/notes/);
return match ? `/${match[1]}/notes` : "";
const match = path.match(/^(\/[^/]+)?\/rnotes/);
return match ? match[0] : "";
}
private async loadNotebooks() {

View File

@ -113,12 +113,12 @@ class FolkPhotoGallery extends HTMLElement {
private getApiBase(): string {
const path = window.location.pathname;
const match = path.match(/^\/([^/]+)\/rphotos/);
return match ? `/${match[1]}/rphotos` : "";
const match = path.match(/^(\/[^/]+)?\/rphotos/);
return match ? match[0] : "";
}
private getImmichUrl(): string {
return `/${this.space}/rphotos/album`;
return `${this.getApiBase()}/album`;
}
private async loadGallery() {

View File

@ -184,8 +184,8 @@ class FolkSwagDesigner extends HTMLElement {
private getApiBase(): string {
const path = window.location.pathname;
const parts = path.split("/").filter(Boolean);
return parts.length >= 2 ? `/${parts[0]}/swag` : "/demo/swag";
const match = path.match(/^(\/[^/]+)?\/rswag/);
return match ? match[0] : "/rswag";
}
private getDemoProduct(): DemoProduct {

View File

@ -42,11 +42,8 @@ class FolkRoutePlanner extends HTMLElement {
private getApiBase(): string {
const path = window.location.pathname;
const parts = path.split("/").filter(Boolean);
if (parts.length >= 2 && parts[1] === "trips") {
return `/${parts[0]}/trips`;
}
return "/demo/trips";
const match = path.match(/^(\/[^/]+)?\/rtrips/);
return match ? match[0] : "/rtrips";
}
private async fetchRoute(input: RouteInput): Promise<FittedRoute> {

View File

@ -292,8 +292,8 @@ class FolkTripsPlanner extends HTMLElement {
private getApiBase(): string {
const path = window.location.pathname;
const match = path.match(/^\/([^/]+)\/trips/);
return match ? `/${match[1]}/trips` : "";
const match = path.match(/^(\/[^/]+)?\/rtrips/);
return match ? match[0] : "";
}
private async loadTrips() {

View File

@ -133,8 +133,8 @@ class FolkVoteDashboard extends HTMLElement {
private getApiBase(): string {
const path = window.location.pathname;
const match = path.match(/^\/([^/]+)\/vote/);
return match ? `/${match[1]}/vote` : "";
const match = path.match(/^(\/[^/]+)?\/rvote/);
return match ? match[0] : "";
}
private async loadSpaces() {

View File

@ -86,8 +86,8 @@ class FolkWalletViewer extends HTMLElement {
private getApiBase(): string {
const path = window.location.pathname;
const match = path.match(/^\/([^/]+)\/wallet/);
return match ? `/${match[1]}/wallet` : "";
const match = path.match(/^(\/[^/]+)?\/rwallet/);
return match ? match[0] : "";
}
private async detectChains() {

View File

@ -56,8 +56,8 @@ class FolkWorkBoard extends HTMLElement {
private getApiBase(): string {
const path = window.location.pathname;
const match = path.match(/^\/([^/]+)\/work/);
return match ? `/${match[1]}/work` : "";
const match = path.match(/^(\/[^/]+)?\/rwork/);
return match ? match[0] : "";
}
private async loadWorkspaces() {