refactor(ui): the settings family migrates; two colour bugs and a dead class
CI & Build / Python lint (push) Successful in 7s
CI & Build / Plugin hooks (push) Successful in 36s
CI & Build / TypeScript typecheck (push) Successful in 49s
CI & Build / integration (push) Successful in 2m16s
CI & Build / Python tests (push) Successful in 2m51s
CI & Build / Build & push image (push) Successful in 1m15s

SettingsView and UserManagementView had near-identical button vocabularies —
btn-delete, btn-cancel-delete, btn-confirm-delete, btn-toggle/-open/-close —
defined separately in each. Parallel duplication (#2278's shape), and it had
already diverged twice:

- .btn-confirm-delete used --color-danger in UserManagement and
  --color-action-destructive in Settings. Those are different colours on
  purpose: the house style keeps error (something went wrong) distinct from
  destructive (something is about to). A delete confirmation is destructive.
  UserManagement was showing the error colour for a button nothing had failed
  in yet.

- .btn-remove-slot's hover reached for --color-danger for the same reason, and
  is the same correction. It turned out to be dead anyway — style rules only,
  no template reference anywhere in the app — so it is gone.

.btn-danger-outline was defined TWICE inside SettingsView, at 0.4rem 0.9rem and
0.45rem 1rem. One file, one class, two geometries, ~1200 lines apart. That is
the clearest single argument for this whole task that I have found: the drift
does not need two files, only enough distance that nobody sees both at once.

The registration toggle keeps .btn-toggle-close, and only that. It is bound
dynamically (:class="registrationOpen ? … : …"), so a name-based scan reads it
as unused — checked before deleting. .btn-toggle-open went, because btn-primary
now says the same thing; the close state stays because it must NOT read as the
primary action it sits on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs
This commit is contained in:
2026-08-01 22:48:35 -04:00
co-authored by Claude Opus 5
parent 3d6931b838
commit 3fc693443e
2 changed files with 34 additions and 289 deletions
+26 -197
View File
@@ -1165,7 +1165,7 @@ function formatUserDate(iso: string): string {
<p class="field-hint">Click Detect to auto-fill from your browser.</p> <p class="field-hint">Click Detect to auto-fill from your browser.</p>
</div> </div>
<div class="actions"> <div class="actions">
<button class="btn-save" @click="saveTimezone" :disabled="savingTimezone"> <button class="btn-primary" @click="saveTimezone" :disabled="savingTimezone">
{{ savingTimezone ? 'Saving…' : 'Save' }} {{ savingTimezone ? 'Saving…' : 'Save' }}
</button> </button>
<span v-if="timezoneSaved" class="saved-msg">Saved!</span> <span v-if="timezoneSaved" class="saved-msg">Saved!</span>
@@ -1190,7 +1190,7 @@ function formatUserDate(iso: string): string {
<p class="field-hint">Set to <strong>0</strong> to keep deleted items forever (never auto-purge).</p> <p class="field-hint">Set to <strong>0</strong> to keep deleted items forever (never auto-purge).</p>
</div> </div>
<div class="actions"> <div class="actions">
<button class="btn-save" @click="saveRetention" :disabled="savingRetention"> <button class="btn-primary" @click="saveRetention" :disabled="savingRetention">
{{ savingRetention ? 'Saving' : 'Save' }} {{ savingRetention ? 'Saving' : 'Save' }}
</button> </button>
<span v-if="retentionSaved" class="saved-msg">Saved!</span> <span v-if="retentionSaved" class="saved-msg">Saved!</span>
@@ -1313,7 +1313,7 @@ function formatUserDate(iso: string): string {
</p> </p>
</div> </div>
<div class="actions"> <div class="actions">
<button class="btn-save" @click="saveKbInject" :disabled="savingKbInject"> <button class="btn-primary" @click="saveKbInject" :disabled="savingKbInject">
{{ savingKbInject ? 'Saving' : 'Save' }} {{ savingKbInject ? 'Saving' : 'Save' }}
</button> </button>
<span v-if="kbInjectSaved" class="saved-msg">Saved!</span> <span v-if="kbInjectSaved" class="saved-msg">Saved!</span>
@@ -1361,7 +1361,7 @@ function formatUserDate(iso: string): string {
</div> </div>
<div class="actions"> <div class="actions">
<button <button
class="btn-save" class="btn-primary"
@click="changeEmail" @click="changeEmail"
:disabled="changingEmail || !emailPassword" :disabled="changingEmail || !emailPassword"
> >
@@ -1409,7 +1409,7 @@ function formatUserDate(iso: string): string {
</div> </div>
<div class="actions"> <div class="actions">
<button <button
class="btn-save" class="btn-primary"
@click="changePassword" @click="changePassword"
:disabled="changingPassword || !currentPassword || newPassword.length < 8 || newPassword !== confirmNewPassword" :disabled="changingPassword || !currentPassword || newPassword.length < 8 || newPassword !== confirmNewPassword"
> >
@@ -1465,7 +1465,7 @@ function formatUserDate(iso: string): string {
</div> </div>
</div> </div>
<div class="actions"> <div class="actions">
<button class="btn-save" @click="saveProfile" :disabled="profileSaving">{{ profileSaving ? 'Saving' : 'Save' }}</button> <button class="btn-primary" @click="saveProfile" :disabled="profileSaving">{{ profileSaving ? 'Saving' : 'Save' }}</button>
<span v-if="profileSaved" class="saved-msg">Saved!</span> <span v-if="profileSaved" class="saved-msg">Saved!</span>
</div> </div>
</section> </section>
@@ -1491,7 +1491,7 @@ function formatUserDate(iso: string): string {
</div> </div>
</div> </div>
<div class="actions"> <div class="actions">
<button class="btn-save" @click="saveProfile" :disabled="profileSaving">{{ profileSaving ? 'Saving' : 'Save' }}</button> <button class="btn-primary" @click="saveProfile" :disabled="profileSaving">{{ profileSaving ? 'Saving' : 'Save' }}</button>
<span v-if="profileSaved" class="saved-msg">Saved!</span> <span v-if="profileSaved" class="saved-msg">Saved!</span>
</div> </div>
</section> </section>
@@ -1501,7 +1501,7 @@ function formatUserDate(iso: string): string {
<p class="section-desc">Topics you care about — used to personalise the journal's daily prep and chat responses.</p> <p class="section-desc">Topics you care about — used to personalise the journal's daily prep and chat responses.</p>
<TagInput v-model="profile.interests" placeholder="Add an interest…" :fetchTags="emptyTagsFetch" /> <TagInput v-model="profile.interests" placeholder="Add an interest…" :fetchTags="emptyTagsFetch" />
<div class="actions"> <div class="actions">
<button class="btn-save" @click="saveProfile" :disabled="profileSaving">{{ profileSaving ? 'Saving…' : 'Save' }}</button> <button class="btn-primary" @click="saveProfile" :disabled="profileSaving">{{ profileSaving ? 'Saving…' : 'Save' }}</button>
<span v-if="profileSaved" class="saved-msg">Saved!</span> <span v-if="profileSaved" class="saved-msg">Saved!</span>
</div> </div>
</section> </section>
@@ -1533,7 +1533,7 @@ function formatUserDate(iso: string): string {
</div> </div>
</div> </div>
<div class="actions"> <div class="actions">
<button class="btn-save" @click="saveProfile" :disabled="profileSaving">{{ profileSaving ? 'Saving…' : 'Save' }}</button> <button class="btn-primary" @click="saveProfile" :disabled="profileSaving">{{ profileSaving ? 'Saving…' : 'Save' }}</button>
<span v-if="profileSaved" class="saved-msg">Saved!</span> <span v-if="profileSaved" class="saved-msg">Saved!</span>
</div> </div>
</section> </section>
@@ -1562,7 +1562,7 @@ function formatUserDate(iso: string): string {
<p class="field-hint">Emails for logins, logouts, and password changes.</p> <p class="field-hint">Emails for logins, logouts, and password changes.</p>
</div> </div>
<div class="actions"> <div class="actions">
<button class="btn-save" @click="saveNotifications" :disabled="savingNotifications"> <button class="btn-primary" @click="saveNotifications" :disabled="savingNotifications">
{{ savingNotifications ? "Saving..." : "Save" }} {{ savingNotifications ? "Saving..." : "Save" }}
</button> </button>
<span v-if="notificationsSaved" class="saved-msg">Saved!</span> <span v-if="notificationsSaved" class="saved-msg">Saved!</span>
@@ -1589,7 +1589,7 @@ function formatUserDate(iso: string): string {
placeholder="Enter a search query..." placeholder="Enter a search query..."
@keydown="onSearchKeydown" @keydown="onSearchKeydown"
/> />
<button class="btn-save" @click="testSearch" :disabled="searchLoading || !searchQuery.trim()"> <button class="btn-primary" @click="testSearch" :disabled="searchLoading || !searchQuery.trim()">
{{ searchLoading ? "Searching..." : "Search" }} {{ searchLoading ? "Searching..." : "Search" }}
</button> </button>
</div> </div>
@@ -1891,7 +1891,7 @@ function formatUserDate(iso: string): string {
/> />
</div> </div>
<div class="actions"> <div class="actions">
<button class="btn-save" @click="saveBaseUrl" :disabled="savingBaseUrl"> <button class="btn-primary" @click="saveBaseUrl" :disabled="savingBaseUrl">
{{ savingBaseUrl ? "Saving..." : "Save" }} {{ savingBaseUrl ? "Saving..." : "Save" }}
</button> </button>
<span v-if="baseUrlSaved" class="saved-msg">Saved!</span> <span v-if="baseUrlSaved" class="saved-msg">Saved!</span>
@@ -1916,7 +1916,7 @@ function formatUserDate(iso: string): string {
/> />
</div> </div>
<div class="actions"> <div class="actions">
<button class="btn-save" @click="saveMarketplaceUrl" :disabled="savingMarketplaceUrl"> <button class="btn-primary" @click="saveMarketplaceUrl" :disabled="savingMarketplaceUrl">
{{ savingMarketplaceUrl ? "Saving..." : "Save" }} {{ savingMarketplaceUrl ? "Saving..." : "Save" }}
</button> </button>
<span v-if="marketplaceUrlSaved" class="saved-msg">Saved!</span> <span v-if="marketplaceUrlSaved" class="saved-msg">Saved!</span>
@@ -1946,10 +1946,10 @@ function formatUserDate(iso: string): string {
</select> </select>
</div> </div>
<div class="actions"> <div class="actions">
<button class="btn-save" @click="saveDbMaintenance" :disabled="savingDbMaint"> <button class="btn-primary" @click="saveDbMaintenance" :disabled="savingDbMaint">
{{ savingDbMaint ? "Saving..." : "Save" }} {{ savingDbMaint ? "Saving..." : "Save" }}
</button> </button>
<button class="btn-save btn-secondary" @click="runDbMaintenanceNow" :disabled="runningDbMaint"> <button class="btn-secondary" @click="runDbMaintenanceNow" :disabled="runningDbMaint">
{{ runningDbMaint ? "Running..." : "Run now" }} {{ runningDbMaint ? "Running..." : "Run now" }}
</button> </button>
<span v-if="dbMaintSaved" class="saved-msg">Saved!</span> <span v-if="dbMaintSaved" class="saved-msg">Saved!</span>
@@ -2040,7 +2040,7 @@ function formatUserDate(iso: string): string {
<p class="field-hint">Recommended for port 587. Implicit TLS is used automatically for port 465.</p> <p class="field-hint">Recommended for port 587. Implicit TLS is used automatically for port 465.</p>
</div> </div>
<div class="actions" style="margin-bottom: 1.25rem;"> <div class="actions" style="margin-bottom: 1.25rem;">
<button class="btn-save" @click="saveSmtp" :disabled="savingSmtp"> <button class="btn-primary" @click="saveSmtp" :disabled="savingSmtp">
{{ savingSmtp ? "Saving..." : "Save SMTP Settings" }} {{ savingSmtp ? "Saving..." : "Save SMTP Settings" }}
</button> </button>
<span v-if="smtpSaved" class="saved-msg">Saved!</span> <span v-if="smtpSaved" class="saved-msg">Saved!</span>
@@ -2054,7 +2054,7 @@ function formatUserDate(iso: string): string {
placeholder="test@example.com" placeholder="test@example.com"
class="input" class="input"
/> />
<button class="btn-save" @click="sendTestEmail" :disabled="sendingTest || !testRecipient.trim()"> <button class="btn-primary" @click="sendTestEmail" :disabled="sendingTest || !testRecipient.trim()">
{{ sendingTest ? "Sending..." : "Send Test" }} {{ sendingTest ? "Sending..." : "Send Test" }}
</button> </button>
</div> </div>
@@ -2079,7 +2079,7 @@ function formatUserDate(iso: string): string {
<p class="field-hint">When closed, new users can only be added by an administrator.</p> <p class="field-hint">When closed, new users can only be added by an administrator.</p>
</div> </div>
<button <button
class="btn-toggle" class="btn-primary btn-toggle"
:class="registrationOpen ? 'btn-toggle-close' : 'btn-toggle-open'" :class="registrationOpen ? 'btn-toggle-close' : 'btn-toggle-open'"
@click="toggleRegistration" @click="toggleRegistration"
:disabled="toggling" :disabled="toggling"
@@ -2100,7 +2100,7 @@ function formatUserDate(iso: string): string {
required required
:disabled="sendingInvite" :disabled="sendingInvite"
/> />
<button type="submit" class="btn-save" :disabled="sendingInvite || !inviteEmail.trim()"> <button type="submit" class="btn-primary" :disabled="sendingInvite || !inviteEmail.trim()">
{{ sendingInvite ? "Sending..." : "Send Invite" }} {{ sendingInvite ? "Sending..." : "Send Invite" }}
</button> </button>
</form> </form>
@@ -2122,7 +2122,7 @@ function formatUserDate(iso: string): string {
<td class="hide-mobile cell-date">{{ formatUserDate(inv.created_at) }}</td> <td class="hide-mobile cell-date">{{ formatUserDate(inv.created_at) }}</td>
<td class="hide-mobile cell-date">{{ formatUserDate(inv.expires_at) }}</td> <td class="hide-mobile cell-date">{{ formatUserDate(inv.expires_at) }}</td>
<td class="cell-actions"> <td class="cell-actions">
<button class="btn-delete" @click="revokeInvitation(inv.id)" :disabled="revokingId !== null"> <button class="btn-ghost btn-compact" @click="revokeInvitation(inv.id)" :disabled="revokingId !== null">
{{ revokingId === inv.id ? "Revoking..." : "Revoke" }} {{ revokingId === inv.id ? "Revoking..." : "Revoke" }}
</button> </button>
</td> </td>
@@ -2161,13 +2161,13 @@ function formatUserDate(iso: string): string {
<span class="you-label">You</span> <span class="you-label">You</span>
</template> </template>
<template v-else-if="confirmDeleteId === u.id"> <template v-else-if="confirmDeleteId === u.id">
<button class="btn-confirm-delete" @click="confirmDelete(u.id)" :disabled="deleting !== null"> <button class="btn-danger btn-compact" @click="confirmDelete(u.id)" :disabled="deleting !== null">
{{ deleting === u.id ? "Deleting..." : "Confirm" }} {{ deleting === u.id ? "Deleting..." : "Confirm" }}
</button> </button>
<button class="btn-cancel-delete" @click="cancelDelete">Cancel</button> <button class="btn-ghost btn-compact" @click="cancelDelete">Cancel</button>
</template> </template>
<template v-else> <template v-else>
<button class="btn-delete" @click="confirmDelete(u.id)" :disabled="deleting !== null">Delete</button> <button class="btn-ghost btn-compact" @click="confirmDelete(u.id)" :disabled="deleting !== null">Delete</button>
</template> </template>
</td> </td>
</tr> </tr>
@@ -2305,10 +2305,10 @@ function formatUserDate(iso: string): string {
<span v-if="g.description" class="group-desc">{{ g.description }}</span> <span v-if="g.description" class="group-desc">{{ g.description }}</span>
</div> </div>
<div class="group-card-actions"> <div class="group-card-actions">
<button class="btn-sm" @click="toggleGroupExpand(g)"> <button class="btn-ghost btn-compact" @click="toggleGroupExpand(g)">
{{ expandedGroupId === g.id ? 'Collapse' : 'Manage' }} {{ expandedGroupId === g.id ? 'Collapse' : 'Manage' }}
</button> </button>
<button class="btn-sm btn-danger-sm" @click="deleteGroupConfirm(g)">Delete</button> <button class="btn-danger-outline btn-compact" @click="deleteGroupConfirm(g)">Delete</button>
</div> </div>
</div> </div>
@@ -2338,7 +2338,7 @@ function formatUserDate(iso: string): string {
<li v-for="m in (groupMembers[g.id] || [])" :key="m.user_id" class="member-row"> <li v-for="m in (groupMembers[g.id] || [])" :key="m.user_id" class="member-row">
<span class="member-name">{{ m.username }}</span> <span class="member-name">{{ m.username }}</span>
<span class="member-role-badge" :class="`role-${m.role}`">{{ m.role }}</span> <span class="member-role-badge" :class="`role-${m.role}`">{{ m.role }}</span>
<button class="btn-sm btn-danger-sm" @click="removeMemberFromGroup(g.id, m.user_id)">Remove</button> <button class="btn-danger-outline btn-compact" @click="removeMemberFromGroup(g.id, m.user_id)">Remove</button>
</li> </li>
<li v-if="!(groupMembers[g.id]?.length)" class="members-empty">No members yet.</li> <li v-if="!(groupMembers[g.id]?.length)" class="members-empty">No members yet.</li>
</ul> </ul>
@@ -2564,54 +2564,6 @@ function formatUserDate(iso: string): string {
flex-wrap: wrap; flex-wrap: wrap;
} }
/* Save: Moss action-primary per Hybrid */ /* Save: Moss action-primary per Hybrid */
.btn-save {
padding: 0.4rem 0.9rem;
background: var(--color-action-primary);
color: var(--fs-text-on-action);
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.875rem;
font-family: inherit;
white-space: nowrap;
transition: background 0.15s;
}
.btn-save:disabled { opacity: 0.6; cursor: default; }
.btn-save:hover:not(:disabled) { background: var(--color-action-primary-hover); }
/* Danger outline (Invalidate sessions, Clear observations, etc.):
Oxblood action-destructive ghost — fills on hover */
.btn-danger-outline {
padding: 0.4rem 0.9rem;
background: none;
color: var(--color-action-destructive);
border: 1px solid var(--color-action-destructive);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.875rem;
font-family: inherit;
white-space: nowrap;
transition: background 0.15s, color 0.15s;
}
.btn-danger-outline:hover:not(:disabled) {
background: var(--color-action-destructive);
color: var(--fs-text-on-action);
}
.btn-danger-outline:disabled { opacity: 0.5; cursor: default; }
/* Filled destructive: Oxblood action-destructive */
/* Secondary: Bronze action-secondary — alternate paths (Detect, Test,
Refresh, Add slot, etc.). Outline form for visual lightness. */
/* DB maintenance last-run summary */
.db-maint-last { margin-top: 1rem; }
.db-maint-last-label {
display: block;
font-size: 0.8rem;
color: var(--color-text-muted);
margin-bottom: 0.4rem;
}
.db-maint-table-list { .db-maint-table-list {
list-style: none; list-style: none;
margin: 0; margin: 0;
@@ -2957,68 +2909,6 @@ function formatUserDate(iso: string): string {
} }
.you-label { font-size: 0.8rem; color: var(--color-text-muted); } .you-label { font-size: 0.8rem; color: var(--color-text-muted); }
/* Per-row delete (users / invitations / etc.): ghost → Oxblood on hover */ /* Per-row delete (users / invitations / etc.): ghost → Oxblood on hover */
.btn-delete {
padding: 0.25rem 0.6rem;
background: transparent;
color: var(--color-text-muted);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.8rem;
}
.btn-delete:hover:not(:disabled) { border-color: var(--color-action-destructive); color: var(--color-action-destructive); }
.btn-delete:disabled { opacity: 0.4; cursor: default; }
/* Two-stage destructive: Confirm = Oxblood filled, Cancel = Bronze ghost */
.btn-confirm-delete {
padding: 0.25rem 0.6rem;
background: var(--color-action-destructive);
color: var(--fs-text-on-action);
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.8rem;
font-weight: 500;
margin-right: 0.25rem;
transition: background 0.15s;
}
.btn-confirm-delete:hover:not(:disabled) { background: var(--color-action-destructive-hover); }
.btn-confirm-delete:disabled { opacity: 0.6; cursor: default; }
.btn-cancel-delete {
padding: 0.25rem 0.6rem;
background: transparent;
color: var(--color-text-muted);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.8rem;
}
.btn-cancel-delete:hover { color: var(--color-text); border-color: var(--color-text-muted); }
/* Toggle (Open/Close registration, etc.): Open = Moss, Close = Pewter ghost */
.btn-toggle {
padding: 0.45rem 1rem;
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.9rem;
font-weight: 500;
white-space: nowrap;
transition: background 0.15s;
}
.btn-toggle:disabled { opacity: 0.6; cursor: default; }
.btn-toggle-open { background: var(--color-action-primary); color: var(--fs-text-on-action); }
.btn-toggle-open:hover:not(:disabled) { background: var(--color-action-primary-hover); }
.btn-toggle-close {
background: var(--color-bg-secondary);
color: var(--color-text);
border: 1px solid var(--color-border);
}
.btn-toggle-close:hover:not(:disabled) { border-color: var(--color-warning); color: var(--color-warning); }
.loading-msg, .empty-msg {
text-align: center;
color: var(--color-text-muted);
font-size: 0.9rem;
padding: 1rem 0;
}
/* Logs panel */ /* Logs panel */
.stats-section { padding: 1rem 1.25rem; } .stats-section { padding: 1rem 1.25rem; }
@@ -3202,32 +3092,6 @@ function formatUserDate(iso: string): string {
flex-shrink: 0; flex-shrink: 0;
} }
.btn-sm {
padding: 0.25rem 0.6rem;
background: none;
border: 1px solid var(--color-border);
border-radius: 4px;
font-size: 0.78rem;
color: var(--color-text-secondary);
cursor: pointer;
font-family: inherit;
transition: border-color 0.15s, color 0.15s;
}
.btn-sm:hover { border-color: var(--color-primary); color: var(--color-primary); }
.btn-danger-sm:hover { border-color: var(--color-action-destructive); color: var(--color-action-destructive); }
.group-members-panel {
padding: 0.75rem 1rem 1rem;
border-top: 1px solid var(--color-border);
background: var(--color-surface);
}
.members-search {
display: flex;
gap: 0.5rem;
align-items: flex-start;
margin-bottom: 0.75rem;
}
.member-search-wrap { .member-search-wrap {
flex: 1; flex: 1;
@@ -3740,21 +3604,6 @@ function formatUserDate(iso: string): string {
text-align: right; text-align: right;
color: var(--color-text-secondary); color: var(--color-text-secondary);
} }
.btn-remove-slot {
background: none;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
color: var(--color-text-muted);
cursor: pointer;
font-size: 0.75rem;
padding: 0.2rem 0.45rem;
line-height: 1;
transition: color 0.15s, border-color 0.15s;
}
.btn-remove-slot:hover {
color: var(--color-danger, #e05555);
border-color: var(--color-danger, #e05555);
}
.blend-actions { .blend-actions {
margin-top: 0.25rem; margin-top: 0.25rem;
} }
@@ -3808,24 +3657,4 @@ function formatUserDate(iso: string): string {
color: var(--color-text-muted); color: var(--color-text-muted);
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
} }
.btn-danger-outline {
padding: 0.45rem 1rem;
background: none;
border: 1px solid var(--color-action-destructive);
border-radius: var(--radius-sm);
color: var(--color-action-destructive);
font-size: 0.9rem;
cursor: pointer;
font-family: inherit;
transition: background 0.15s, color 0.15s;
}
.btn-danger-outline:hover:not(:disabled) {
background: var(--color-action-destructive);
color: var(--fs-text-on-action);
}
.btn-danger-outline:disabled { opacity: 0.5; cursor: default; }
@keyframes va-dot-bounce {
0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
40% { transform: scale(1); opacity: 1; }
}
</style> </style>
+8 -92
View File
@@ -167,7 +167,7 @@ function formatDate(iso: string): string {
</p> </p>
</div> </div>
<button <button
class="btn-toggle" class="btn-primary btn-toggle"
:class="registrationOpen ? 'btn-toggle-close' : 'btn-toggle-open'" :class="registrationOpen ? 'btn-toggle-close' : 'btn-toggle-open'"
@click="toggleRegistration" @click="toggleRegistration"
:disabled="toggling" :disabled="toggling"
@@ -190,7 +190,7 @@ function formatDate(iso: string): string {
/> />
<button <button
type="submit" type="submit"
class="btn-invite" class="btn-primary"
:disabled="sendingInvite || !inviteEmail.trim()" :disabled="sendingInvite || !inviteEmail.trim()"
> >
{{ sendingInvite ? "Sending..." : "Send Invite" }} {{ sendingInvite ? "Sending..." : "Send Invite" }}
@@ -216,7 +216,7 @@ function formatDate(iso: string): string {
<td class="hide-mobile cell-date">{{ formatDate(inv.expires_at) }}</td> <td class="hide-mobile cell-date">{{ formatDate(inv.expires_at) }}</td>
<td class="cell-actions"> <td class="cell-actions">
<button <button
class="btn-delete" class="btn-ghost btn-compact"
@click="revokeInvitation(inv.id)" @click="revokeInvitation(inv.id)"
:disabled="revokingId !== null" :disabled="revokingId !== null"
> >
@@ -262,17 +262,17 @@ function formatDate(iso: string): string {
</template> </template>
<template v-else-if="confirmDeleteId === u.id"> <template v-else-if="confirmDeleteId === u.id">
<button <button
class="btn-confirm-delete" class="btn-danger btn-compact"
@click="confirmDelete(u.id)" @click="confirmDelete(u.id)"
:disabled="deleting !== null" :disabled="deleting !== null"
> >
{{ deleting === u.id ? "Deleting..." : "Confirm" }} {{ deleting === u.id ? "Deleting..." : "Confirm" }}
</button> </button>
<button class="btn-cancel-delete" @click="cancelDelete">Cancel</button> <button class="btn-ghost btn-compact" @click="cancelDelete">Cancel</button>
</template> </template>
<template v-else> <template v-else>
<button <button
class="btn-delete" class="btn-ghost btn-compact"
@click="confirmDelete(u.id)" @click="confirmDelete(u.id)"
:disabled="deleting !== null" :disabled="deleting !== null"
> >
@@ -328,24 +328,6 @@ function formatDate(iso: string): string {
outline: none; outline: none;
border-color: var(--color-primary); border-color: var(--color-primary);
} }
.btn-invite {
padding: 0.45rem 1rem;
background: var(--color-action-primary);
color: var(--fs-text-on-action);
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.9rem;
font-weight: var(--fs-weight-medium);
white-space: nowrap;
}
.btn-invite:disabled {
opacity: 0.6;
cursor: default;
}
.btn-invite:hover:not(:disabled) {
opacity: 0.9;
}
.invite-list { .invite-list {
margin-top: 1rem; margin-top: 1rem;
} }
@@ -380,26 +362,8 @@ function formatDate(iso: string): string {
font-size: 0.8rem; font-size: 0.8rem;
color: var(--color-text-muted); color: var(--color-text-muted);
} }
.btn-toggle { /* The one genuine override: 'close registration' must NOT read as the
padding: 0.45rem 1rem; primary action it sits on. Scoped, so it beats the shared variant. */
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.9rem;
font-weight: var(--fs-weight-medium);
white-space: nowrap;
}
.btn-toggle:disabled {
opacity: 0.6;
cursor: default;
}
.btn-toggle-open {
background: var(--color-action-primary);
color: var(--fs-text-on-action);
}
.btn-toggle-open:hover:not(:disabled) {
opacity: 0.9;
}
.btn-toggle-close { .btn-toggle-close {
background: var(--color-bg-secondary); background: var(--color-bg-secondary);
color: var(--color-text); color: var(--color-text);
@@ -478,54 +442,6 @@ function formatDate(iso: string): string {
font-size: 0.8rem; font-size: 0.8rem;
color: var(--color-text-muted); color: var(--color-text-muted);
} }
.btn-delete {
padding: 0.25rem 0.6rem;
background: transparent;
color: var(--color-text-muted);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.8rem;
}
.btn-delete:hover:not(:disabled) {
border-color: var(--color-danger);
color: var(--color-danger);
}
.btn-delete:disabled {
opacity: 0.4;
cursor: default;
}
.btn-confirm-delete {
padding: 0.25rem 0.6rem;
background: var(--color-danger);
color: var(--fs-text-on-action);
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.8rem;
font-weight: var(--fs-weight-medium);
margin-right: 0.25rem;
}
.btn-confirm-delete:hover:not(:disabled) {
filter: brightness(0.9);
}
.btn-confirm-delete:disabled {
opacity: 0.6;
cursor: default;
}
.btn-cancel-delete {
padding: 0.25rem 0.6rem;
background: transparent;
color: var(--color-text-muted);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.8rem;
}
.btn-cancel-delete:hover {
color: var(--color-text);
border-color: var(--color-text-muted);
}
@media (max-width: 768px) { @media (max-width: 768px) {
.registration-row { .registration-row {