Files
flowdeck/app/templates/accounts.html
T
bruno afbc236cc2
FlowDeck CI / docker (push) Successful in 1m51s
FlowDeck CI / lint (push) Successful in 1m57s
FlowDeck CI / test (push) Successful in 15m16s
fix: navigation partielle Alpine sans course + scripts de page idempotents (v7.45.2)
- app.js : x-ignore sur .main-wrapper au swap fdLoad (nœud remplacé
  uniquement), démontage unique quand tous les <script src> ont exécuté
  (load/error + filet 4s) via Alpine.initTree idempotent (_x_marker) —
  fini les cascades « Undefined variable » (éditeur) et « reading 'has' »
  ($store.fdCtx) : la zone s'initialisait avant composants et stores.
- 5 scripts de page gardés contre la ré-exécution (SyntaxError
  « Identifier 'LW' has already been declared » à la 2e visite
  partielle) : local_workspace, board, settings, database_table,
  page_editor_realtime.
- if (window.Alpine) → Alpine.data immédiat sinon listener alpine:init
  (déjà passé sur swap) : 8 scripts + 7 templates inline.
- app.css : purge des 7 blocs @font-face Inter orphelins (fichiers
  inexistants → 302 HTML → « Failed to decode downloaded font »).
- Gates : e2e/regression_editor_mount + e2e/regression_partial_nav
  (7 pages x complet/1er/2e passage) + regression_logout_dnd + smoke
  = 6/6 verts sur l'image rebuildée · pytest 1094 passed · ruff OK ·
  OpenAPI 7.45.2.
2026-10-02 21:29:46 -04:00

139 lines
5.6 KiB
HTML

{% extends "base.html" %}
{% block page_icon %}{{ fd_icon("user",18) }}{% endblock %}
{% block page_title %}Accounts{% endblock %}
{% block title_prefix %}Accounts{% endblock %}
{% block content %}
<div class="page-title-area">
<div class="page-title">
<span class="page-icon-lg">{{ fd_icon("user",24) }}</span>
<h1>Account Management</h1>
</div>
</div>
<div style="padding: 0 24px; max-width: 900px;" x-data="accountsData()">
<!-- Current user profile -->
<div style="background:var(--bg-secondary); border-radius:8px; padding:20px; margin-bottom:24px;">
<h3 style="margin-bottom:16px;">Your Profile</h3>
<div style="display:flex; gap:16px; align-items:flex-start;">
<div class="workspace-avatar" style="width:48px; height:48px; font-size:20px;">
{{ user.login[0] if user else 'B' }}
</div>
<div style="flex:1;">
<div style="display:flex; gap:12px; margin-bottom:12px;">
<div style="flex:1;">
<label style="font-size:12px; color:var(--text-dim);">Username</label>
<input type="text" value="{{ user.login }}" disabled
style="width:100%; background:var(--bg-tertiary); border:1px solid var(--border); border-radius:6px;
padding:8px; color:var(--text-dim); font-size:14px;">
</div>
<div style="flex:1;">
<label style="font-size:12px; color:var(--text-dim);">Display name</label>
<input type="text" x-model="profile.full_name"
style="width:100%; background:var(--bg-secondary); border:1px solid var(--border); border-radius:6px;
padding:8px; color:var(--text-primary); font-size:14px; outline:none;">
</div>
</div>
<div style="margin-bottom:12px;">
<label style="font-size:12px; color:var(--text-dim);">Email</label>
<input type="text" x-model="profile.email"
style="width:100%; background:var(--bg-secondary); border:1px solid var(--border); border-radius:6px;
padding:8px; color:var(--text-primary); font-size:14px; outline:none;">
</div>
<button class="btn-new" @click="saveProfile()">Save Profile</button>
<span x-show="saved" style="color:var(--green); font-size:13px; margin-left:8px;">✓ Saved</span>
</div>
</div>
</div>
<!-- Connected accounts -->
<div style="background:var(--bg-secondary); border-radius:8px; padding:20px; margin-bottom:24px;">
<h3 style="margin-bottom:12px;">Connected Accounts</h3>
<div style="display:flex; align-items:center; gap:12px; padding:12px; border:1px solid var(--border); border-radius:6px;">
<span style="font-size:20px;">{{ fd_icon("link",20) }}</span>
<div style="flex:1;">
<div style="font-weight:600;">Gitea</div>
<div style="font-size:12px; color:var(--text-dim);">
{% if user %}Connected as {{ user.login }}{% else %}Not connected{% endif %}
</div>
</div>
{% if user %}
<a href="/auth/logout" class="toolbar-btn" style="color:var(--red);">Disconnect</a>
{% else %}
<a href="/auth/login" class="btn-new">Connect Gitea</a>
{% endif %}
</div>
</div>
<!-- Users in workspace -->
<div style="background:var(--bg-secondary); border-radius:8px; padding:20px;">
<h3 style="margin-bottom:12px;">Workspace Members</h3>
<div style="font-size:13px; color:var(--text-dim); margin-bottom:12px;">
Members are managed through Gitea. New users connect via OAuth and appear here automatically.
</div>
<table class="data-table" style="width:100%;">
<thead>
<tr>
<th>User</th>
<th>Email</th>
<th>Joined</th>
<th>Role</th>
</tr>
</thead>
<tbody>
{% for u in users %}
<tr>
<td>
<div style="display:flex; align-items:center; gap:8px;">
<div class="card-avatar" style="width:24px; height:24px; font-size:11px;">{{ u.login[0] }}</div>
<span>{{ u.login }}</span>
</div>
</td>
<td>{{ u.email or '—' }}</td>
<td>{{ u.created_at[:10] if u.created_at else '—' }}</td>
<td>{% if u.is_admin %}Admin{% else %}Member{% endif %}</td>
</tr>
{% endfor %}
{% if not users %}
<tr><td colspan="4" style="text-align:center; color:var(--text-dim); padding:24px;">No members yet</td></tr>
{% endif %}
</tbody>
</table>
</div>
</div>
{% endblock %}
{% block scripts %}
<script data-cfasync="false" nonce="{{ csp_nonce() }}">
// A20 phase 3 : registre Alpine.data (le build CSP ne résout pas les
// globales window — probe « Undefined variable: accountsData »).
if (window.Alpine) { Alpine.data('accountsData', accountsData); }
else document.addEventListener('alpine:init', function () { Alpine.data('accountsData', accountsData); });
function accountsData() {
return {
profile: { full_name: '', email: '' },
saved: false,
async init() {
try {
const r = await fetch('/api/users/me');
const data = await r.json();
this.profile = { full_name: data.full_name || '', email: data.email || '' };
} catch(e) {}
},
saveProfile() {
const csrf = getCsrf();;
const token = csrf;
fetch(`/api/users/me?full_name=${encodeURIComponent(this.profile.full_name)}&email=${encodeURIComponent(this.profile.email)}`, {
method: 'PUT', headers: { 'X-CSRF-Token': token }
}).then(() => {
this.saved = true;
setTimeout(() => this.saved = false, 3000);
});
}
};
}
</script>
{% endblock %}