fix: remove rvote proposals from space dashboard + fix protocol in redirects
1. Remove all rvote/proposals fetching from rstack-user-dashboard.
rApp-specific data (proposals) should stay within the rVote module,
not leak into the space-level dashboard.
2. Fix url.protocol in bare-domain redirects — TLS is terminated by
Cloudflare/Traefik so url.protocol is always http: internally.
Use https: for production domains.
3. Rewrite /{space}/api/... paths internally on bare domain instead
of redirecting to subdomain (avoids CORS + mixed content issues).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6e4d1436e0
commit
4536b5cab1
|
|
@ -2,7 +2,7 @@
|
||||||
* <rstack-user-dashboard> — Space-centric dashboard shown when all tabs are closed.
|
* <rstack-user-dashboard> — Space-centric dashboard shown when all tabs are closed.
|
||||||
*
|
*
|
||||||
* Sections: space header + stats, members, tools open, recent activity,
|
* Sections: space header + stats, members, tools open, recent activity,
|
||||||
* active votes, and quick actions.
|
* and quick actions.
|
||||||
*
|
*
|
||||||
* Attributes:
|
* Attributes:
|
||||||
* space — current space slug
|
* space — current space slug
|
||||||
|
|
@ -26,15 +26,6 @@ interface NotificationItem {
|
||||||
read: boolean;
|
read: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ProposalInfo {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
status: string;
|
|
||||||
score: number;
|
|
||||||
vote_count: string;
|
|
||||||
created_at: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TabLayer {
|
interface TabLayer {
|
||||||
moduleId: string;
|
moduleId: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
|
|
@ -47,11 +38,9 @@ export class RStackUserDashboard extends HTMLElement {
|
||||||
#shadow: ShadowRoot;
|
#shadow: ShadowRoot;
|
||||||
#members: MemberInfo[] = [];
|
#members: MemberInfo[] = [];
|
||||||
#notifications: NotificationItem[] = [];
|
#notifications: NotificationItem[] = [];
|
||||||
#proposals: ProposalInfo[] = [];
|
|
||||||
#openTabs: TabLayer[] = [];
|
#openTabs: TabLayer[] = [];
|
||||||
#membersLoading = true;
|
#membersLoading = true;
|
||||||
#notifLoading = true;
|
#notifLoading = true;
|
||||||
#proposalsLoading = true;
|
|
||||||
#lastFetch = 0;
|
#lastFetch = 0;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -91,7 +80,6 @@ export class RStackUserDashboard extends HTMLElement {
|
||||||
}
|
}
|
||||||
this.#membersLoading = true;
|
this.#membersLoading = true;
|
||||||
this.#notifLoading = true;
|
this.#notifLoading = true;
|
||||||
this.#proposalsLoading = true;
|
|
||||||
this.#render();
|
this.#render();
|
||||||
this.#loadData();
|
this.#loadData();
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +89,6 @@ export class RStackUserDashboard extends HTMLElement {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.#fetchMembers(),
|
this.#fetchMembers(),
|
||||||
this.#fetchNotifications(),
|
this.#fetchNotifications(),
|
||||||
this.#fetchProposals(),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,25 +126,6 @@ export class RStackUserDashboard extends HTMLElement {
|
||||||
this.#render();
|
this.#render();
|
||||||
}
|
}
|
||||||
|
|
||||||
async #fetchProposals() {
|
|
||||||
this.#proposalsLoading = true;
|
|
||||||
try {
|
|
||||||
const slug = encodeURIComponent(this.space);
|
|
||||||
const res = await fetch(`/${slug}/rvote/api/proposals?space_slug=${slug}&limit=5`);
|
|
||||||
if (res.ok) {
|
|
||||||
const data = await res.json();
|
|
||||||
this.#proposals = (data.proposals || []).filter(
|
|
||||||
(p: ProposalInfo) => p.status !== "completed" && p.status !== "rejected",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// rvote not installed or offline — hide section
|
|
||||||
this.#proposals = [];
|
|
||||||
}
|
|
||||||
this.#proposalsLoading = false;
|
|
||||||
this.#render();
|
|
||||||
}
|
|
||||||
|
|
||||||
#timeAgo(iso: string): string {
|
#timeAgo(iso: string): string {
|
||||||
const diff = Date.now() - new Date(iso).getTime();
|
const diff = Date.now() - new Date(iso).getTime();
|
||||||
const mins = Math.floor(diff / 60_000);
|
const mins = Math.floor(diff / 60_000);
|
||||||
|
|
@ -219,9 +187,6 @@ export class RStackUserDashboard extends HTMLElement {
|
||||||
if (!this.#membersLoading) {
|
if (!this.#membersLoading) {
|
||||||
statPills.push(`<span class="stat-pill">${this.#members.length} member${this.#members.length !== 1 ? "s" : ""}</span>`);
|
statPills.push(`<span class="stat-pill">${this.#members.length} member${this.#members.length !== 1 ? "s" : ""}</span>`);
|
||||||
}
|
}
|
||||||
if (!this.#proposalsLoading && this.#proposals.length > 0) {
|
|
||||||
statPills.push(`<span class="stat-pill">${this.#proposals.length} active proposal${this.#proposals.length !== 1 ? "s" : ""}</span>`);
|
|
||||||
}
|
|
||||||
if (this.#openTabs.length > 0) {
|
if (this.#openTabs.length > 0) {
|
||||||
statPills.push(`<span class="stat-pill">${this.#openTabs.length} tab${this.#openTabs.length !== 1 ? "s" : ""} open</span>`);
|
statPills.push(`<span class="stat-pill">${this.#openTabs.length} tab${this.#openTabs.length !== 1 ? "s" : ""} open</span>`);
|
||||||
}
|
}
|
||||||
|
|
@ -277,32 +242,7 @@ export class RStackUserDashboard extends HTMLElement {
|
||||||
`).join("");
|
`).join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Active Votes ──
|
|
||||||
let votesHTML = "";
|
|
||||||
if (!this.#proposalsLoading && this.#proposals.length > 0) {
|
|
||||||
votesHTML = `
|
|
||||||
<div class="section">
|
|
||||||
<div class="section-header"><h2>Active Votes</h2></div>
|
|
||||||
<div class="votes-list">
|
|
||||||
${this.#proposals.map(p => `
|
|
||||||
<button class="vote-item" data-navigate="rvote">
|
|
||||||
<div class="vote-info">
|
|
||||||
<div class="vote-title">${p.title}</div>
|
|
||||||
<div class="vote-meta">
|
|
||||||
<span class="vote-status">${p.status}</span>
|
|
||||||
<span class="vote-count">${p.vote_count} vote${p.vote_count !== "1" ? "s" : ""}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span class="vote-score">${p.score > 0 ? "+" : ""}${p.score}</span>
|
|
||||||
</button>
|
|
||||||
`).join("")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Quick Actions ──
|
// ── Quick Actions ──
|
||||||
const hasProposals = this.#proposals.length > 0;
|
|
||||||
|
|
||||||
this.#shadow.innerHTML = `
|
this.#shadow.innerHTML = `
|
||||||
<style>${STYLES}</style>
|
<style>${STYLES}</style>
|
||||||
|
|
@ -332,8 +272,6 @@ export class RStackUserDashboard extends HTMLElement {
|
||||||
<div class="activity-list">${activityHTML}</div>
|
<div class="activity-list">${activityHTML}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
${votesHTML}
|
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="section-header"><h2>Quick Actions</h2></div>
|
<div class="section-header"><h2>Quick Actions</h2></div>
|
||||||
<div class="actions-row">
|
<div class="actions-row">
|
||||||
|
|
@ -346,11 +284,6 @@ export class RStackUserDashboard extends HTMLElement {
|
||||||
<button class="action-btn" data-action-module="rspace">
|
<button class="action-btn" data-action-module="rspace">
|
||||||
<span class="action-icon">\u{1F30C}</span><span>Canvas</span>
|
<span class="action-icon">\u{1F30C}</span><span>Canvas</span>
|
||||||
</button>
|
</button>
|
||||||
${hasProposals ? `
|
|
||||||
<button class="action-btn" data-action-module="rvote">
|
|
||||||
<span class="action-icon">\u{1F5F3}</span><span>Vote</span>
|
|
||||||
</button>
|
|
||||||
` : ""}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -388,13 +321,6 @@ export class RStackUserDashboard extends HTMLElement {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Vote items
|
|
||||||
this.#shadow.querySelectorAll(".vote-item").forEach(el => {
|
|
||||||
el.addEventListener("click", () => {
|
|
||||||
this.#dispatch((el as HTMLElement).dataset.navigate || "rvote");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Quick action buttons
|
// Quick action buttons
|
||||||
this.#shadow.querySelectorAll(".action-btn").forEach(el => {
|
this.#shadow.querySelectorAll(".action-btn").forEach(el => {
|
||||||
el.addEventListener("click", () => {
|
el.addEventListener("click", () => {
|
||||||
|
|
@ -668,77 +594,6 @@ const STYLES = `
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Active Votes ── */
|
|
||||||
|
|
||||||
.votes-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
border-radius: 10px;
|
|
||||||
background: var(--rs-bg-surface, #1e293b);
|
|
||||||
border: 1px solid var(--rs-border, rgba(255,255,255,0.08));
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vote-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 10px 16px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.15s;
|
|
||||||
border: none;
|
|
||||||
border-bottom: 1px solid var(--rs-border, rgba(255,255,255,0.06));
|
|
||||||
background: none;
|
|
||||||
text-align: left;
|
|
||||||
color: inherit;
|
|
||||||
font: inherit;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.vote-item:last-child { border-bottom: none; }
|
|
||||||
.vote-item:hover {
|
|
||||||
background: var(--rs-bg-hover, rgba(255,255,255,0.05));
|
|
||||||
}
|
|
||||||
|
|
||||||
.vote-info { flex: 1; min-width: 0; }
|
|
||||||
|
|
||||||
.vote-title {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--rs-text-primary, #e2e8f0);
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vote-meta {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
margin-top: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vote-status {
|
|
||||||
font-size: 0.65rem;
|
|
||||||
padding: 1px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: rgba(251,191,36,0.15);
|
|
||||||
color: #fbbf24;
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vote-count {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
color: var(--rs-text-muted, #94a3b8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vote-score {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--rs-accent, #06b6d4);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ── Quick Actions ── */
|
/* ── Quick Actions ── */
|
||||||
|
|
||||||
.actions-row {
|
.actions-row {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue