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
This commit is contained in:
bunker-admin 2026-04-11 21:15:36 -06:00
parent df65b1b72e
commit 82db26fcef

View File

@ -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<DashboardProfile | null> {
async function getReferral(userId: string): Promise<DashboardReferral> {
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<DashboardMyEvent[]> {
}
async function getPoints(userId: string): Promise<DashboardPoints> {
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 };
}