Skip to main content
RapidDev - Software Development Agency
Lovable PromptsInternal ToolsIntermediate

Build an Attendance App in Lovable

A clock-in/clock-out attendance app with QR check-in, geo-fence validation enforced server-side, duplicate-check prevention via a UNIQUE constraint, admin reporting, and Excel-friendly CSV export.

Time to MVP

~1 day

Credits

~60-120 credits for full chain

Difficulty

Intermediate

Cloud features

3

TL;DR

The one-paragraph version before you dive in.

Paste the starter prompt below into Lovable Build mode and get a clock-in/clock-out attendance app with QR code check-in, server-side geo-fence validation, a UNIQUE constraint that blocks duplicate same-day check-ins, and an admin log with CSV export. Full build fits in one day with around 60-120 credits on a Pro plan.

Setup checklist

Complete these steps in Lovable before pasting the starter prompt — takes about 5 minutes.

Cloud tab settings

Database

Stores attendance records, events/shifts, and profiles with employee codes. The UNIQUE constraint on (user_id, event_id, date) is the core buddy-punch prevention mechanism.

  1. 1Click the + button next to Preview to open the Cloud tab
  2. 2Click Database — the starter prompt creates all three tables and the check_in RPC via migration
  3. 3Leave it empty now

Auth

Email+password or magic-link for employees. Admin role stored in app_metadata for the /admin routes.

  1. 1Cloud tab → Users & Auth
  2. 2Enable Email sign-in (default — already on). Optionally enable Magic Links for passwordless check-in on shared devices
  3. 3To make a user an admin: click their row → Edit → set app_metadata to {"role": "admin"}
  4. 4To make a user a supervisor: set app_metadata to {"role": "supervisor"}

Edge Functions

Three Edge Functions handle server-side check-in validation, QR token generation, and CSV export.

  1. 1Cloud tab → Edge Functions — leave empty; the starter prompt generates check-in and generate-qr-token, and the follow-up prompts generate export-csv
  2. 2After the auto-close follow-up, set up a scheduled Edge Function in Cloud tab → Edge Functions → auto-close-stale-shifts → Schedule (nightly at 23:59 local time)

Secrets (Cloud tab → Secrets)

QR_TOKEN_SECRET

Purpose: Signs short-lived QR tokens in generate-qr-token and verifies them in the check-in Edge Function so tokens cannot be forged.

Where to get it: Generate a random 32-byte hex string: in any browser console run `crypto.getRandomValues(new Uint8Array(32)).reduce((s,b)=>s+b.toString(16).padStart(2,'0'),'')`. Paste the result in Cloud tab → Secrets.

RESEND_API_KEY

Purpose: Sends 'Did you forget to clock out today?' reminder emails via the scheduled auto-close function.

Where to get it: https://resend.com/api-keys — create a key with Send access. Only needed if you add the forgot-to-clock-out reminder follow-up.

Preflight checklist

  • You're in a fresh Lovable project (Vite + React + TypeScript + Tailwind + shadcn/ui already scaffolded)
  • You're on Pro $25/mo — the full chain is 60-120 credits, which fits within Pro's 100/mo allowance with possibly one $15 top-up if you iterate on the geo-fence logic
  • You have a venue or event location in mind — you'll need lat/lng coordinates for the events table (find them in Google Maps: right-click any point → the coordinates appear at the top of the context menu)
  • Credit estimates are extrapolated from HR/CRUD-with-timestamps cousin patterns — no documented exact-match Lovable attendance build exists; treat numbers as ranges

The starter prompt — paste this first

Copy this. Paste it into Lovable Agent Mode (the default chat at the bottom-left of the editor). Hit send.

lovable-agent-mode.txt
~30-50 credits
Build an attendance tracking app with QR check-in, geo-fence validation, and admin reporting. Stack assumptions: React + Vite + TypeScript + Tailwind + shadcn/ui already scaffolded by Lovable, React Router for routes, Supabase JS client against Lovable Cloud.

## Database schema (create one migration)

Create three tables in the public schema:

1. `events` — id (uuid pk default gen_random_uuid()), name (text not null), location_lat (numeric(9,6)), location_lng (numeric(9,6)), radius_m (int not null default 100), qr_token (text unique), qr_token_expires_at (timestamptz), scheduled_start (timestamptz), scheduled_end (timestamptz), status (text not null default 'scheduled' check (status in ('scheduled','active','closed'))), created_at (timestamptz default now()).

