Compare commits

...

5 Commits

Author SHA1 Message Date
42156209d8 fix(signup): when signing up for the event and already logged in, set the email of the account 2026-03-11 12:29:37 +01:00
569ee4ec40 fix(admin): fix search (or vs and), remove sticky header 2026-03-11 12:03:08 +01:00
29250f8694 fix: resolve biome lint warnings in admin page (useless fragment, non-null assertion) 2026-03-11 11:55:00 +01:00
f214660ab2 feat: redesign admin page with dense ops-tool aesthetic and expandable rows
- Replace generic card layout with near-black background and monospace data display
- Add expandable rows for guest details, notes/questions, and performer details
- Fix duplicate stat cards (artiesten/bezoekers were shown twice)
- Fix gift revenue card showing wrong value (stats.today → totalGiftRevenue)
- Fix mobile card view to use accessible <button> for expandable rows
- Remove unused Users and Card component imports
2026-03-11 11:52:26 +01:00
ba0804db30 fix: performers shouldnt see payment badge 2026-03-11 11:47:54 +01:00
5 changed files with 953 additions and 873 deletions

View File

@@ -1,6 +1,7 @@
import { useMutation } from "@tanstack/react-query";
import { useState } from "react";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { authClient } from "@/lib/auth-client";
import {
inputCls,
validateEmail,
@@ -25,6 +26,9 @@ interface Props {
}
export function PerformerForm({ onBack, onSuccess }: Props) {
const { data: session } = authClient.useSession();
const sessionEmail = session?.user?.email ?? "";
const [data, setData] = useState({
firstName: "",
lastName: "",
@@ -39,6 +43,12 @@ export function PerformerForm({ onBack, onSuccess }: Props) {
const [errors, setErrors] = useState<PerformerErrors>({});
const [touched, setTouched] = useState<Record<string, boolean>>({});
useEffect(() => {
if (sessionEmail) {
setData((prev) => ({ ...prev, email: sessionEmail }));
}
}, [sessionEmail]);
const submitMutation = useMutation({
...orpc.submitRegistration.mutationOptions(),
onSuccess: (result) => {
@@ -254,14 +264,15 @@ export function PerformerForm({ onBack, onSuccess }: Props) {
id="p-email"
name="email"
value={data.email}
onChange={handleChange}
onBlur={handleBlur}
onChange={sessionEmail ? undefined : handleChange}
onBlur={sessionEmail ? undefined : handleBlur}
readOnly={!!sessionEmail}
placeholder="jouw@email.be"
autoComplete="email"
inputMode="email"
aria-required="true"
aria-invalid={touched.email && !!errors.email}
className={inputCls(!!touched.email && !!errors.email)}
className={`${inputCls(!!touched.email && !!errors.email)}${sessionEmail ? "cursor-not-allowed opacity-75" : ""}`}
/>
{touched.email && errors.email && (
<span className="text-red-300 text-sm" role="alert">

View File

@@ -1,6 +1,7 @@
import { useMutation } from "@tanstack/react-query";
import { useState } from "react";
import { useEffect, useState } from "react";
import { toast } from "sonner";
import { authClient } from "@/lib/auth-client";
import {
calculateDrinkCard,
type GuestEntry,
@@ -30,6 +31,9 @@ interface Props {
// ── Main watcher form ──────────────────────────────────────────────────────
export function WatcherForm({ onBack, onSuccess }: Props) {
const { data: session } = authClient.useSession();
const sessionEmail = session?.user?.email ?? "";
const [data, setData] = useState({
firstName: "",
lastName: "",
@@ -43,6 +47,12 @@ export function WatcherForm({ onBack, onSuccess }: Props) {
const [guestErrors, setGuestErrors] = useState<GuestErrors[]>([]);
const [giftAmount, setGiftAmount] = useState(0);
useEffect(() => {
if (sessionEmail) {
setData((prev) => ({ ...prev, email: sessionEmail }));
}
}, [sessionEmail]);
const submitMutation = useMutation({
...orpc.submitRegistration.mutationOptions(),
onSuccess: (result) => {
@@ -290,14 +300,15 @@ export function WatcherForm({ onBack, onSuccess }: Props) {
id="w-email"
name="email"
value={data.email}
onChange={handleChange}
onBlur={handleBlur}
onChange={sessionEmail ? undefined : handleChange}
onBlur={sessionEmail ? undefined : handleBlur}
readOnly={!!sessionEmail}
placeholder="jouw@email.be"
autoComplete="email"
inputMode="email"
aria-required="true"
aria-invalid={touched.email && !!errors.email}
className={inputCls(!!touched.email && !!errors.email)}
className={`${inputCls(!!touched.email && !!errors.email)}${sessionEmail ? "cursor-not-allowed opacity-75" : ""}`}
/>
{touched.email && errors.email && (
<span className="text-red-300 text-sm" role="alert">

File diff suppressed because it is too large Load Diff

View File

@@ -585,8 +585,10 @@ function ManageRegistrationPage() {
)}
</div>
{/* Payment status - shown for everyone with pending/extra payment or gift */}
{(data.paymentStatus !== "paid" || (data.giftAmount ?? 0) > 0) && (
{/* Payment status badge:
- performers only see a badge if they have a gift to pay, or already paid one
- watchers always have a payment (drink card), so always show a badge */}
{(isPerformer ? (data.giftAmount ?? 0) > 0 : true) && (
<div className="mb-6">
{data.paymentStatus === "paid" ? (
<PaidBadge />

View File

@@ -5,7 +5,18 @@ import { user } from "@kk/db/schema/auth";
import { drinkkaart, drinkkaartTopup } from "@kk/db/schema/drinkkaart";
import { env } from "@kk/env/server";
import type { RouterClient } from "@orpc/server";
import { and, count, desc, eq, gte, isNull, like, lte, sum } from "drizzle-orm";
import {
and,
count,
desc,
eq,
gte,
isNull,
like,
lte,
or,
sum,
} from "drizzle-orm";
import { z } from "zod";
import {
sendCancellationEmail,
@@ -426,7 +437,7 @@ export const appRouter = {
if (input.search) {
const term = `%${input.search}%`;
conditions.push(
and(
or(
like(registration.firstName, term),
like(registration.lastName, term),
like(registration.email, term),