feat: pre-registrant management and invitation system
Admin interface to review, filter, and batch-invite the 95 pre-registrants from Baby Ghosts. Accept-invitation page pre-fills their data and collects circle, pronouns, motivation, contribution tier, and agreement before creating their member record.
This commit is contained in:
parent
bab53cec9e
commit
501be10bfe
15 changed files with 1896 additions and 1 deletions
108
server/api/invite/accept.post.js
Normal file
108
server/api/invite/accept.post.js
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import jwt from 'jsonwebtoken'
|
||||
import PreRegistration from '../../models/preRegistration.js'
|
||||
import Member from '../../models/member.js'
|
||||
import { connectDB } from '../../utils/mongoose.js'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const body = await validateBody(event, inviteAcceptSchema)
|
||||
const config = useRuntimeConfig(event)
|
||||
await connectDB()
|
||||
|
||||
// Re-verify the token is still valid (not expired)
|
||||
let decoded
|
||||
try {
|
||||
decoded = jwt.verify(body.token, config.jwtSecret)
|
||||
} catch {
|
||||
throw createError({ statusCode: 401, statusMessage: 'Invalid or expired invitation link' })
|
||||
}
|
||||
|
||||
if (decoded.type !== 'prereg-invite' || decoded.preRegistrationId !== body.preRegistrationId) {
|
||||
throw createError({ statusCode: 401, statusMessage: 'Invalid or expired invitation link' })
|
||||
}
|
||||
|
||||
const preReg = await PreRegistration.findById(body.preRegistrationId)
|
||||
if (!preReg) {
|
||||
throw createError({ statusCode: 404, statusMessage: 'Pre-registration not found' })
|
||||
}
|
||||
|
||||
if (preReg.status === 'accepted') {
|
||||
throw createError({ statusCode: 400, statusMessage: 'This invitation has already been accepted' })
|
||||
}
|
||||
|
||||
// Check no existing member with this email
|
||||
const existingMember = await Member.findOne({ email: preReg.email })
|
||||
if (existingMember) {
|
||||
throw createError({ statusCode: 409, statusMessage: 'A member with this email already exists' })
|
||||
}
|
||||
|
||||
// Create the member
|
||||
const member = await Member.create({
|
||||
email: preReg.email,
|
||||
name: body.name,
|
||||
pronouns: body.pronouns || undefined,
|
||||
location: body.location || undefined,
|
||||
circle: body.circle,
|
||||
contributionTier: body.contributionTier,
|
||||
bio: body.motivation || undefined,
|
||||
status: body.contributionTier === '0' ? 'active' : 'pending_payment',
|
||||
})
|
||||
|
||||
// Update pre-registration
|
||||
await PreRegistration.findByIdAndUpdate(preReg._id, {
|
||||
$set: {
|
||||
status: 'accepted',
|
||||
acceptedAt: new Date(),
|
||||
memberId: member._id,
|
||||
}
|
||||
})
|
||||
|
||||
logActivity(member._id, 'member_joined', {
|
||||
source: 'pre-registration',
|
||||
preRegistrationId: preReg._id,
|
||||
})
|
||||
|
||||
// For free tier, issue session and redirect to welcome
|
||||
if (body.contributionTier === '0') {
|
||||
const sessionToken = jwt.sign(
|
||||
{ memberId: member._id, email: member.email, tv: member.tokenVersion },
|
||||
config.jwtSecret,
|
||||
{ expiresIn: '7d' },
|
||||
)
|
||||
|
||||
setCookie(event, 'auth-token', sessionToken, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
maxAge: 60 * 60 * 24 * 7,
|
||||
})
|
||||
|
||||
return {
|
||||
success: true,
|
||||
requiresPayment: false,
|
||||
redirectUrl: '/welcome',
|
||||
member: {
|
||||
id: member._id,
|
||||
email: member.email,
|
||||
name: member.name,
|
||||
circle: member.circle,
|
||||
contributionTier: member.contributionTier,
|
||||
status: member.status,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For paid tiers, return member info so frontend can proceed to Helcim payment
|
||||
return {
|
||||
success: true,
|
||||
requiresPayment: true,
|
||||
member: {
|
||||
id: member._id,
|
||||
email: member.email,
|
||||
name: member.name,
|
||||
circle: member.circle,
|
||||
contributionTier: member.contributionTier,
|
||||
status: member.status,
|
||||
}
|
||||
}
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue