diff --git a/app/pages/join.vue b/app/pages/join.vue index a76c316..67bc0d6 100644 --- a/app/pages/join.vue +++ b/app/pages/join.vue @@ -317,13 +317,17 @@

How membership works

+

+ Community connection happens in our Slack workspace, joined in monthly + onboarding waves — there may be a short wait after you join. +

diff --git a/app/pages/member/dashboard.vue b/app/pages/member/dashboard.vue index 26c0ad9..de91d71 100644 --- a/app/pages/member/dashboard.vue +++ b/app/pages/member/dashboard.vue @@ -39,8 +39,8 @@ ${{ memberData?.contributionAmount ?? 0 }} CAD/mo

- Slack workspace access is part of your membership. Your invitation - typically arrives within 2–3 weeks of joining. + Slack workspace access is part of your membership. Invitations are + sent in monthly onboarding waves — we'll be in touch.

diff --git a/server/api/helcim/customer.post.js b/server/api/helcim/customer.post.js index d0fc95d..2d09ff3 100644 --- a/server/api/helcim/customer.post.js +++ b/server/api/helcim/customer.post.js @@ -2,6 +2,7 @@ import { getRequestHeader, getRequestIP } from 'h3' import Member from '../../models/member.js' import { connectDB } from '../../utils/mongoose.js' import { createHelcimCustomer } from '../../utils/helcim.js' +import PreRegistration from '../../models/preRegistration.js' import { sendMagicLink } from '../../utils/magicLink.js' import { setPaymentBridgeCookie } from '../../utils/auth.js' import { rateLimit } from '../../utils/rateLimit.js' @@ -82,6 +83,32 @@ export default defineEventHandler(async (event) => { }) } + // If this email matches a pending pre-registrant, mark the PreRegistration + // as accepted and link it to the new Member. Silent — keeps /join and + // /admin/pre-registrants from showing the same person twice. + try { + const preReg = await PreRegistration.findOne({ email: normalizedEmail }) + if ( + preReg && + !preReg.memberId && + ['pending', 'selected', 'invited'].includes(preReg.status) + ) { + await PreRegistration.findByIdAndUpdate( + preReg._id, + { + $set: { + status: 'accepted', + acceptedAt: new Date(), + memberId: member._id, + }, + }, + { runValidators: false } + ) + } + } catch (linkError) { + console.error('Failed to link PreRegistration to new member:', linkError) + } + await sendMagicLink(normalizedEmail, { subject: 'Verify your Ghost Guild signup', intro: 'Verify your email to finish your Ghost Guild signup:', diff --git a/server/api/invite/accept.post.js b/server/api/invite/accept.post.js index 84d5db6..27e5109 100644 --- a/server/api/invite/accept.post.js +++ b/server/api/invite/accept.post.js @@ -5,6 +5,7 @@ import { connectDB } from '../../utils/mongoose.js' import { setAuthCookie } from '../../utils/auth.js' import { assignMemberNumber } from '../../utils/memberNumber.js' import { createHelcimCustomer } from '../../utils/helcim.js' +import { sendWelcomeEmail } from '../../utils/resend.js' export default defineEventHandler(async (event) => { const body = await validateBody(event, inviteAcceptSchema) @@ -88,6 +89,15 @@ export default defineEventHandler(async (event) => { // For free tier, redirect to welcome if (body.contributionAmount === 0) { await autoFlagPreExistingSlackAccess(member) + try { + await sendWelcomeEmail(member) + logActivity(member._id, 'email_sent', { + emailType: 'welcome', + subject: 'Welcome to Ghost Guild' + }) + } catch (emailError) { + console.error('Failed to send welcome email:', emailError) + } return { success: true, requiresPayment: false, diff --git a/server/utils/resend.js b/server/utils/resend.js index e3cada2..ce79e94 100644 --- a/server/utils/resend.js +++ b/server/utils/resend.js @@ -282,7 +282,7 @@ Welcome to Ghost Guild! You're now part of the ${member.circle} circle. Sign in to your dashboard to get started: ${baseUrl}/member/dashboard -If you have questions, reach out to jennie + eileen on Slack or reply to this email.`, +If you have questions, just reply to this email.`, }); if (error) { diff --git a/tests/server/api/free-signup-flow.test.js b/tests/server/api/free-signup-flow.test.js index 521c0b2..bee73b3 100644 --- a/tests/server/api/free-signup-flow.test.js +++ b/tests/server/api/free-signup-flow.test.js @@ -20,6 +20,9 @@ vi.mock('../../../server/models/member.js', () => ({ findOneAndUpdate: vi.fn() } })) +vi.mock('../../../server/models/preRegistration.js', () => ({ + default: { findOne: vi.fn().mockResolvedValue(null), findByIdAndUpdate: vi.fn() } +})) vi.mock('../../../server/utils/mongoose.js', () => ({ connectDB: vi.fn() })) vi.mock('../../../server/utils/helcim.js', () => ({ createHelcimCustomer: vi.fn(), diff --git a/tests/server/api/helcim-customer.test.js b/tests/server/api/helcim-customer.test.js index cba7df5..a023c27 100644 --- a/tests/server/api/helcim-customer.test.js +++ b/tests/server/api/helcim-customer.test.js @@ -12,6 +12,9 @@ import { createMockEvent } from '../helpers/createMockEvent.js' vi.mock('../../../server/models/member.js', () => ({ default: { findOne: vi.fn(), create: vi.fn(), findByIdAndUpdate: vi.fn() } })) +vi.mock('../../../server/models/preRegistration.js', () => ({ + default: { findOne: vi.fn().mockResolvedValue(null), findByIdAndUpdate: vi.fn() } +})) vi.mock('../../../server/utils/mongoose.js', () => ({ connectDB: vi.fn() })) vi.mock('../../../server/utils/helcim.js', () => ({ createHelcimCustomer: vi.fn()