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
48
scripts/mint-invite-link.cjs
Normal file
48
scripts/mint-invite-link.cjs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
require('dotenv').config()
|
||||
const mongoose = require('mongoose')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const { randomUUID } = require('crypto')
|
||||
|
||||
const BASE_URL = process.argv[2]
|
||||
const EMAIL = process.argv[3] || 'jennie+cleonguyen@machinemagic.co'
|
||||
|
||||
if (!BASE_URL) {
|
||||
console.error('Usage: node scripts/mint-invite-link.cjs <base-url> [email]')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const secret = process.env.NUXT_JWT_SECRET || process.env.JWT_SECRET
|
||||
if (!secret) {
|
||||
console.error('Missing NUXT_JWT_SECRET / JWT_SECRET in .env')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
;(async () => {
|
||||
await mongoose.connect(process.env.MONGODB_URI)
|
||||
const db = mongoose.connection.db
|
||||
|
||||
const preReg = await db.collection('preregistrations').findOne({ email: EMAIL })
|
||||
if (!preReg) {
|
||||
console.error(`No preregistration found for ${EMAIL}`)
|
||||
await mongoose.disconnect()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const jti = randomUUID()
|
||||
const token = jwt.sign(
|
||||
{ preRegistrationId: preReg._id.toString(), jti, type: 'prereg-invite' },
|
||||
secret,
|
||||
{ expiresIn: '48h' },
|
||||
)
|
||||
|
||||
await db.collection('preregistrations').updateOne(
|
||||
{ _id: preReg._id },
|
||||
{ $set: { magicLinkJti: jti, magicLinkJtiUsed: false, status: 'invited' } },
|
||||
)
|
||||
|
||||
const link = `${BASE_URL.replace(/\/$/, '')}/accept-invite#${token}`
|
||||
console.log('\nFresh invite link for', EMAIL, ':\n')
|
||||
console.log(link, '\n')
|
||||
|
||||
await mongoose.disconnect()
|
||||
})()
|
||||
34
scripts/reset-invite.cjs
Normal file
34
scripts/reset-invite.cjs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
require('dotenv').config()
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
;(async () => {
|
||||
await mongoose.connect(process.env.MONGODB_URI)
|
||||
const db = mongoose.connection.db
|
||||
|
||||
const email = 'jennie+cleonguyen@machinemagic.co'
|
||||
|
||||
const memberRes = await db.collection('members').deleteOne({ email })
|
||||
console.log(`Deleted ${memberRes.deletedCount} member(s)`)
|
||||
|
||||
const preRegRes = await db.collection('preregistrations').updateOne(
|
||||
{ email },
|
||||
{
|
||||
$set: { status: 'pending', magicLinkJtiUsed: false },
|
||||
$unset: { acceptedAt: '', memberId: '' },
|
||||
}
|
||||
)
|
||||
console.log(`Reset ${preRegRes.modifiedCount} preRegistration(s)`)
|
||||
|
||||
const member = await db.collection('members').findOne({ email })
|
||||
console.log('\nMember state after reset:')
|
||||
console.log(JSON.stringify(member, null, 2))
|
||||
|
||||
const preReg = await db.collection('preregistrations').findOne(
|
||||
{ email },
|
||||
{ projection: { email: 1, status: 1, acceptedAt: 1, memberId: 1, magicLinkJtiUsed: 1 } }
|
||||
)
|
||||
console.log('\nPreRegistration state after reset:')
|
||||
console.log(JSON.stringify(preReg, null, 2))
|
||||
|
||||
await mongoose.disconnect()
|
||||
})()
|
||||
Loading…
Add table
Add a link
Reference in a new issue