Tests, UX improvements.

This commit is contained in:
Jennie Robinson Faber 2026-04-05 14:25:29 +01:00
parent 4e6f5d36b8
commit 0ae18f495e
63 changed files with 1384 additions and 2330 deletions

View file

@ -19,197 +19,187 @@
/>
<!-- CONTENT AREA WITH EVENTS SIDEBAR -->
<div class="content-area">
<div class="page-content">
<div class="account-columns">
<!-- LEFT COLUMN: Membership Status & Email -->
<div class="account-col-left">
<section class="account-section">
<div class="account-col-inset">
<div class="section-label">Current Membership</div>
<SidebarLayout>
<div class="account-columns">
<!-- LEFT COLUMN: Membership Status & Email -->
<div class="account-col-left">
<section class="account-section">
<div class="account-col-inset">
<div class="section-label">Current Membership</div>
<div class="membership-card">
<div class="membership-row">
<span class="membership-k">Status</span>
<span class="membership-v status-v">
<span
class="status-dot"
:class="memberData.status || 'active'"
></span>
<span>{{
formatStatus(memberData.status || "active")
}}</span>
</span>
</div>
<div class="membership-row">
<span class="membership-k">Circle</span>
<div class="membership-card">
<div class="membership-row">
<span class="membership-k">Status</span>
<span class="membership-v status-v">
<span
class="membership-v"
:style="{
color: `var(--c-${memberData.circle || 'community'})`,
}"
>
{{
memberData.circle
? capitalise(memberData.circle)
: "Community"
}}
</span>
</div>
<div class="membership-row">
<span class="membership-k">Contribution</span>
<span class="membership-v"
>${{ memberData.contributionTier || 0 }} / month</span
>
</div>
<div class="membership-row">
<span class="membership-k">Member since</span>
<span class="membership-v">{{
formatMemberSince(memberData.createdAt)
class="status-dot"
:class="memberData.status || 'active'"
></span>
<span>{{
formatStatus(memberData.status || "active")
}}</span>
</div>
</span>
</div>
<div class="membership-row">
<span class="membership-k">Circle</span>
<span
class="membership-v"
:style="{
color: `var(--c-${memberData.circle || 'community'})`,
}"
>
{{
memberData.circle
? capitalise(memberData.circle)
: "Community"
}}
</span>
</div>
<div class="membership-row">
<span class="membership-k">Contribution</span>
<span class="membership-v"
>${{ memberData.contributionTier || 0 }} / month</span
>
</div>
<div class="membership-row">
<span class="membership-k">Member since</span>
<span class="membership-v">{{
formatMemberSince(memberData.createdAt)
}}</span>
</div>
</div>
</section>
</div>
</section>
<section class="account-section">
<div class="account-col-inset">
<div class="section-label">Email</div>
<section class="account-section">
<div class="account-col-inset">
<div class="section-label">Email</div>
<div v-if="!showEmailEdit" class="email-display">
<span class="email-value">{{ memberData.email }}</span>
<div v-if="!showEmailEdit" class="email-display">
<span class="email-value">{{ memberData.email }}</span>
<button class="btn btn-inline" @click="showEmailEdit = true">
Change
</button>
</div>
<div v-else class="email-edit">
<div class="field">
<label>New email address</label>
<input
type="email"
v-model="newEmail"
placeholder="you@example.com"
@keydown.enter="handleUpdateEmail"
@keydown.escape="cancelEmailEdit"
autofocus
/>
</div>
<div class="email-edit-actions">
<button
class="btn btn-inline"
@click="showEmailEdit = true"
class="btn btn-primary"
@click="handleUpdateEmail"
:disabled="isUpdatingEmail || !newEmail.trim()"
>
Change
{{ isUpdatingEmail ? "Saving…" : "Save" }}
</button>
<button
class="btn"
@click="cancelEmailEdit"
:disabled="isUpdatingEmail"
>
Cancel
</button>
</div>
<div v-else class="email-edit">
<div class="field">
<label>New email address</label>
<input
type="email"
v-model="newEmail"
placeholder="you@example.com"
@keydown.enter="handleUpdateEmail"
@keydown.escape="cancelEmailEdit"
autofocus
/>
</div>
<div class="email-edit-actions">
<button
class="btn btn-primary"
@click="handleUpdateEmail"
:disabled="isUpdatingEmail || !newEmail.trim()"
>
{{ isUpdatingEmail ? "Saving…" : "Save" }}
</button>
<button
class="btn"
@click="cancelEmailEdit"
:disabled="isUpdatingEmail"
>
Cancel
</button>
</div>
</div>
<div class="email-hint">
Used for login magic links and notifications
</div>
</div>
</section>
<section class="account-section account-section--danger">
<div class="account-col-inset">
<div class="section-label danger">Danger Zone</div>
<div class="danger-zone">
<p>
Cancelling your membership will immediately revoke access
to member-only resources, events, and the Slack workspace.
<strong>This action cannot be easily undone.</strong>
<div class="email-hint">
Used for login magic links and notifications
</div>
</div>
</section>
<section class="account-section account-section--danger">
<div class="account-col-inset">
<div class="section-label danger">Danger Zone</div>
<div class="danger-zone">
<p>
Cancelling your membership will immediately revoke access to
member-only resources, events, and the Slack workspace.
<strong>This action cannot be easily undone.</strong>
</p>
<div v-if="showCancelConfirm" class="cancel-confirm">
<p class="cancel-confirm-prompt">
Are you sure? This cannot be easily undone.
</p>
<div v-if="showCancelConfirm" class="cancel-confirm">
<p class="cancel-confirm-prompt">
Are you sure? This cannot be easily undone.
</p>
<div class="cancel-confirm-actions">
<button
class="btn btn-danger"
@click="confirmCancelMembership"
:disabled="isCancelling"
>
{{ isCancelling ? "Cancelling…" : "Yes, Cancel" }}
</button>
<button class="btn" @click="showCancelConfirm = false">
Nevermind
</button>
</div>
<div class="cancel-confirm-actions">
<button
class="btn btn-danger"
@click="confirmCancelMembership"
:disabled="isCancelling"
>
{{ isCancelling ? "Cancelling…" : "Yes, Cancel" }}
</button>
<button class="btn" @click="showCancelConfirm = false">
Nevermind
</button>
</div>
<button
v-else
class="btn btn-danger"
@click="handleCancelMembership"
:disabled="isCancelling"
>
Cancel Membership
</button>
</div>
</div>
</section>
</div>
<!-- RIGHT COLUMN: Change Contribution & Circle -->
<div class="account-col-right">
<section class="account-section">
<div class="account-col-inset">
<div class="section-label">Change Contribution</div>
<TierPicker v-model="selectedTier" :tiers="tiers" />
<div class="tier-hint">
Changes take effect on your next billing cycle
</div>
<button
class="btn btn-primary btn-section"
@click="handleUpdateTier"
:disabled="
selectedTier ===
Number(memberData.contributionTier || 0) || isUpdating
"
v-else
class="btn btn-danger"
@click="handleCancelMembership"
:disabled="isCancelling"
>
{{ isUpdating ? "Updating…" : "Update Contribution" }}
Cancel Membership
</button>
</div>
</section>
</div>
</section>
</div>
<section class="account-section">
<div class="account-col-inset">
<div class="section-label">Change Circle</div>
<!-- RIGHT COLUMN: Change Contribution & Circle -->
<div class="account-col-right">
<section class="account-section">
<div class="account-col-inset">
<div class="section-label">Change Contribution</div>
<CirclePicker
v-model="selectedCircle"
:circles="circleOptions"
/>
<button
class="btn btn-primary btn-section"
@click="handleUpdateCircle"
:disabled="
selectedCircle === memberData.circle || isUpdating
"
>
{{ isUpdating ? "Updating…" : "Update Circle" }}
</button>
<TierPicker v-model="selectedTier" :tiers="tiers" />
<div class="tier-hint">
Changes take effect on your next billing cycle
</div>
</section>
</div>
<button
class="btn btn-primary btn-section"
@click="handleUpdateTier"
:disabled="
selectedTier === Number(memberData.contributionTier || 0) ||
isUpdating
"
>
{{ isUpdating ? "Updating…" : "Update Contribution" }}
</button>
</div>
</section>
<section class="account-section">
<div class="account-col-inset">
<div class="section-label">Change Circle</div>
<CirclePicker
v-model="selectedCircle"
:circles="circleOptions"
/>
<button
class="btn btn-primary btn-section"
@click="handleUpdateCircle"
:disabled="selectedCircle === memberData.circle || isUpdating"
>
{{ isUpdating ? "Updating…" : "Update Circle" }}
</button>
</div>
</section>
</div>
</div>
<!-- EVENTS MINI SIDEBAR -->
<EventsMiniSidebar :events="upcomingEvents" />
</div>
</SidebarLayout>
</div>
</div>
</template>
@ -281,11 +271,6 @@ watchEffect(() => {
}
});
const { data: upcomingEvents } = await useFetch("/api/events", {
query: { limit: 3, upcoming: true },
default: () => [],
});
const formatMemberSince = (dateStr) => {
if (!dateStr) return "";
return new Date(dateStr).toLocaleDateString("en-US", {
@ -420,23 +405,6 @@ const confirmCancelMembership = async () => {
min-height: 0;
}
/* ---- CONTENT AREA ---- */
.content-area {
flex: 1;
display: grid;
grid-template-columns: 1fr 200px;
align-items: stretch;
min-height: 0;
}
.page-content {
min-width: 0;
align-self: stretch;
height: 100%;
min-height: 100%;
display: flex;
flex-direction: column;
}
/* ---- TWO-COLUMN LAYOUT ---- */
.account-columns {
flex: 1;
@ -644,9 +612,6 @@ const confirmCancelMembership = async () => {
/* ---- RESPONSIVE ---- */
@media (max-width: 1024px) {
.content-area {
grid-template-columns: 1fr;
}
.account-columns {
grid-template-columns: 1fr;
}