Repository navigation
v0.3.0 Customization
Note: Documentation of Guestroll v0.3.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.
Filters and frames are applied by the upload queue (components/upload/applyEffects.ts),
not by the capture component: a custom capture component gets them for free.
The pixel math is in src/lib/effects.ts; add a filter there and to FILTERS,
then give it a name in the translations.
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.