A Firebase-hosted React + Vite calendar for game events. The public calendar shows ongoing and upcoming events as date-grouped 24-hour timelines. Admins manage games, events, and users at /admin. Managed users can be assigned Admin or GM roles. Game Masters sign in at /gm, create their own events, generate invite links with 6-digit PINs, and manage joined players.
For a product-facing overview of current screens, role capabilities, invite behavior, and common terms, see Chronocodex Features And Terms.
Install these locally:
- Node.js and npm
- Firebase CLI:
npm install -g firebase-tools
Enable these Firebase products:
- Firebase Hosting
- Cloud Firestore
- Firebase Authentication with Email/Password provider
- Cloud Functions for Firebase
- Google reCAPTCHA v3 site key and secret key
Cloud Functions use Node 20 and deploy to asia-southeast1.
- Copy
.env.exampleto.env. - Fill in your Firebase web app config and
VITE_RECAPTCHA_SITE_KEY. - Sign in to Firebase CLI:
firebase login- Install app dependencies:
npm install- Install Cloud Functions dependencies:
npm install --prefix functions- Store the reCAPTCHA secret for Cloud Functions:
firebase functions:secrets:set RECAPTCHA_SECRET_KEY- Create the first admin Auth account in Firebase Authentication.
- Create the first admin document manually in Firestore:
admins/your-email@example.com
The document can be empty. The document ID must be the lowercase Firebase Auth email. After this, use /admin > Users to create Admin and GM login accounts.
Run the app:
npm run devBuild the frontend:
npm run buildCheck the functions file syntax:
node --check functions/index.jsBuild first:
npm run buildDeploy hosting, Firestore rules, and functions together:
firebase deploy --only hosting,firestore:rules,functionsHosting only:
npm run build
firebase deploy --only hostingFirestore rules only:
firebase deploy --only firestore:rulesValidate Firestore rules without deploying:
firebase deploy --only firestore:rules --dry-runFunctions only:
firebase deploy --only functionsDeploy one function:
firebase deploy --only functions:createUserAccount
firebase deploy --only functions:updateUserAccount
firebase deploy --only functions:setUserDisabled
firebase deploy --only functions:deleteUserAccount
firebase deploy --only functions:createGameMasterAccount
firebase deploy --only functions:updateGameMasterAccount
firebase deploy --only functions:deleteGameMasterAccount
firebase deploy --only functions:joinEvent
firebase deploy --only functions:verifyRecaptchaTokenFunction source lives in functions/index.js.
After changing function code:
- Check syntax:
node --check functions/index.js- If dependencies changed, update
functions/package.jsonand run:
npm install --prefix functions- Deploy functions. On the first deploy after adding reCAPTCHA, make sure
RECAPTCHA_SECRET_KEYhas been set withfirebase functions:secrets:set RECAPTCHA_SECRET_KEY:
firebase deploy --only functions- If the frontend calls a new function name or changed payload shape, rebuild and deploy hosting too:
npm run build
firebase deploy --only hosting,functionscreateUserAccount: admin-only; creates Firebase Auth user,users/{uid}profile, and the role compatibility document.updateUserAccount: admin-only; updates Firebase Auth user,users/{uid}profile, and role compatibility documents.setUserDisabled: admin-only; disables or enables Firebase Auth access and app role access.deleteUserAccount: admin-only; hard-deletes the Firebase Auth user and removes user/role profile documents.createGameMasterAccount: admin-only; creates Firebase Auth user andgameMasters/{uid}profile.updateGameMasterAccount: admin-only; updates GM profile and Auth account.deleteGameMasterAccount: admin-only; disables Auth account and removes the GM profile.joinEvent: public callable; validates event ID, player name, published/invite state, 6-digit PIN, and reCAPTCHA v3 token before adding a joined player.verifyRecaptchaToken: public callable; verifies reCAPTCHA v3 tokens for admin and GM login attempts before Firebase email/password sign-in.
These functions are Firebase callable functions, not plain REST endpoints. Call them with the Firebase client SDK so Firebase sends the expected callable request format, auth context, region, and headers.
The app already uses src/firebase.js:
import { getFunctions, httpsCallable } from "firebase/functions";
const functions = getFunctions(app, "asia-southeast1");
const joinEvent = httpsCallable(functions, "joinEvent");
await joinEvent({
eventId,
playerName,
pin,
recaptchaToken,
});Do not call callable functions with a browser fetch() to the Google Cloud URL unless you are intentionally implementing the callable protocol yourself. A direct fetch() often appears as a CORS failure because the backend rejects the request shape before the browser can read a useful response.
For local development against deployed functions, run the Vite app normally:
npm run devThe frontend can call the deployed asia-southeast1 callable functions from http://localhost:5173 through the Firebase SDK.
To test local functions with the emulator, start the emulator and connect the client with connectFunctionsEmulator:
import { connectFunctionsEmulator, getFunctions } from "firebase/functions";
const functions = getFunctions(app, "asia-southeast1");
if (import.meta.env.DEV && import.meta.env.VITE_USE_FUNCTIONS_EMULATOR === "true") {
connectFunctionsEmulator(functions, "127.0.0.1", 5001);
}Then add this to .env only when using the emulator:
VITE_USE_FUNCTIONS_EMULATOR=true
Browser console messages can say CORS even when the real problem is a callable/auth validation failure. Check the Network tab response and Firebase Functions logs:
firebase functions:logCommon causes:
403 Forbiddenon admin functions: the signed-in user is not listed inadmins/{lowercase-email}.permission-denied: the callable function loaded, but your account is not allowed to perform that action.unauthenticated: the user is not signed in, or the request was made outside the Firebase SDK.invalid-argument: required payload fields are missing, such aseventId,pin,playerName, orrecaptchaToken.failed-preconditiononjoinEvent: the event is unpublished, invite joining is disabled, or the reCAPTCHA secret is not configured.- CORS-looking failure from localhost: make sure the frontend uses
httpsCallable(...), not directfetch(...), and thatgetFunctions(app, "asia-southeast1")matches the deployed region. The deployed functions use explicitcors: true; redeploy functions after changing CORS options. - Preflight CORS failure with no function log entry: the request did not reach user code. For 2nd gen functions, check the Cloud Run service/function IAM and allow unauthenticated invocation, then rely on the callable function auth/admin checks for app security.
- Authorization header exists but the browser still reports CORS: check whether the response is actually a Google Cloud
403before the function runs. Redeploy after setting explicit callablecors: true, then verify the 2nd gen function/Cloud Run service allows public invocation. Firebase callable functions still enforce app-level admin checks inside the function. - reCAPTCHA v3 is invisible by design: there is no checkbox challenge in the form. Google may show a small badge after the v3 script loads.
ERROR for site owner: Invalid key type: the frontend is using a v2 checkbox key or old checkbox script with the v3 flow. Create a reCAPTCHA v3 key, put that site key inVITE_RECAPTCHA_SITE_KEY, and redeploy hosting.- reCAPTCHA failure from localhost: add
localhost,127.0.0.1, and the production domain to the allowed domains for the reCAPTCHA v3 site key in the Google reCAPTCHA admin console.
users/{uid}: canonical managed user profiles with Admin or GM role metadata.admins/{email}: admin allowlist documents.gameMasters/{uid}: Game Master profiles linked to Firebase Auth users.events/{eventId}: event documents, includingcreatedBy,gameMasterUid, andinviteEnabled.eventSecrets/{eventId}: private invite PIN documents.events/{eventId}/players/{playerId}: joined player names.games/{gameId}: game dropdown options.