No updates detected yet. GitHub releases will appear here.
QR attendance for lecturers, trainers and events. Start a session, share the QR, attendance is done.
翻訳待ち
Loading the full README. The saved excerpt is shown below.
Start a session. Share the QR. Attendance is done.
QR attendance for lecturers, trainers and event organisers. Participants scan with their
phone camera and type their ID and name — no app, no account.
Stack: React + TypeScript + Vite + Tailwind, Firebase Auth + Firestore + Hosting, installable PWA.
There are no Cloud Functions; everything is enforced by firestore.rules.
Uses the Firebase emulators. The Firestore emulator needs Java 11+.Read the full README ↗Start a session. Share the QR. Attendance is done.
QR attendance for lecturers, trainers and event organisers. Participants scan with their phone camera and type their ID and name — no app, no account.
Stack: React + TypeScript + Vite + Tailwind, Firebase Auth + Firestore + Hosting, installable PWA.
There are no Cloud Functions; everything is enforced by firestore.rules.
Uses the Firebase emulators. The Firestore emulator needs Java 11+.
npm install
npm run emulators # terminal 1: Auth on :9099, Firestore on :8080
npm run dev:emulator # terminal 2: app on http://localhost:5173
dev:emulator also serves on your LAN address. Open the app from that address
(e.g. http://192.168.1.20:5173) and a phone on the same Wi-Fi can scan the QR.
Emulator data is wiped when the emulators stop.
.env.local (see .env.example).npx firebase login && npx firebase use --addnpx firebase deploy --only firestore (rules + indexes)npm run dev| Variable | Required | Purpose |
|---|---|---|
VITE_FIREBASE_API_KEY | yes | Web app config |
VITE_FIREBASE_AUTH_DOMAIN | yes | Web app config |
VITE_FIREBASE_PROJECT_ID | yes | Web app config |
VITE_FIREBASE_APP_ID | yes | Web app config |
VITE_PUBLIC_URL | no | Base URL encoded in the QR. Defaults to the origin the lecturer is on. |
VITE_USE_EMULATORS | no | true to use the local emulators. |
attend, deploys automatically on every push to main)attend-d2744 (Firestore in asia-southeast1, Singapore). Its web config is in
.env.production; those values are public by design, and access is controlled by firestore.rules.To change the app: commit and push to main; Vercel rebuilds and publishes it.
To change the security rules or indexes, either paste firestore.rules into the Firebase console
(Firestore → Rules → Publish), or use the CLI once:
npx firebase login
npx firebase deploy --only firestore
Pushing to GitHub does not update the rules. If a change touches firestore.rules, publish
the rules as well, or the live site and its rules will disagree.
Customers pay through Stripe Checkout; Dobry never handles a card. Three small Vercel functions
in api/ do the work (they need no Firebase paid plan):
| Function | What it does |
|---|---|
POST /api/checkout | A signed-in admin names a plan; the server works out the price from the Console's price list (platformConfig/pricing, or the code's defaults) and replies with Stripe's payment page. Someone already paying is moved to the new plan in place. |
POST /api/portal | Opens Stripe's billing page (card, invoices, cancel). |
POST /api/stripe-webhook | Stripe's messages, signature-checked. The only thing that sets plan: 'pro', paidUntil and seats on an organisation; it also writes each paid invoice to billing and a line to platformAudit. |
GET /api/health | Which settings are present (never their values). |
Settings, in Vercel → Project → Settings → Environment Variables:
FIREBASE_SERVICE_ACCOUNT: the whole JSON of a Firebase service-account key (or that JSON in base64). Always needed: it is how the functions reach the database.STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET (optional): only used when no keys are saved in the Console.The Stripe keys themselves are entered in Console → Settings → Payments (Stripe) by an owner
(POST /api/stripe-settings). They are checked with Stripe, then kept in platformSecrets/stripe,
which no browser can read or write (no rule matches it; tests/rules.test.ts proves it). The
Console is only ever told the account name, test or live, and the keys' last four characters.
Moving to another Stripe account is saving its two keys there; a change is in use within a minute.
VITE_PAYMENTS_OPEN=true: opens payment to every customer (new accounts then start a 14-day trial). Leave it out until ready.SITE_URL (optional): where Stripe sends people back to; defaults to the address the request came to.In Stripe: add a webhook endpoint for https://<site>/api/stripe-webhook with the events
checkout.session.completed, customer.subscription.created, customer.subscription.updated,
customer.subscription.deleted, invoice.paid, invoice.payment_failed; and save the Customer
portal settings once. No products or prices need making by hand: one product per plan is created
the first time it is bought, and the amount always comes from the price list.
What the free plan holds back. allows(feature) in src/lib/plan.ts is the one switch: a
workplace on the free plan (and only a workplace) is without PDF download, Excel export, overtime
and rest-day/holiday hours, leave tracking, more than one office or shift, branch managers, shift
mode, the location check and refusing other people's phones. <Paid> / <UpgradeNote>
(src/components/Paid.tsx) put a note with a link to the plans where the feature would be. The
limits apply only when locksOn() is true: once payments are open, or in a browser trying them
with ?pay=test. They are checks in the app, not in the database rules. Reports on screen, the
Today screen, the door screen and full history stay free. Tests in tests/plan.test.ts.
This is the beginning of the README. Read the rest at the original. Read the full README ↗