2. `profiles` — id (uuid pk references auth.users(id) on delete cascade), full_name (text not null), employee_code (text unique), default_role (text not null default 'attendee' check (default_role in ('attendee','supervisor','admin'))), timezone (text not null default 'UTC'), created_at (timestamptz default now()).

3. `attendance` — id (uuid pk default gen_random_uuid()), user_id (uuid not null references auth.users(id)), event_id (uuid references events(id)), check_in_at (timestamptz not null default now()), check_out_at (timestamptz), method (text not null check (method in ('manual','qr','geo','admin'))), check_in_lat (numeric(9,6)), check_in_lng (numeric(9,6)), check_in_accuracy_m (int), notes (text), created_at (timestamptz default now()), CONSTRAINT attendance_user_event_day_key UNIQUE (user_id, event_id, (date_trunc('day', check_in_at))).

## RLS policies

Enable RLS on all three tables.
- attendance: SELECT for authenticated where user_id = auth.uid() (per-user own history); also SELECT for admin via `has_role('admin')`. INSERT only via check_in RPC (no direct INSERT policy for users). UPDATE only for admin.
- events: SELECT for any authenticated user; INSERT/UPDATE/DELETE for admin only.
- profiles: SELECT for user's own row and admin; UPDATE for user's own row.

Create SECURITY DEFINER plpgsql functions:
- `has_role(p_role text)` returns boolean: RETURN (auth.jwt() ->> 'app_metadata')::jsonb ->> 'role' = p_role;
- `check_in(p_event_id uuid, p_token text, p_lat numeric, p_lng numeric, p_accuracy_m int)` returns uuid (the new attendance row id). The function must:
  1. Look up the event: `SELECT * FROM events WHERE id = p_event_id FOR SHARE` (shared lock, not exclusive — we don't need row exclusivity for attendance).
  2. Validate status = 'active' — raise 'Event is not accepting check-ins' if not.
  3. Validate qr_token = p_token AND now() < qr_token_expires_at — raise 'QR code has expired or is invalid'.
  4. Validate haversine: if location_lat IS NOT NULL, compute haversine distance between (location_lat, location_lng) and (p_lat, p_lng). If (distance_m - p_accuracy_m) > radius_m, raise 'You appear to be outside the venue (Xm away, Ym accuracy). Check in from on-site.' (format actual distances in the message).
  5. INSERT into attendance (user_id=auth.uid(), event_id, check_in_at=now(), method='qr', check_in_lat, check_in_lng, check_in_accuracy_m) — if the UNIQUE constraint fires (duplicate), catch the exception and raise 'You are already checked in for this event today. Go to /me to check out.'.
  6. Return the new attendance.id.
- `record_check_out(p_attendance_id uuid)` returns void — UPDATE attendance SET check_out_at = now() WHERE id = p_attendance_id AND user_id = auth.uid() AND check_out_at IS NULL.

Add a haversine SQL helper function:
`CREATE OR REPLACE FUNCTION haversine_m(lat1 numeric, lng1 numeric, lat2 numeric, lng2 numeric) RETURNS numeric AS $$ SELECT 2 * 6371000 * asin(sqrt(sin(radians((lat2-lat1)/2))^2 + cos(radians(lat1)) * cos(radians(lat2)) * sin(radians((lng2-lng1)/2))^2)) $$ LANGUAGE sql IMMUTABLE;`

Grant EXECUTE on check_in and record_check_out to authenticated.

## Layout

Two layouts:
1. `src/layouts/AppLayout.tsx` — mobile-first single column for attendees: top bar with logo, user avatar, hamburger menu. No sidebar (check-in is used on phones).
2. `src/layouts/AdminLayout.tsx` — desktop sidebar (Dashboard, Events, Attendance Log, Reports, Settings) + scrollable main area.

## Pages and routes

Create these routes in src/App.tsx:
- /check-in (CheckIn.tsx) — reads ?token= and ?event_id= from the URL query params (the QR code encodes these). Shows event name and location. Large 'Check In' button = CheckInButton component. After clicking: request browser geolocation, call check_in RPC, show success or error.
- /me (Me.tsx) — auth-gated. Shows the user's attendance history sorted by check_in_at DESC. Each active session (check_out_at IS NULL) shows a green 'Active — Check Out' button that calls record_check_out.
- /admin (Dashboard.tsx) — admin-gated (has_role('admin')). 3 KPI cards: Today's check-ins (count), Currently checked in (active sessions), Events today (events with status='active').
- /admin/events (Events.tsx) — events CRUD table. Row click opens a Sheet with name, lat/lng, radius_m, scheduled_start/end, status controls. 'Generate QR' button on each active event opens a Dialog with a QRCodeDisplay component.
- /admin/log (Log.tsx) — filterable attendance log with: date range picker, event selector, user search by name/employee_code, status filter (checked in / checked out / all). AttendanceTable component. CSV export button.
- /login (Login.tsx) — email+password form.

## Key components

Create: CheckInButton (handles navigator.geolocation.getCurrentPosition() with a clear permission-denied error message, calls check_in RPC, shows success with event name + check-in time or error from RPC), QRCodeDisplay (uses the qrcode.react library to render a QR code for the URL `https://your-app.lovable.app/check-in?event_id={id}&token={token}` — update the base URL to the real domain after deploying), AttendanceTable (shadcn Table with date range filter, user filter, event filter, check-in/out times, duration in hours if both times present, status chip), CheckedInBadge (green pulse dot + 'Currently checked in').

## generate-qr-token Edge Function

Create supabase/functions/generate-qr-token/index.ts. The function accepts {event_id} in the body, verifies the caller has role='admin', generates a cryptographically random 32-byte token, updates events SET qr_token = hex_token, qr_token_expires_at = now() + interval '15 minutes' WHERE id = event_id, and returns {token, expires_at}. The QRCodeDisplay component polls this endpoint every 14 minutes to rotate the token.

## Styling

Light mode default (legibility for indoor use). Primary color teal-600. /check-in page uses very large tap targets (min-h-16 buttons, text-xl labels) — this is used on phones by people in motion. Admin pages use standard desktop density. No clever animations — clarity over polish.

What this prompt generates

  • Creates one SQL migration with 3 tables, RLS policies, haversine helper, check_in RPC with token validation + geo-fence + UNIQUE constraint protection, and record_check_out RPC
  • Scaffolds mobile-first AppLayout for attendees and sidebar AdminLayout for managers
  • Generates /check-in flow with geolocation permission handling and RPC call, /me history with check-out button
  • Builds admin /admin/events with QR generation dialog, /admin/log with filters and CSV export, and KPI dashboard
  • Creates CheckInButton, QRCodeDisplay, AttendanceTable, and CheckedInBadge components

Paste into: Lovable Agent Mode (the default chat at the bottom-left of the editor)

Expected output

What Lovable will generate after the starter prompt runs successfully.

Files
supabase/migrations/0001_attendance_schema.sql

3 tables + RLS + haversine helper + check_in RPC + record_check_out RPC

src/layouts/AppLayout.tsx

Mobile-first layout for attendee-facing pages

src/layouts/AdminLayout.tsx

Desktop sidebar layout for admin pages

src/components/AuthGuard.tsx

Redirects to /login if not authenticated

src/components/AdminGuard.tsx

Redirects to /unauthorized if not admin

src/components/CheckInButton.tsx

Handles geo permission, calls check_in RPC, shows success/error

src/components/QRCodeDisplay.tsx

Renders QR code for check-in URL using qrcode.react, auto-rotates every 14 min

src/components/AttendanceTable.tsx

Filterable table with date range, event, user search, CSV export

src/components/CheckedInBadge.tsx

Green pulse badge for active sessions

src/pages/CheckIn.tsx

Mobile check-in flow reading ?token and ?event_id from URL

src/pages/Me.tsx

User attendance history with active check-out button

src/pages/admin/Dashboard.tsx

KPI cards for today's activity

src/pages/admin/Events.tsx

Events CRUD with QR generation dialog

src/pages/admin/Log.tsx

Filterable attendance log with CSV export

supabase/functions/generate-qr-token/index.ts

Rotates QR token every 15 minutes, admin-only

Routes
/check-in

QR scan target — mobile check-in flow with geo validation

/me

Auth-gated personal attendance history with check-out

/admin

KPI dashboard for admins

/admin/events

Event CRUD with QR code generation

/admin/log

Filterable attendance log with CSV export

/login

Email+password sign-in

DB Tables
attendance
ColumnType
iduuid primary key
user_iduuid not null references auth.users(id)
event_iduuid references events(id)
check_in_attimestamptz not null default now()
check_out_attimestamptz
methodtext check in (manual,qr,geo,admin)
check_in_latnumeric(9,6)
check_in_lngnumeric(9,6)
check_in_accuracy_mint

RLS: Per-user SELECT; admin full access; INSERT only via check_in RPC — UNIQUE on (user_id, event_id, date_trunc(day, check_in_at))

events
ColumnType
iduuid primary key
nametext not null
location_latnumeric(9,6)
location_lngnumeric(9,6)
radius_mint default 100
qr_tokentext unique
qr_token_expires_attimestamptz
statustext check in (scheduled,active,closed)

RLS: Authenticated SELECT; admin write

profiles
ColumnType
iduuid primary key references auth.users(id)
full_nametext not null
employee_codetext unique
timezonetext default UTC

RLS: Per-user read/write own; admin read all

Components
CheckInButton

Handles geolocation permission, calls check_in RPC, shows clear success or error

QRCodeDisplay

Renders auto-rotating QR code using qrcode.react

AttendanceTable

Filterable log with date range, user search, event selector, duration calculation

CheckedInBadge

Green pulse dot for currently active sessions

Follow-up prompts

Paste these into Agent Mode one by one, in order, after the starter prompt finishes.

1

Add check-out flow and auto-close stale shifts

Check-out flow on /me and nightly auto-close for users who forget to clock out

~25-35 credits
prompt
Add two check-out features:

1. In /me, each attendance row where check_out_at IS NULL shows a green 'Check Out Now' button. Clicking it calls record_check_out(p_attendance_id) via supabase.rpc(). Show a toast with the total duration in hours and minutes. Refresh the list.

2. Create a scheduled Edge Function at supabase/functions/auto-close-stale-shifts/index.ts. This function runs nightly at 23:59 (schedule via Cloud tab → Edge Functions → Schedule). It finds all attendance rows where check_in_at::date = current_date AND check_out_at IS NULL. For each, sets check_out_at = check_in_at + interval '8 hours' and method = 'admin', adds a note = 'Auto-closed by system: no clock-out recorded'. Returns a count of closed sessions.

After generating the function, go to Cloud tab → Edge Functions → auto-close-stale-shifts → Schedule and set it to run daily.

When to use: Immediately after the starter prompt — stale open shifts make your duration calculations meaningless

2

Add CSV export with timezone-correct timestamps

Timezone-correct CSV export suitable for payroll processing in Gusto, Xero, or ADP

~25-30 credits
prompt
Create a Supabase Edge Function at supabase/functions/export-csv/index.ts. The function is called from the CSV export button in /admin/log with optional query parameters: ?start_date=, ?end_date=, ?event_id=, ?tz=America/New_York (timezone for timestamp display).

The function should:
1. Query attendance joined to profiles and events for the given filters.
2. Convert check_in_at and check_out_at to the requested timezone using Postgres AT TIME ZONE.
3. Calculate duration_hours as EXTRACT(EPOCH FROM (check_out_at - check_in_at))/3600 rounded to 2 decimal places (NULL if no check-out).
4. Format each row as CSV: employee_code, full_name, event_name, check_in_at_local (YYYY-MM-DD HH:MM), check_out_at_local, duration_hours, method, notes.
5. Return a text/csv response with Content-Disposition: attachment; filename="attendance-[start_date]-[end_date].csv".

In AttendanceTable, update the 'Export CSV' button to call this Edge Function with the current filter state and trigger a file download using URL.createObjectURL.

When to use: When your admin needs to feed data into a payroll tool

3

Add weekly hours report with Recharts

Weekly hours report per employee with Recharts bar chart and drill-down

~30-40 credits
prompt
Create an /admin/reports page accessible from the admin sidebar.

The page should show:
1. A date range picker (default: current week Mon-Sun).
2. A Recharts BarChart showing total hours worked per user for the selected week. Each bar group = one user. X-axis = user full_name, Y-axis = hours (0-60 for a weekly view). Bars colored teal-500.
3. A data table below the chart with columns: Employee Code, Full Name, Days Worked, Total Hours, Avg Hours/Day. Clicking a row opens a Sheet with that user's day-by-day breakdown for the week (attendance rows grouped by date, each row shows event name + duration).

Data query: SELECT profiles.employee_code, profiles.full_name, count(DISTINCT attendance.check_in_at::date) as days_worked, COALESCE(SUM(EXTRACT(EPOCH FROM (check_out_at - check_in_at))/3600), 0) as total_hours FROM attendance JOIN profiles ON profiles.id = attendance.user_id WHERE check_in_at >= :start AND check_in_at < :end GROUP BY profiles.id ORDER BY total_hours DESC.

Add 'Reports' to the admin sidebar nav in AdminLayout.

When to use: When you have 10+ users and need to review weekly hours at a glance

4

Add Resend forgot-to-clock-out reminders

Automated 7pm reminder emails for employees with open check-in sessions

~25-35 credits
prompt
Add a Resend email reminder for users who have an open check-in session at 19:00 (7pm) local time. Update the auto-close-stale-shifts Edge Function to also send reminders before it runs auto-close.

Create a separate Edge Function at supabase/functions/send-checkout-reminders/index.ts. The function:
1. Finds all attendance rows where check_in_at::date = current_date AND check_out_at IS NULL AND check_in_at < now() - interval '8 hours'.
2. For each, looks up profiles.timezone and the user's email from auth.users.
3. Converts check_in_at to the user's timezone.
4. Calls Resend to send: Subject 'Did you forget to clock out today?', Body 'You checked in at [local_time] for [event_name]. Did you forget to clock out? Click here to check out: [app-url]/me'.
5. Returns {sent_count}.

Schedule this function via Cloud tab → Edge Functions → send-checkout-reminders → Schedule at 19:00 UTC (adjust to your target timezone). Add RESEND_API_KEY to Cloud tab → Secrets first.

When to use: When stale open shifts become a regular problem — typically after 2+ weeks of usage

5

Add per-event admin reports and export

Per-event attendance summary with on-time/late analysis and instant close action

~30-40 credits
prompt
Add event-level reporting to /admin/events. When an admin clicks an event row, the Sheet already shows the event details. Add a second tab 'Attendance' to the Sheet with:
1. A summary: Total attendees (count of attendance rows for this event), On-time (checked in within 15 minutes of scheduled_start), Late (checked in more than 15 minutes after scheduled_start), Did not check out (check_out_at IS NULL and event is closed).
2. A list of all attendees for this event sorted by check_in_at: avatar initials, full_name, employee_code, check_in_at (local time), check_out_at (local time), duration, method chip.
3. An 'Export this event' button that calls export-csv with event_id = current event and no date filter.

Also add a 'Close event' button (admin only) that sets events.status = 'closed' and triggers auto-close-stale-shifts for this specific event immediately.

When to use: When you run recurring events and need per-event summary reports

Common errors

Real error strings you'll see. Find yours, paste the fix prompt.

duplicate key value violates unique constraint "attendance_user_event_day_key"

The user tapped Check In twice for the same event on the same day — the UNIQUE constraint is working correctly. This is not a bug; it's the buddy-punch prevention guard.

Fix — paste into Lovable Agent Mode
Catch this specific error in CheckInButton and show a friendly message: 'You are already checked in for today. Head to /me to check out.' Do not surface the raw Postgres error to the user.
User denied geolocation / GeolocationPositionError: User denied Geolocation

The user clicked 'Block' on the browser geolocation prompt, or the page is served over HTTP (geolocation requires HTTPS even on localhost on some browsers).

Fix — paste into Lovable Agent Mode
In CheckInButton, handle the PERMISSION_DENIED error code (error.code === 1) with a clear message: 'Location access is required to check in. Open your browser settings and allow location for this site, then tap Check In again.' Show a help link. For testing in Lovable preview, the preview URL is already HTTPS — geolocation works there.
You appear to be outside the venue (280m away, 350m accuracy)

Indoor GPS accuracy is often 50-500m, and your events.radius_m is set too tight for the venue. The check_in RPC correctly factors accuracy_m into the calculation, but the radius is still smaller than the GPS uncertainty.

Fix — paste into Lovable Agent Mode
In /admin/events, update the event's radius_m to 200-500m for indoor venues. As a rule of thumb: office building = 200m, large warehouse = 400m, outdoor site = 100m. The haversine check uses (distance - accuracy_m) <= radius_m so you get credit for GPS uncertainty, but if accuracy_m itself is 350m you still need radius_m > 0m to ever check in.
permission denied for function check_in

The check_in RPC was created but EXECUTE was not granted to the authenticated role.

Manual fix

Open Cloud tab → Database → SQL Editor and run: GRANT EXECUTE ON FUNCTION check_in(uuid, text, numeric, numeric, integer) TO authenticated; Also grant record_check_out: GRANT EXECUTE ON FUNCTION record_check_out(uuid) TO authenticated;

Admin sees no rows in /admin/log even though check-ins exist

Only the per-user RLS policy (user_id = auth.uid()) was created, so the admin querying without their own user_id filter gets 0 rows.

Fix — paste into Lovable Agent Mode
Add an admin SELECT policy on the attendance table: CREATE POLICY admin_read_all ON attendance FOR SELECT TO authenticated USING (has_role('admin')); Keep the per-user policy intact alongside it — they stack with OR logic so both the user's own rows and the admin's full access work simultaneously.
check_in function works in preview but 'function check_in does not exist' in production

You created the function in the SQL Editor during preview development but the migration file was not saved, so the production database does not have it.

Fix — paste into Lovable Agent Mode
Open the migrations folder in your project and confirm the check_in function definition is inside a .sql migration file (not just run in the SQL Editor ad hoc). If it's missing, add it to the migration file and re-apply: Cloud tab → Database → Migrations → Run pending migrations. Or paste the function definition directly into the production SQL Editor.

Cost reality

What this build actually costs — no surprises on your card.

Recommended Lovable plan

Pro $25/mo. The full 5-follow-up chain is 60-120 credits, which fits within Pro's monthly 100-credit allowance — you may need one $15 top-up if you iterate on the geo-fence logic. After launch, you can drop to Free plan since you won't be iterating daily.

Monthly run cost breakdown

~60-120 credits (starter ~30-50 credits, five follow-ups ~70-100 credits if you do them all; most readers stop after follow-ups #1 and #2 for about 80 credits total). These are extrapolated estimates from HR/CRUD cousin patterns. total
ItemCost
Lovable Pro

Drop to Free after the build is stable — attendance apps don't need constant iteration

$25/mo
Lovable Cloud / Supabase Free

Free tier holds 500K attendance rows — decades of small-business usage. This is one of the cheapest apps to run in the 31-page series.

$0/mo
Resend

Free up to 3,000 emails/month — enough for daily checkout reminders to a team of 100

$0/mo
Custom domain$10-15/yr

Scaling notes: Costs basically do not scale until you hit 50,000 MAU (Supabase Pro at $25/mo) or 500MB DB (~5 million attendance rows, roughly 10+ years of daily check-ins for a 100-person team). Attendance is a fundamentally low-data application — this is one of the cheapest categories in the 31-page series to run forever.

Production checklist

Steps to take before you share the URL with real users.

Domain & SSL

  • Publish to a custom domain so QR codes point to your domain

    Click the Publish icon (top right in Lovable) → Settings → Custom Domain → enter your domain. Follow the CNAME DNS instructions. Once live, update QRCodeDisplay to use your custom domain base URL instead of the lovable.app preview URL.

Security

  • Test check_in RPC with a non-admin account in incognito

    Open an incognito window, sign in as a regular attendee (not the admin or the Lovable preview account). Attempt to directly insert into attendance via the Supabase JS client without going through the check_in RPC — should return permission denied. Confirm the QR token check works by scanning an expired QR code.

  • Rotate QR tokens before each event

    In /admin/events, click Generate QR on any event before the event starts. The QRCodeDisplay shows the code and auto-rotates every 14 minutes. Print the code or display it on a screen at the door — the printed code stops working after 15 minutes, which is the intended behavior.

Timestamps

  • Confirm check_in_at is stored as UTC timestamptz

    In Cloud tab → Database → SQL Editor, run: SELECT check_in_at, check_in_at AT TIME ZONE 'America/New_York' FROM attendance LIMIT 5. The first column should be UTC, the second should be local time. If check_in_at is stored in a local timezone, you have a DST problem — fix the migration to use default now() (which is UTC in Postgres).

Backups

  • Confirm daily backups are enabled

    Cloud tab → Database → Backups — confirm daily backups are on. Supabase Free retains backups for ~7 days. For a payroll-critical attendance system, consider upgrading to Supabase Pro ($25/mo) for point-in-time recovery.

Frequently asked questions

Can I run this on the Free Lovable plan?

You can start on Free for the first few prompts, but the full build including the check_in RPC and Edge Functions will likely exceed the 30-credit monthly cap. The starter prompt alone runs 30-50 credits. Plan on Pro $25/mo from the start. After the build is stable and you stop iterating, you can drop back to Free — you won't need daily credits once it's deployed.

How do I prevent buddy-punching?

Three controls work together. First, the UNIQUE constraint on (user_id, event_id, date) blocks the simplest case — two people using the same account to check in twice, or one person tapping Check In twice accidentally. Second, the short-lived QR token (15-minute expiry, rotated by generate-qr-token) means a screenshot of the QR code becomes useless after 15 minutes. Third, the geo-fence check inside the check_in RPC rejects check-ins from outside the venue radius. The combination of QR + geo is much harder to defeat than either alone — someone would need to be physically present at the venue AND have the current QR code. A determined adversary with two devices and GPS spoofing can still defeat this, but that's a much higher bar than most small-business attendance fraud.

What if my venue has bad GPS reception?

Increase the radius_m for that event in /admin/events. Indoor GPS accuracy varies from 50m (open atrium) to 500m (underground, thick concrete). The check_in RPC factors check_in_accuracy_m into the comparison — it checks if (distance - accuracy) <= radius, giving the user credit for GPS uncertainty. Set radius_m to 200-400m for most office buildings and 400-600m for warehouses or basements. If GPS is completely unreliable at your venue (e.g. a basement or a building with GPS-blocking film on windows), you can disable geo-fence per event by setting location_lat to NULL — the check_in RPC skips the haversine check when location_lat IS NULL.

Can I export to payroll software like Gusto, Xero, or ADP?

The export-csv follow-up generates a CSV with employee_code, full_name, check-in/out times in local timezone, and decimal hours worked. All major payroll platforms (Gusto, Xero, ADP, QuickBooks) accept CSV imports. The column names in the export match what these platforms typically expect for time-entry imports. For automated sync (rather than manual CSV upload), you'd need to write to the payroll platform's API from a scheduled Edge Function — that is a custom integration, not covered in this kit.

What about offline check-in when WiFi is down?

This kit requires an internet connection for check-in — the Edge Function call and geo validation happen server-side. There is no offline mode. If your venue has unreliable WiFi, consider a few options: ensure attendees use cellular data (4G/5G) rather than WiFi for check-in; deploy a local WiFi access point that connects to the internet via cellular backup; or for truly offline scenarios (remote sites, basements), use a different approach such as NFC card taps logged locally and synced when connectivity is restored — that is a hardware integration outside this kit's scope.

Can I use barcode badge scans instead of QR codes?

Yes, with minor changes. Replace the QR token with a barcode value stored in profiles.employee_code (already in the schema). The admin scans the barcode with a USB barcode scanner (which types the code into a focused input), and the check-in form auto-submits when the input receives a barcode value. You'd replace the check_in RPC's token validation with an employee_code lookup instead. The geo-fence check can remain or be disabled depending on whether your venue requires it.

Can RapidDev build a custom attendance system for my fleet of locations?

If your build outgrows this prompt kit and you need custom architecture, RapidDev builds production-grade Lovable apps at $13K-$25K — book a free 30-minute consultation at rapidevelopers.com.

Need a production-grade version?

RapidDev builds production-grade Lovable apps at $13K–$25K. Book a free 30-minute consultation.

Book a free consultation

30-min call. No commitment.

Want this built for you?

We ship production apps at a fixed price — $13K–$25K, 6–10 weeks, source code yours. You've seen what it takes; we do it every week.

Get a fixed-price quote

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.