feat: redesign CMS and unify German admin UI
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import { randomInt } from "node:crypto";
|
||||
|
||||
// Unambiguous alphabet (no 0/O/1/I/l) — easier to read aloud/type correctly.
|
||||
// Unambiguous alphabet (no 0/O/1/I/l) - easier to read aloud/type correctly.
|
||||
const ALPHABET = "ABCDEFGHJKMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
|
||||
|
||||
/**
|
||||
* Cryptographically random meeting password, e.g. "K7mQ-42Px". Independent
|
||||
* of the event's id/slug — never derive it from the meeting identifier.
|
||||
* of the event's id/slug - never derive it from the meeting identifier.
|
||||
*/
|
||||
export function generateMeetingPassword(): string {
|
||||
const chars = Array.from({ length: 8 }, () => ALPHABET[randomInt(ALPHABET.length)]);
|
||||
|
||||
@@ -20,7 +20,7 @@ function formatTime(date: Date): string {
|
||||
|
||||
/**
|
||||
* Checks all upcoming online events for due 60/30-minute reminders and sends
|
||||
* them — to registered, non-cancelled participants and (optionally) to the
|
||||
* them - to registered, non-cancelled participants and (optionally) to the
|
||||
* admin host. Idempotent: every send is guarded by a "...Sent" flag so a
|
||||
* cron job that runs every few minutes never double-sends.
|
||||
*/
|
||||
@@ -74,7 +74,7 @@ export async function runEventReminders() {
|
||||
dateLabel,
|
||||
timeLabel,
|
||||
joinUrl,
|
||||
meetingPassword: event.meetingPassword || "—",
|
||||
meetingPassword: event.meetingPassword || "-",
|
||||
minutesBefore: threshold.minutes,
|
||||
});
|
||||
results.emailsSent++;
|
||||
@@ -82,7 +82,7 @@ export async function runEventReminders() {
|
||||
await payload.update({ collection: "events", id: event.id, data: { [threshold.hostField]: true } });
|
||||
}
|
||||
|
||||
// Participant reminders — only registered (not cancelled), not yet sent.
|
||||
// Participant reminders - only registered (not cancelled), not yet sent.
|
||||
const { docs: registrations } = await payload.find({
|
||||
collection: "event-registrations",
|
||||
where: {
|
||||
@@ -102,7 +102,7 @@ export async function runEventReminders() {
|
||||
dateLabel,
|
||||
timeLabel,
|
||||
joinUrl,
|
||||
meetingPassword: event.meetingPassword || "—",
|
||||
meetingPassword: event.meetingPassword || "-",
|
||||
minutesBefore: threshold.minutes,
|
||||
});
|
||||
results.emailsSent++;
|
||||
|
||||
@@ -4,7 +4,7 @@ export type MeetingStatus = "scheduled" | "open" | "live" | "ended";
|
||||
|
||||
/**
|
||||
* Payload's "time only" date fields still store a full ISO timestamp, just
|
||||
* with an arbitrary date portion — this combines the real event date with
|
||||
* with an arbitrary date portion - this combines the real event date with
|
||||
* the hour/minute from a time-only field into one correct Date.
|
||||
*/
|
||||
export function combineDateAndTime(dateISO: string, timeISO?: string | null): Date {
|
||||
|
||||
@@ -6,13 +6,13 @@ export type MeetingTokenPayload = {
|
||||
eventSlug: string;
|
||||
participantId: string;
|
||||
// Self-declared, persisted client-side (localStorage, not sessionStorage)
|
||||
// identity for participants — participantId is fresh per /join call, so
|
||||
// identity for participants - participantId is fresh per /join call, so
|
||||
// without this a kick could always be undone by simply rejoining. Only
|
||||
// used to make a kick stick across a rejoin (see rooms.ts kickedClientIds);
|
||||
// never used to key live room membership, so a client choosing an
|
||||
// arbitrary/colliding value can only affect their own future kick status,
|
||||
// never another participant's connection. Still bypassable by clearing
|
||||
// localStorage or joining from a different browser — there's no real
|
||||
// localStorage or joining from a different browser - there's no real
|
||||
// identity to anchor to for an unauthenticated participant.
|
||||
clientId?: string;
|
||||
name: string;
|
||||
|
||||
Reference in New Issue
Block a user