From 82db26fcef545a73a487f082188174666b2eb0c4 Mon Sep 17 00:00:00 2001 From: bunker-admin Date: Sat, 11 Apr 2026 21:15:36 -0600 Subject: [PATCH] Auto-mint referral codes + action-based points for volunteer dashboard Referral: if the user has no active invite code when the dashboard loads, one is auto-created via referralService.createInviteCode. Every volunteer now sees a ready-to-share referral link on first visit without needing to manually create one. Points: replaced the media-engagement placeholder (videos watched + upvotes + comments) with a weighted tally of actual volunteer actions: shift signups (10pts), influence campaign emails (5pts), verified petition signatures (5pts), event tickets (10pts), action step completions (5pts), and achievements (15pts). This reflects organizing work rather than gallery browsing. Bunker Admin --- .../volunteer-dashboard.service.ts | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/api/src/modules/volunteer-dashboard/volunteer-dashboard.service.ts b/api/src/modules/volunteer-dashboard/volunteer-dashboard.service.ts index a32d9146..e1911f93 100644 --- a/api/src/modules/volunteer-dashboard/volunteer-dashboard.service.ts +++ b/api/src/modules/volunteer-dashboard/volunteer-dashboard.service.ts @@ -1,4 +1,4 @@ -import { Prisma, TicketedEventStatus, ShiftKind, ShiftStatus, TicketStatus } from '@prisma/client'; +import { Prisma, TicketedEventStatus, ShiftKind, ShiftStatus, SignupStatus, TicketStatus } from '@prisma/client'; import { prisma } from '../../config/database'; import { env } from '../../config/env'; import { actionCampaignsService, type ActiveCampaignForUser } from '../action-campaigns/action-campaigns.service'; @@ -97,13 +97,17 @@ async function getProfile(userId: string): Promise { async function getReferral(userId: string): Promise { const stats = await referralService.getReferralStats(userId); - const firstCode = await prisma.inviteCode.findFirst({ + let firstCode = await prisma.inviteCode.findFirst({ where: { createdByUserId: userId, isActive: true }, orderBy: { createdAt: 'desc' }, select: { code: true }, }); - const code = firstCode?.code ?? null; - const link = code ? `${env.ADMIN_URL}/register?ref=${code}` : null; + if (!firstCode) { + const created = await referralService.createInviteCode(userId, {}); + firstCode = { code: created.code }; + } + const code = firstCode.code; + const link = `${env.ADMIN_URL}/register?ref=${code}`; return { code, link, totalReferrals: stats.totalReferrals }; } @@ -196,19 +200,26 @@ async function getMyEvents(userId: string): Promise { } async function getPoints(userId: string): Promise { - const [stats, achievementCount] = await Promise.all([ - prisma.userStats.findUnique({ - where: { userId }, - select: { totalVideosWatched: true, totalUpvotesGiven: true, totalCommentsMade: true, totalFinishes: true }, - }), - prisma.userAchievement.count({ where: { userId } }), - ]); + const user = await prisma.user.findUnique({ where: { id: userId }, select: { email: true } }); + const email = user?.email ?? ''; + + const [shiftSignups, campaignEmails, petitionSignatures, eventTickets, stepCompletions, achievementCount] = + await Promise.all([ + prisma.shiftSignup.count({ where: { userId, status: SignupStatus.CONFIRMED } }), + prisma.campaignEmail.count({ where: { userId } }), + prisma.petitionSignature.count({ where: { signerEmail: email, verifiedAt: { not: null } } }), + prisma.ticket.count({ where: { userId, status: TicketStatus.VALID } }), + prisma.actionStepCompletion.count({ where: { userId } }), + prisma.userAchievement.count({ where: { userId } }), + ]); const total = - (stats?.totalVideosWatched ?? 0) + - (stats?.totalUpvotesGiven ?? 0) + - (stats?.totalCommentsMade ?? 0) + - (stats?.totalFinishes ?? 0); + shiftSignups * 10 + + campaignEmails * 5 + + petitionSignatures * 5 + + eventTickets * 10 + + stepCompletions * 5 + + achievementCount * 15; return { total, achievementCount }; }