Build a clock-in/clock-out attendance app with V0 using Next.js and Supabase. You'll get GPS-verified time tracking, a manager dashboard with department views, leave request management, and exportable CSV attendance reports — all in about 1-2 hours without any local setup.
What you're building
Small businesses and HR teams need a reliable way to track employee attendance without expensive enterprise software. A simple clock-in/clock-out system with GPS verification prevents buddy punching and provides accurate time records for payroll.
V0 makes this fast to build by generating the attendance UI and tracking logic from natural language prompts. Connect Supabase via the Connect panel for instant database provisioning — it handles employee records, attendance logs, and leave requests with row-level security so employees only see their own data.
The architecture uses a Next.js App Router client component for the clock-in interface (needs browser geolocation API), Server Components for manager dashboards, an API route for location-verified clock events, and Supabase with a partial unique index to prevent duplicate open sessions per employee.
Final result
A complete attendance tracking system with GPS-verified clock-in/out, a manager dashboard with department filtering, leave request management, and exportable monthly attendance reports.
Tech stack
Prerequisites
- A V0 account (Premium plan recommended for multi-page projects)
- A Supabase project (free tier works — connect via V0's Connect panel)
- A Google Maps API key for location verification (optional but recommended)
- Basic understanding of your company's attendance and leave policies
Build steps
Set up the project and database schema for attendance tracking
Create a new V0 project and connect Supabase via the Connect panel. Then prompt V0 to create the employee, attendance, and leave request tables with proper constraints and a partial unique index that prevents duplicate open clock-in sessions.
1// Paste this prompt into V0's AI chat:2// Build an attendance tracking system with Supabase. Create these tables:3// 1. employees: id (uuid PK), user_id (uuid FK to auth.users), full_name (text), department (text), role (text check 'employee','manager'), is_active (boolean default true), created_at (timestamptz)4// 2. attendance_records: id (uuid PK), employee_id (uuid FK), clock_in (timestamptz), clock_out (timestamptz), clock_in_location (point), clock_out_location (point), total_hours (numeric generated always as extract(epoch from clock_out - clock_in)/3600), notes (text), status (text default 'present')5// 3. leave_requests: id (uuid PK), employee_id (uuid FK), leave_type (text), start_date (date), end_date (date), reason (text), status (text default 'pending'), approved_by (uuid FK)6// Add a partial unique index: CREATE UNIQUE INDEX ON attendance_records (employee_id) WHERE clock_out IS NULL7// Add RLS so employees see only their own records, managers see their department.Pro tip: The partial unique index is the key to preventing duplicate clock-ins. It ensures only one row per employee can have a NULL clock_out value at any time — if an employee tries to clock in twice, Supabase returns a constraint violation error.
Expected result: Supabase is connected with all tables created, RLS policies applied, and the partial unique index in place to prevent duplicate clock-in sessions.
Build the clock-in/clock-out interface with GPS capture
Create the employee-facing page with a large toggle button for clocking in and out. The component uses the browser's Geolocation API to capture coordinates and sends them to an API route that verifies the location server-side before recording the event.
1import { NextRequest, NextResponse } from 'next/server'2import { createClient } from '@supabase/supabase-js'34const supabase = createClient(5 process.env.SUPABASE_URL!,6 process.env.SUPABASE_SERVICE_ROLE_KEY!7)89export async function POST(req: NextRequest) {10 const { employeeId, action, latitude, longitude } = await req.json()11 const location = `(${longitude},${latitude})`1213 if (action === 'clock_in') {14 const { data, error } = await supabase15 .from('attendance_records')16 .insert({17 employee_id: employeeId,18 clock_in: new Date().toISOString(),19 clock_in_location: location,20 })21 .select()22 .single()2324 if (error?.code === '23505') {25 return NextResponse.json(26 { error: 'You already have an open clock-in session' },27 { status: 409 }28 )29 }30 if (error) return NextResponse.json({ error: error.message }, { status: 500 })31 return NextResponse.json({ data })32 }3334 if (action === 'clock_out') {35 const { data, error } = await supabase36 .from('attendance_records')37 .update({38 clock_out: new Date().toISOString(),39 clock_out_location: location,40 })41 .eq('employee_id', employeeId)42 .is('clock_out', null)43 .select()44 .single()4546 if (error) return NextResponse.json({ error: error.message }, { status: 500 })47 return NextResponse.json({ data })48 }4950 return NextResponse.json({ error: 'Invalid action' }, { status: 400 })51}Expected result: The API route handles clock-in and clock-out actions. Clock-in creates a new record with location; clock-out updates the open record. Duplicate clock-ins return a 409 conflict error.
Create the manager dashboard with department filtering
Build the manager-facing dashboard that shows today's attendance status for all department employees, weekly summaries, and the ability to filter by department. Use Server Components for efficient data fetching.
1// Paste this prompt into V0's AI chat:2// Build a manager attendance dashboard at app/dashboard/page.tsx.3// Requirements:4// - Show today's attendance summary: Present count, Absent count, On Leave count in shadcn/ui Cards5// - Display a Table of today's attendance records with columns: Employee Name, Clock In Time, Clock Out Time, Total Hours, Status Badge6// - Add a Select dropdown to filter by department7// - Show a monthly Progress bar per employee showing attendance percentage (days present / working days)8// - Add Tabs for Today, This Week, This Month views9// - Weekly view shows a grid of days with green (present), red (absent), yellow (leave) indicators per employee10// - Use Server Components to fetch data from Supabase11// - Managers should only see employees in their department (enforce via Supabase RLS)Pro tip: Use Design Mode (Option+D) to adjust the dashboard Card layout, Progress bar colors, and attendance grid spacing without spending any credits.
Expected result: The manager dashboard shows today's attendance with department filtering, a weekly attendance grid, and monthly Progress bars per employee.
Build the leave request system with approval workflow
Create the leave request form for employees and the approval interface for managers. Employees submit requests with date ranges and reasons. Managers see pending requests and can approve or reject them with a single click.
1'use server'23import { createClient } from '@supabase/supabase-js'4import { revalidatePath } from 'next/cache'56const supabase = createClient(7 process.env.SUPABASE_URL!,8 process.env.SUPABASE_SERVICE_ROLE_KEY!9)1011export async function submitLeaveRequest(12 employeeId: string,13 leaveType: string,14 startDate: string,15 endDate: string,16 reason: string17) {18 const { error } = await supabase.from('leave_requests').insert({19 employee_id: employeeId,20 leave_type: leaveType,21 start_date: startDate,22 end_date: endDate,23 reason,24 })2526 if (error) throw new Error(error.message)27 revalidatePath('/leave')28}2930export async function handleLeaveRequest(31 requestId: string,32 status: 'approved' | 'rejected',33 managerId: string34) {35 const { error } = await supabase36 .from('leave_requests')37 .update({ status, approved_by: managerId })38 .eq('id', requestId)39 .eq('status', 'pending')4041 if (error) throw new Error(error.message)42 revalidatePath('/dashboard')43}Expected result: Employees can submit leave requests with type, date range, and reason. Managers see pending requests and can approve or reject them, updating the status in Supabase.
Add exportable attendance reports
Create a reports page that generates monthly attendance summaries by department. Managers can view reports and export them as CSV files for payroll processing.
1// Paste this prompt into V0's AI chat:2// Build an attendance reports page at app/reports/page.tsx.3// Requirements:4// - Select month and department using DatePicker and Select5// - Display a summary Table: Employee Name, Days Present, Days Absent, Days on Leave, Total Hours, Late Arrivals6// - Show department averages at the bottom7// - Add a "Download CSV" Button that generates and downloads a CSV file client-side8// - Add a PieChart (Recharts) showing the breakdown of present/absent/leave days for the department9// - Use Server Components for the initial data fetch10// - The CSV should include all columns plus clock-in and clock-out times for each dayExpected result: The reports page shows a monthly attendance summary table filterable by department with a downloadable CSV export and a PieChart showing attendance distribution.
Complete code
1import { NextRequest, NextResponse } from 'next/server'2import { createClient } from '@supabase/supabase-js'34const supabase = createClient(5 process.env.SUPABASE_URL!,6 process.env.SUPABASE_SERVICE_ROLE_KEY!7)89export async function POST(req: NextRequest) {10 const { employeeId, action, latitude, longitude } = await req.json()11 const location = `(${longitude},${latitude})`1213 if (action === 'clock_in') {14 const { data, error } = await supabase15 .from('attendance_records')16 .insert({17 employee_id: employeeId,18 clock_in: new Date().toISOString(),19 clock_in_location: location,20 })21 .select()22 .single()2324 if (error?.code === '23505') {25 return NextResponse.json(26 { error: 'Already clocked in' },27 { status: 409 }28 )29 }30 if (error) {31 return NextResponse.json({ error: error.message }, { status: 500 })32 }33 return NextResponse.json({ data })34 }3536 if (action === 'clock_out') {37 const { data, error } = await supabase38 .from('attendance_records')39 .update({40 clock_out: new Date().toISOString(),41 clock_out_location: location,42 })43 .eq('employee_id', employeeId)44 .is('clock_out', null)45 .select()46 .single()4748 if (error) {49 return NextResponse.json({ error: error.message }, { status: 500 })50 }51 return NextResponse.json({ data })52 }5354 return NextResponse.json({ error: 'Invalid action' }, { status: 400 })55}5657export async function GET(req: NextRequest) {58 const { searchParams } = new URL(req.url)59 const employeeId = searchParams.get('employee_id')6061 const { data } = await supabase62 .from('attendance_records')63 .select('*')64 .eq('employee_id', employeeId!)65 .is('clock_out', null)66 .maybeSingle()6768 return NextResponse.json({ active_session: data })69}Customization ideas
Add geofencing verification
Define an allowed radius around the office location and reject clock-in attempts from outside the geofence by calculating the Haversine distance between the employee's coordinates and the office coordinates in the API route.
Add overtime calculations
Extend the reporting logic to calculate overtime hours for employees who clock more than 8 hours per day or 40 hours per week, with configurable overtime rates per department.
Add push notifications for late arrivals
Use Supabase Edge Functions with a cron trigger to check if employees have not clocked in by their expected start time and send notification emails via Resend.
Add shift scheduling
Create a shift management system where managers define shifts (morning, afternoon, night) and assign employees to shifts with a Calendar-based drag-and-drop interface.
Common pitfalls
Pitfall: Storing GOOGLE_MAPS_API_KEY with NEXT_PUBLIC_ prefix
How to avoid: Store GOOGLE_MAPS_API_KEY in V0's Vars tab without any prefix. Proxy geolocation verification through your API route at app/api/attendance/route.ts.
Pitfall: Not preventing duplicate clock-in sessions
How to avoid: Add a partial unique index: CREATE UNIQUE INDEX ON attendance_records (employee_id) WHERE clock_out IS NULL. This guarantees only one open session exists per employee at the database level.
Pitfall: Calculating total hours on the client side
How to avoid: Use a PostgreSQL generated column: total_hours numeric GENERATED ALWAYS AS (EXTRACT(EPOCH FROM clock_out - clock_in) / 3600). This calculates hours at the database level and cannot be tampered with.
Pitfall: Not handling timezone differences for distributed teams
How to avoid: Always use timestamptz (timestamp with time zone) in Supabase and store the employee's timezone in the employees table. Display times converted to the employee's local timezone on the frontend.
Best practices
- Use a partial unique index on attendance_records to prevent duplicate open clock-in sessions at the database level
- Store GPS coordinates as Supabase point type for efficient location-based queries and geofencing
- Use Server Components for the manager dashboard to keep Supabase queries server-side and secure
- Store GOOGLE_MAPS_API_KEY in V0's Vars tab without NEXT_PUBLIC_ prefix and proxy verification through an API route
- Use Supabase RLS policies scoped by department so managers only see their own team's attendance records
- Use Design Mode (Option+D) to adjust the clock-in button size, dashboard layout, and status colors without spending credits
- Always use timestamptz columns to handle timezone differences correctly for distributed teams
- Use PostgreSQL generated columns for total_hours calculations to prevent client-side manipulation
AI prompts to try
Copy these prompts to build this project faster.
I'm building a clock-in/clock-out attendance app with Next.js App Router and Supabase. I need GPS location capture, a partial unique index to prevent duplicate clock-ins, leave request management, and manager dashboards with department filtering. Help me design the schema and the GPS verification flow.
Build a geolocation-verified clock-in component using the browser Geolocation API. The component should: get the user's position with navigator.geolocation.getCurrentPosition, show a loading state while acquiring GPS, POST the coordinates to /api/attendance with the action type, display the current session timer after clock-in, and show the total hours after clock-out. Use shadcn/ui Button, Card, and Badge components with Tailwind CSS.
Frequently asked questions
How does the GPS clock-in verification work?
When an employee taps the clock-in button, the browser's Geolocation API captures their coordinates. These are sent to the API route which can optionally verify them against your office location using the Google Maps API. The coordinates are stored with the attendance record for audit purposes.
Can employees clock in from their phones?
Yes. The app is fully responsive and works in any mobile browser. The browser's Geolocation API works on both iOS and Android. For the best experience, employees should add the site to their home screen as a PWA.
What V0 plan do I need for an attendance app?
V0 Premium is recommended because the attendance app requires multiple pages (clock-in, dashboard, reports, leave requests) and API routes. The free plan's limited credits may not cover the full build.
How do I handle employees who forget to clock out?
Set up a Supabase Edge Function triggered by a cron schedule that runs at the end of each workday. It checks for open sessions (clock_out IS NULL) and either auto-clocks them out at the expected end time or flags them for manager review.
How do I deploy the attendance app?
Click Share then Publish to Production in V0 for instant Vercel deployment. The app will be accessible from any browser. For custom domains, configure them in the Vercel Dashboard after publishing.
Can I integrate this with payroll software?
Yes. Add an API route that exports attendance data in the format your payroll provider expects. Most payroll systems accept CSV imports, which you can generate from the monthly reports page.
Can RapidDev help build a custom attendance system?
Yes. RapidDev has built 600+ apps including workforce management tools with advanced features like biometric verification, shift scheduling, and payroll integration. Book a free consultation to discuss your specific attendance tracking needs.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation