v0.2.0 Customization - achedon12/guestroll GitHub Wiki
Note: Documentation of Guestroll v0.2.0. The latest release is v0.5.0: this page in v0.5.0 · what changed · other versions
Most things are configurable from the admin: colors, logo, QR code design, capture mode, language. This page is about changing the code itself.
The capture component is the part of the guest page that decides how guests
take or pick photos: by default, a big shutter button that opens the camera.
It is deliberately separate from the upload: a capture component hands files to
onFiles(), and the host component around it compresses, queues, uploads,
retries and shows progress. Whatever you build, uploads keep working.
// src/components/capture/types.ts
export interface CaptureProps {
/** Queue files for upload. Call it as many times as you like. */
onFiles: (files: File[]) => void;
/** True while uploads are closed or the guest reached their quota. */
disabled: boolean;
/** Photos this guest may still send, or null when unlimited. */
remaining: number | null;
event: PublicEvent;
}The component renders inside the camera body, on the event's color. Use the
text-accent-ink and border-accent-ink classes for anything drawn on it:
they are computed to stay readable on any color the hosts pick.
// src/components/capture/CountdownCapture.tsx
"use client";
import { useState } from "react";
import { useFileInput } from "./useFileInput";
import { ShutterButton } from "./ShutterButton";
import type { CaptureProps } from "./types";
export function CountdownCapture({ onFiles, disabled }: CaptureProps) {
const camera = useFileInput(onFiles, { camera: true, multiple: false });
const [count, setCount] = useState<number | null>(null);
function start() {
let n = 3;
setCount(n);
const timer = setInterval(() => {
n -= 1;
if (n > 0) return setCount(n);
clearInterval(timer);
setCount(null);
camera.open();
}, 1000);
}
return (
<div className="flex flex-col items-center gap-3 text-accent-ink">
{camera.input}
<ShutterButton onClick={start} disabled={disabled || count !== null} label="Start the countdown" />
<span className="text-base font-semibold">{count ?? "Smile!"}</span>
</div>
);
}useFileInput and ShutterButton are exported for exactly this. Browsers only
open the camera from a user gesture, so a countdown that calls camera.open()
from a timer works on Android but is blocked by iOS Safari: in production,
open the camera first and count down on screen instead.
The registry maps each capture mode to a component. Point a mode at yours:
// src/components/capture/registry.ts
export const captureComponents: Record<CaptureMode, ComponentType<CaptureProps>> = {
camera: CountdownCapture, // every event in "camera only" mode now uses it
picker: PickerCapture,
both: CameraAndPickerCapture,
};To offer it next to the built-in ones instead, add a mode:
- add it to
CAPTURE_MODESinsrc/lib/constants.ts; - add it to the
captureModeenum insrc/lib/db/schema.tsand runnpm run db:generate; - map it in the registry;
- add its label under
admin.form.captureModesin each language ofsrc/lib/i18n/messages.ts.
Then build your own image: docker build -t my-guestroll .
From the admin, per event: accent color, logo (shown on the guest page, in the QR code and on the social card), QR code colors, shapes and caption.
The interface colors are tokens in src/app/globals.css (--color-canvas,
--color-ink, …), defined once for the light theme and once for the dark one.
Components only use the token names, so changing a value restyles everything.
All text lives in src/lib/i18n/messages.ts: English is the reference, and the
other languages must provide every key (tests/translate.test.ts fails
otherwise). To add a language:
- add its code to
LOCALESinsrc/lib/constants.ts, and its name toLOCALE_NAMESinsrc/lib/i18n/locale.ts; - add a dictionary in
messages.tsand register it inmessages; - add it to the
defaultLocaleenum insrc/lib/db/schema.tsand runnpm run db:generate.
Pull requests with new languages are very welcome.