feat(signup): unify cadence UX across accept-invite, join, and account
Extract shared SignupFlowOverlay component. Static "Monthly Contribution" label on all three contribution inputs (was misleadingly dynamic). "Per Year"/"Per Month" toggle copy; Per Year default on accept-invite, Per Month default on join. Live billing-summary card on both signup flows. Welcome-heading on dashboard via ?welcome=1 for new signups. $0-member polish on account page (hide payment-history + Solidarity Fund prompts). State-aware contribution-change hint. Invite accept now creates Helcim customer and sets auth cookie server-side for both free and paid branches. Pre-registrant invite + /join signup flows manually verified against Cleo Nguyen preReg and $0-$50 variants.
This commit is contained in:
parent
493be2f3bc
commit
a80728f0a8
10 changed files with 553 additions and 321 deletions
191
app/components/SignupFlowOverlay.vue
Normal file
191
app/components/SignupFlowOverlay.vue
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="state !== 'idle'" class="signup-flow-overlay">
|
||||
<div class="signup-flow-card">
|
||||
<div class="signup-flow-step">{{ stepLabel }}</div>
|
||||
|
||||
<template v-if="isProgress">
|
||||
<h2 class="signup-flow-heading">{{ progressHeading }}</h2>
|
||||
<p class="signup-flow-body">
|
||||
Please don't close this window. This usually takes a few seconds.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template v-if="state === 'success'">
|
||||
<h2 class="signup-flow-heading">Welcome to Ghost Guild!</h2>
|
||||
<DashedBox :hoverable="false">
|
||||
<div class="section-label" style="margin-bottom: 12px">
|
||||
Membership Details
|
||||
</div>
|
||||
<dl class="details-list">
|
||||
<div class="details-row">
|
||||
<dt>Name</dt><dd>{{ summary?.name }}</dd>
|
||||
</div>
|
||||
<div class="details-row">
|
||||
<dt>Email</dt><dd>{{ summary?.email }}</dd>
|
||||
</div>
|
||||
<div class="details-row">
|
||||
<dt>Circle</dt><dd class="capitalize">{{ summary?.circle }}</dd>
|
||||
</div>
|
||||
<div class="details-row">
|
||||
<dt>Contribution</dt><dd>{{ summary?.contribution }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</DashedBox>
|
||||
<p class="signup-flow-body" style="margin-top: 16px">
|
||||
We've sent a confirmation email to {{ summary?.email }}. Redirecting
|
||||
you to your dashboard...
|
||||
</p>
|
||||
<div class="button-row" style="margin-top: 20px">
|
||||
<NuxtLink :to="dashboardHref" class="btn btn-primary">
|
||||
Go to Dashboard Now
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="state === 'error'">
|
||||
<h2 class="signup-flow-heading">We couldn't complete your signup</h2>
|
||||
<div v-if="errorMessage" class="error-box">
|
||||
{{ errorMessage }}
|
||||
</div>
|
||||
<div class="button-row" style="margin-top: 20px">
|
||||
<button class="btn" @click="$emit('close')">
|
||||
Back to form
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
state: { type: String, required: true },
|
||||
summary: { type: Object, default: null },
|
||||
errorMessage: { type: String, default: "" },
|
||||
dashboardHref: { type: String, default: "/welcome" },
|
||||
});
|
||||
|
||||
defineEmits(["close"]);
|
||||
|
||||
const PROGRESS_STATES = [
|
||||
"creating-customer",
|
||||
"opening-payment",
|
||||
"processing-payment",
|
||||
"creating-subscription",
|
||||
];
|
||||
|
||||
const isProgress = computed(() => PROGRESS_STATES.includes(props.state));
|
||||
|
||||
const progressHeading = computed(() => {
|
||||
switch (props.state) {
|
||||
case "creating-customer": return "Creating your account...";
|
||||
case "opening-payment": return "Opening secure payment...";
|
||||
case "processing-payment": return "Confirming your card...";
|
||||
case "creating-subscription": return "Activating your membership...";
|
||||
default: return "";
|
||||
}
|
||||
});
|
||||
|
||||
const stepLabel = computed(() => {
|
||||
switch (props.state) {
|
||||
case "creating-customer":
|
||||
case "opening-payment":
|
||||
return "Step 2 of 3 — Payment";
|
||||
case "processing-payment":
|
||||
case "creating-subscription":
|
||||
return "Step 2 of 3 — Finalizing";
|
||||
case "success":
|
||||
return "Step 3 of 3 — Welcome";
|
||||
case "error":
|
||||
return "Something went wrong";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.signup-flow-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 50;
|
||||
background: rgba(42, 32, 21, 0.72);
|
||||
backdrop-filter: blur(4px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.signup-flow-card {
|
||||
background: var(--bg);
|
||||
border: 1px dashed var(--border);
|
||||
padding: 32px;
|
||||
max-width: 520px;
|
||||
width: 100%;
|
||||
max-height: calc(100vh - 48px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.signup-flow-step {
|
||||
font-family: var(--font-body);
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-dim);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.signup-flow-heading {
|
||||
font-family: var(--font-display);
|
||||
font-size: 1.5rem;
|
||||
color: var(--text-bright);
|
||||
margin: 0 0 16px;
|
||||
}
|
||||
|
||||
.signup-flow-body {
|
||||
font-family: var(--font-body);
|
||||
color: var(--text);
|
||||
line-height: 1.5;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.details-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.details-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.details-row dt {
|
||||
color: var(--text-faint);
|
||||
}
|
||||
|
||||
.details-row dd {
|
||||
color: var(--text-bright);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.error-box {
|
||||
border: 1px dashed var(--ember);
|
||||
color: var(--ember);
|
||||
padding: 12px 16px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue