Adds a new "bangs" module with a dashboard widget that tracks household bang events. The widget shows the total count, a configurable recent-bang list, and an "Add Bang" button that accepts a backdatable date. On submission, a full celebration sequence fires: screen flash, widget shake, count pop animation, synthesized firework sound (Web Audio API), 50 full-screen emoji particles via portal, canvas-confetti star bursts, side cannons, and a timed sequence of 7 firework bursts. - New bang_events table (migration 0017) - canvas-confetti dependency for fireworks/confetti effects - CSS keyframe animations: shake, countPop, emojiFloat - Fixes pre-existing Drizzle snapshot chain collision (0007/0008) - Adds OpenPlantBook env vars to compose.yaml
51 lines
1.2 KiB
TypeScript
51 lines
1.2 KiB
TypeScript
"use server";
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
import { z } from "zod";
|
|
import { db } from "@/lib/db";
|
|
import { getCurrentSession } from "@/lib/session";
|
|
import { logActivity } from "@/modules/_core/activity";
|
|
import { bangEvents } from "../schema";
|
|
|
|
const addBangInput = z.object({
|
|
occurredOn: z
|
|
.string()
|
|
.regex(/^\d{4}-\d{2}-\d{2}$/, "Must be YYYY-MM-DD")
|
|
.optional(),
|
|
});
|
|
|
|
function todayString(): string {
|
|
const d = new Date();
|
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
}
|
|
|
|
export async function addBang(input: z.input<typeof addBangInput> = {}) {
|
|
const parsed = addBangInput.parse(input);
|
|
const { user, household } = await getCurrentSession();
|
|
|
|
const occurredOn = parsed.occurredOn ?? todayString();
|
|
|
|
const [bang] = await db
|
|
.insert(bangEvents)
|
|
.values({
|
|
householdId: household.id,
|
|
recordedBy: user.id,
|
|
occurredOn,
|
|
})
|
|
.returning();
|
|
|
|
if (!bang) throw new Error("Bang was not recorded");
|
|
|
|
await logActivity({
|
|
entityType: "bangs.event",
|
|
entityId: bang.id,
|
|
action: "create",
|
|
payload: { occurredOn },
|
|
});
|
|
|
|
revalidatePath("/");
|
|
revalidatePath("/d/[slug]", "page");
|
|
|
|
return bang;
|
|
}
|