Skip to content

prat-eek17/SplitwisePlus

Repository files navigation

SplitWise+

A premium, mobile-first expense-splitting PWA. Next.js 15 (App Router) + TypeScript + Tailwind + Framer Motion on the frontend, Supabase (Postgres + Auth + Realtime + RLS) as the entire backend — no custom server.

Design: ink-black surfaces, a single violet brand accent, and a strict money-semantic pair (emerald = owed to you, rose = you owe). The signature visual is the "settlement flow" bar on the Settle Up tab — a moving gradient between two avatars instead of a flat list row.

What's included in this pass

  • Auth: Google OAuth + email magic link, session-aware middleware
  • Groups: create, invite by link or email, strict privacy via RLS
  • Expenses: add with equal-split, animated timeline, expand-to-see-split, delete
  • Balances: net-balance engine + minimum-transaction settlement suggestions, mark as settled
  • Dashboard: total balance, you owe / you're owed, monthly spend, groups list
  • Activity feed per group, realtime-synced
  • Search + category/member filters, both global and per-group
  • Basic analytics (category breakdown, member contributions, highest spender)
  • PWA: manifest, service worker, installable, safe-area support
  • Profile/settings screen with "Developed by Prateek ❤️"

Not yet built (natural next steps)

  • Unequal/percentage/exact-amount splits (equal split only for now)
  • Editing an existing expense (add is wired; edit needs the same form pre-filled)
  • Push notifications, currency conversion, data export/delete-account actions (UI stubs only)
  • Swipe-to-delete / pull-to-refresh gestures
  • Custom app icons in public/icons/ (placeholders referenced in the manifest)

1. Set up Supabase

  1. Create a project at supabase.com (you're already signed in via GitHub — just hit "New project").
  2. In the SQL editor, run the two migration files in order:
    • supabase/migrations/0001_schema.sql
    • supabase/migrations/0002_rls.sql
  3. Authentication → Providers: enable Google (add your OAuth client ID/secret) and make sure Email is on for magic links.
  4. Authentication → URL Configuration: add http://localhost:3000/auth/callback (and your production URL later) as a redirect URL.
  5. Project Settings → API: copy the Project URL and anon public key.

2. Configure the app

cp .env.local.example .env.local
# paste your Project URL and anon key into .env.local

3. Run it

npm install
npm run dev

Open http://localhost:3000. On a phone, use your machine's LAN IP so you can test the install-to-home-screen flow.

4. Deploy

  • Push to GitHub, import into Vercel, add the two NEXT_PUBLIC_SUPABASE_* env vars in Vercel's project settings, deploy.
  • Add your Vercel domain to Supabase's redirect URL allowlist (step 4 above).

Verifying group privacy

This was the spec's hardest requirement, so it's worth checking directly: sign in as two different users, have user A create a group and NOT invite user B, then sign in as user B and confirm that group never appears anywhere (dashboard, search, or via a guessed URL — the groups_select_member_only RLS policy blocks the row at the database level, not just in the UI).

Hotfix (if you already deployed the first version)

Two things were broken and are fixed in this codebase — but if you already ran the original SQL and deployed to Vercel, you need to apply these manually:

1. Group creation was failing with a 42501 / new row violates row-level security policy for table "groups" error. Cause: creating a group does insert().select() to hand the new row back to the app, but the old SELECT policy only let members see a group — and you're not added as a member until the next line of code runs. Postgres reports that timing gap as an RLS violation on insert. Run this in your Supabase SQL editor:

drop policy if exists "groups_select_member_only" on public.groups;
create policy "groups_select_member_only"
  on public.groups for select
  to authenticated
  using (public.is_group_member(id) or created_by = auth.uid());

If you tried creating groups before this fix, check for orphaned rows (created but with no members, so invisible in the app):

select g.* from public.groups g
left join public.group_members gm on gm.group_id = g.id
where gm.group_id is null;

Delete any test ones you don't need, or manually insert yourself as a member (insert into group_members (group_id, user_id, role) values ('<group id>', '<your user id>', 'owner')).

2. Profile settings rows (Theme, Currency, Notifications, Privacy, Export, Delete account) did nothing. They were built as static placeholders with no click handlers, and colors were hardcoded hex values with no theming system underneath — even a working button couldn't have switched anything. Both are now real:

  • Theme (light/dark/system) actually switches the whole color system, via CSS variables in globals.css + lib/use-theme.ts
  • Currency picker updates every amount shown in the app, persisted to localStorage
  • Notifications: local toggle preferences, saved to the device (there's no push server behind this app yet, stated honestly in the sheet itself)
  • Privacy: an info panel describing the actual RLS model, not fake toggles
  • Export data: a real JSON download of everything you can see (your groups, expenses, settlements)
  • Delete account: flags the account and signs you out — full erasure needs a server-side admin call (deleting auth.users requires the service-role key, which can't run in the browser), noted directly in the confirmation sheet

Just redeploy — no SQL changes needed for this one, it was purely a frontend gap.

  • lib/balance-calculator.ts is the single source of truth for money math (both the dashboard's aggregate balance and each group's balance call into it), so the two screens can't drift out of sync.
  • lib/queries.ts wraps every read/write in TanStack Query and subscribes to Supabase Realtime per group; any insert/update/delete on expenses, settlements, activity_log, or group_members invalidates the relevant query for every open tab/device.
  • RLS is enforced with a security definer helper (is_group_member) to avoid the classic recursive-policy trap on group_members.

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors