Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
c7abcd1
feat: implement server-side rendering for profile and account pages
sergeychernyshev Feb 22, 2026
75c08d0
feat: correctly implement SSR for profile and account pages with redi…
sergeychernyshev Feb 22, 2026
dba1910
feat: render login credentials on SSR
sergeychernyshev Feb 22, 2026
7c10037
feat: implement SSR for credentials and fix handleMyAccounts regression
sergeychernyshev Feb 22, 2026
f3e5058
fix: ensure all credentials are included in handleMe and used by load…
sergeychernyshev Feb 22, 2026
70acd3a
feat: render team members on account page using SSR
sergeychernyshev Feb 22, 2026
68fb63c
feat: dynamic providers in HTML templates using SSR
sergeychernyshev Feb 22, 2026
bb86198
feat: dynamic providers in Link another account section using SSR
sergeychernyshev Feb 22, 2026
5b74f37
fix: twitch icon contrast and further dynamic provider improvements
sergeychernyshev Feb 22, 2026
0e94cc2
test: verify rendered providers match configuration
sergeychernyshev Feb 22, 2026
327795b
fix: ensure SSR data is only used on initial load to allow client-sid…
sergeychernyshev Feb 22, 2026
5648e9a
style: picture removal button grey with red hover
sergeychernyshev Feb 22, 2026
6babce2
fix: refactor handleMe to fix TypeError and improve efficiency
sergeychernyshev Feb 22, 2026
0730c3b
feat: prevent DO creation for non-existent users by checking SystemDO…
sergeychernyshev Feb 22, 2026
2dbd370
refactor: remove redundant SystemDO check and trust cookie for UserDO…
sergeychernyshev Feb 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 88 additions & 52 deletions public/users/accounts.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
<title>Account Settings</title>
<link rel="stylesheet" href="/users/style.css" />
</head>
<body>
<body data-ssr-account="{{ssr:account_json}}" data-ssr-profile="{{ssr:profile_json}}" data-ssr-members="{{ssr:account_members_json}}">
<power-strip
providers="google,twitch"
providers="{{ssr:providers}}"
style="position: absolute; top: 0; right: 0; z-index: 9999; padding: 0.1rem; border-radius: 0 0 0 0.3rem"
>
<svg viewBox="0 0 24 24" style="width: 1rem; height: 1rem"><path d="M7 2v11h3v9l7-12h-4l4-8z" fill="#ffcc00" /></svg>
Expand All @@ -18,9 +18,9 @@
<div class="header-area">
<a href="/" class="back-link">← Back to Home</a>
<h1 class="page-subtitle">Account Settings</h1>
<div id="account-name-heading" class="page-title">Account</div>
<div id="account-name-heading" class="page-title">{{ssr:account_name}}</div>
<div id="account-id-subtitle" class="subtitle">
<span class="id-text" id="account-id-text"></span>
<span class="id-text" id="account-id-text">ID: {{ssr:account_id}}</span>
<button id="copy-id-btn" class="copy-btn" title="Copy Account ID">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
Expand All @@ -43,14 +43,20 @@ <h1 class="page-subtitle">Account Settings</h1>
</nav>

<div class="content-area">
<section id="account-info-section">
<section id="account-info-section" style="{{ssr:account_info_section_display}}">
<div class="avatar-section">
<div style="position: relative">
<img id="account-avatar" class="account-avatar-large" src="" alt="Account Avatar" style="display: none" />
<img
id="account-avatar"
class="account-avatar-large"
src="{{ssr:account_picture}}"
alt="Account Avatar"
style="{{ssr:account_picture_display}}"
/>
<div
id="account-avatar-placeholder"
class="account-avatar-large"
style="background: #f1f3f4; display: flex; align-items: center; justify-content: center; color: #5f6368"
style="background: #f1f3f4; {{ssr:account_placeholder_display}} align-items: center; justify-content: center; color: #5f6368"
>
<svg viewBox="0 0 24 24" style="width: 48px; height: 48px; fill: currentColor">
<path
Expand Down Expand Up @@ -79,55 +85,35 @@ <h1 class="page-subtitle">Account Settings</h1>
type="button"
id="remove-avatar-btn"
title="Remove image"
style="
position: absolute;
top: -5px;
right: -5px;
width: 20px;
height: 20px;
border-radius: 50%;
background: #ea4335;
color: white;
border: 2px solid white;
cursor: pointer;
display: none;
align-items: center;
justify-content: center;
padding: 0;
line-height: 1;
font-weight: bold;
font-size: 12px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
"
class="remove-image-btn"
style="{{ssr:account_remove_btn_display}}"
>
</button>
</div>
<div>
<h2 id="display-account-name" style="margin: 0; color: #333">Loading...</h2>
<p id="display-account-plan" style="margin: 0.25rem 0 0 0; color: #666"></p>
<h2 id="display-account-name" style="margin: 0; color: #333">{{ssr:account_name}}</h2>
<p id="display-account-plan" style="margin: 0.25rem 0 0 0; color: #666">{{ssr:account_plan_name}}</p>
</div>
</div>

<h2>General Information</h2>
<form id="account-info-form">
<div class="form-group">
<label for="account-name">Account Name</label>
<input type="text" id="account-name" name="name" maxlength="50" required />
<input type="text" id="account-name" name="name" maxlength="50" value="{{ssr:account_name}}" required />
</div>
<div class="form-group">
<label>Plan</label>
<input type="text" id="account-plan-display" disabled />
<input type="text" id="account-plan-display" value="{{ssr:account_plan_name}}" disabled />
</div>
<button type="submit" id="save-account-btn" disabled>Save Changes</button>
</form>
</section>

<section id="members-section">
<section id="members-section" style="{{ssr:account_members_section_display}}">
<h2>Team Members</h2>
<div id="members-list">
<p>Loading members...</p>
</div>
<div id="members-list">{{ssr:account_members_list_html}}</div>
</section>
</div>
</div>
Expand All @@ -140,18 +126,47 @@ <h2>Team Members</h2>
let currentUserRole = 0;
let currentUserId = null;
let initialAccountInfo = {};
let isInitialLoad = true;

async function init() {
try {
const res = await fetch(`${API_BASE}/me`);
if (!res.ok) {
if (res.status === 401) {
window.location.href = '/';
return;
// Try to get data from SSR first
let data = null;
let members = null;
const ssrProfileJson = document.body.getAttribute('data-ssr-profile');
const ssrAccountJson = document.body.getAttribute('data-ssr-account');
const ssrMembersJson = document.body.getAttribute('data-ssr-members');

if (ssrProfileJson && !ssrProfileJson.startsWith('{{ssr:') && ssrAccountJson && !ssrAccountJson.startsWith('{{ssr:')) {
try {
const profile = JSON.parse(ssrProfileJson);
const account = JSON.parse(ssrAccountJson);
if (ssrMembersJson && !ssrMembersJson.startsWith('{{ssr:')) {
members = JSON.parse(ssrMembersJson);
}
if (profile.valid) {
data = {
...profile,
account: account,
};
}
} catch (e) {
console.error('Failed to parse SSR data', e);
}
throw new Error('Failed to load user info');
}
const data = await res.json();

if (!data) {
const res = await fetch(`${API_BASE}/me`);
if (!res.ok) {
if (res.status === 401) {
window.location.href = '/';
return;
}
throw new Error('Failed to load user info');
}
data = await res.json();
}

if (data.valid && data.account) {
currentAccountId = data.account.id;
currentUserRole = data.account.role;
Expand Down Expand Up @@ -186,10 +201,11 @@ <h2>Team Members</h2>
section.appendChild(msg);
document.querySelector('.content-area').appendChild(section);
} else {
loadMembers();
loadMembers(members);
}

loadAccountDetails();
loadAccountDetails(data.account);
isInitialLoad = false;
}
} catch (e) {
showToast(e.message);
Expand Down Expand Up @@ -297,11 +313,17 @@ <h2>Team Members</h2>
}
};

async function loadAccountDetails() {
async function loadAccountDetails(ssrData) {
try {
const res = await fetch(`${API_BASE}/me/accounts/${currentAccountId}`);
if (res.ok) {
const data = await res.json();
let data = ssrData;
if (!data) {
const res = await fetch(`${API_BASE}/me/accounts/${currentAccountId}`);
if (res.ok) {
data = await res.json();
}
}

if (data) {
if (data.name) {
document.getElementById('account-name').value = data.name;
document.getElementById('display-account-name').textContent = data.name;
Expand Down Expand Up @@ -334,12 +356,26 @@ <h2>Team Members</h2>
}
}

async function loadMembers() {
async function loadMembers(ssrData) {
const list = document.getElementById('members-list');
try {
const res = await fetch(`${API_BASE}/me/accounts/${currentAccountId}/members`);
if (!res.ok) throw new Error('Failed to load members');
const members = await res.json();
let members = ssrData;
if (!members && isInitialLoad) {
const ssrMembersJson = document.body.getAttribute('data-ssr-members');
if (ssrMembersJson && !ssrMembersJson.startsWith('{{ssr:')) {
try {
members = JSON.parse(ssrMembersJson);
} catch (e) {
console.error('Failed to parse SSR members', e);
}
}
}

if (!members) {
const res = await fetch(`${API_BASE}/me/accounts/${currentAccountId}/members`);
if (!res.ok) throw new Error('Failed to load members');
members = await res.json();
}

if (members.length === 0) {
list.innerHTML = '<p>No members found.</p>';
Expand Down
2 changes: 1 addition & 1 deletion public/users/admin/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@
</head>
<body>
<power-strip
providers="google,twitch"
providers="{{ssr:providers}}"
style="position: absolute; top: 0; right: 0; z-index: 9999; padding: 0.1rem; border-radius: 0 0 0 0.3rem"
>
<svg viewBox="0 0 24 24" style="width: 1rem; height: 1rem"><path d="M7 2v11h3v9l7-12h-4l4-8z" fill="#ffcc00" /></svg>
Expand Down
Loading
Loading