Skip to content

Latest commit

Β 

History

20 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ’° FinTrack β€” Full Stack Budgeting App (iOS & Android)

A full-stack budgeting app for iOS and Android built with Expo and Supabase β€” track accounts and transactions, set a monthly budget, and log expenses by typing, scanning a receipt, or just speaking to it.


πŸ“± APK Download

Click the button below to download and install the app on your Android device:

Download APK


✨ Features

  • 🏦 Accounts β€” Manage multiple accounts (cash, bank, credit card, savings) at once. Mark one as your default so new transactions land in the right place automatically, and see a running balance update in real time as you spend or earn.

  • 🧾 Transactions β€” Log income and expenses with categories, then search and filter your history to find exactly what you're looking for. Daily income vs. expense charts give you an at-a-glance view of your cash flow trends over time.

  • πŸ“Έ AI Receipt Scanning β€” Snap a photo of a receipt (or pick one from your gallery), and Gemini automatically extracts the amount, category, and description β€” no manual typing needed.

  • πŸŽ™οΈ AI Voice Entry β€” Just say it out loud: "I spent 400 on groceries yesterday." FinTrack transcribes your voice, parses the details with AI, and logs the transaction for you.

  • πŸ“Š Monthly Budget β€” Set a spending limit for the month and track your progress against it right from the dashboard, so you always know how much room you have left.

  • πŸ“€ CSV Export β€” Export your recent transactions to CSV and share them straight from the Transactions screen β€” handy for backups, taxes, or spreadsheets.

  • πŸ€– AI Assistant β€” Ask natural-language questions about your spending β€” "How much did I spend on food this month?" or "Am I over budget anywhere?" β€” and get instant answers based on your real transaction data.

  • πŸš€ Onboarding β€” A smooth first-run flow to set your currency and starting balance, so the app is ready to use in seconds.

  • πŸ“¬ Smart Email Alerts (backend) β€” Get emailed automatically when you cross 80% or 100% of your monthly budget, plus a weekly digest of personalized AI-generated spending tips based on your last 7 days of activity.

πŸ› οΈ Tech Stack

  • βš›οΈ Expo (SDK 54) + Expo Router
  • πŸ” Clerk for authentication
  • πŸ—„οΈ Supabase (Postgres + Row Level Security) as the backend, authenticated via Clerk's native third-party auth integration (no Supabase JWT template needed)
  • 🧠 Google Gemini for receipt/voice extraction and AI features
  • 🎨 NativeWind (Tailwind for React Native)
  • 🐻 Zustand
  • πŸ”„ TanStack Query
  • βœ… React Hook Form + ZOD

πŸš€ Get Started

  1. Install dependencies

    npm install
  2. Add a .env file in the project root with:

    EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=
    EXPO_PUBLIC_SUPABASE_URL=
    EXPO_PUBLIC_SUPABASE_KEY=
    EXPO_PUBLIC_GEMINI_API_KEY=
    
  3. Connect Clerk to Supabase β€” In your Supabase project, add Clerk as a Third-Party Auth provider (Authentication β†’ Sign In / Providers β†’ Clerk) so auth.jwt()->>'sub' resolves to the Clerk user id β€” this app does not use the legacy Supabase JWT template approach.

  4. Set up the database β€” Run the Supabase queries below to create the schema.

  5. Start the app

    npx expo start -c

    In the output, you'll find options to open the app in a:

πŸ—„οΈ Supabase Queries

πŸ‘€ Users Table

create table users (
  clerk_id text primary key,
  email text not null,
  name text,
  image_url text,
  currency text, -- null until the user completes onboarding
  created_at timestamp with time zone default now()
);

πŸ”’ Users RLS Policies

alter table users enable row level security;

create policy "Users can insert own row"
on users for insert
with check (clerk_id = auth.jwt()->>'sub');

create policy "Users can read own row"
on users for select
using (clerk_id = auth.jwt()->>'sub');

create policy "Users can update own row"
on users for update
using (clerk_id = auth.jwt()->>'sub');

🏦 Accounts Table

create table accounts (
  id uuid default gen_random_uuid() primary key,
  user_id text not null references users(clerk_id) on delete cascade,
  name text not null,
  type text not null, -- 'CASH' | 'BANK' | 'CREDIT_CARD' | 'SAVINGS'
  balance numeric not null default 0,
  is_default boolean not null default false,
  created_at timestamp with time zone default now()
);

πŸ”’ Accounts RLS Policies

alter table accounts enable row level security;

create policy "Users can manage own accounts"
on accounts for all
using (user_id = auth.jwt()->>'sub')
with check (user_id = auth.jwt()->>'sub');

🧾 Transactions Table

create table transactions (
  id uuid default gen_random_uuid() primary key,
  user_id text not null references users(clerk_id) on delete cascade,
  account_id uuid not null references accounts(id) on delete cascade,
  type text not null, -- 'INCOME' | 'EXPENSE'
  amount numeric not null,
  category text not null,
  description text,
  date timestamp with time zone not null default now(),
  status text not null default 'COMPLETED',
  input_method text not null default 'MANUAL', -- 'MANUAL' | 'RECEIPT_SCAN' | 'VOICE'
  voice_transcript text,
  is_flagged boolean not null default false,
  flag_reason text,
  created_at timestamp with time zone default now(),
  updated_at timestamp with time zone default now()
);

πŸ”’ Transactions RLS Policies

alter table transactions enable row level security;

create policy "Users can manage own transactions"
on transactions for all
using (user_id = auth.jwt()->>'sub')
with check (user_id = auth.jwt()->>'sub');

πŸ“Š Budgets Table

-- One budget per user (simple monthly budget, no per-category breakdown)
create table budgets (
  id uuid default gen_random_uuid() primary key,
  user_id text not null unique references users(clerk_id) on delete cascade,
  amount numeric not null,
  last_alert_sent timestamp with time zone,
  last_alert_threshold numeric, -- last budget-usage % (80 or 100) emailed for, resets each calendar month
  created_at timestamp with time zone default now(),
  updated_at timestamp with time zone default now()
);

πŸ”’ Budgets RLS Policies

alter table budgets enable row level security;

create policy "Users can manage own budget"
on budgets for all
using (user_id = auth.jwt()->>'sub')
with check (user_id = auth.jwt()->>'sub');

πŸ“¬ Budget Alerts & Weekly Tips

Two Supabase Edge Functions in supabase/functions/ run on a schedule and email the user via Resend:

  • 🚨 check-budget-alerts β€” Runs daily, emails a user the first time they cross 80% and again the first time they cross 100% of their monthly budget (tracked via last_alert_sent/last_alert_threshold, resets each calendar month).
  • πŸ’‘ weekly-tips β€” Runs every Monday, asks Gemini for a few short, personalized tips based on each user's last 7 days of transactions and emails them.

Neither is called from the app β€” they're standalone, timer-driven backend jobs that live entirely on Supabase.

βš™οΈ Setup

  1. Link the project (one-time; skip if using the dashboard only)

    npx supabase login
    npx supabase init
    npx supabase link --project-ref <your-project-ref>
  2. Apply the DB schema β€” Supabase dashboard β†’ SQL Editor β†’ run scripts/schema.sql.

  3. Deploy the two edge functions β€” either:

    npx supabase functions deploy check-budget-alerts
    npx supabase functions deploy weekly-tips

    or from the dashboard: Edge Functions β†’ Deploy a new function, paste in the contents of the corresponding index.ts (and the files under _shared/), and give it the same name as the folder β€” no CLI required.

  4. Set secrets (project-wide, covers both functions) β€” either npx supabase secrets set KEY=value per line below, or dashboard β†’ Edge Functions β†’ Secrets:

    RESEND_API_KEY=
    RESEND_FROM_EMAIL=        # "FinTrack <alerts@yourdomain.com>" once you verify a domain in Resend;
                               # until then, use "FinTrack <onboarding@resend.dev>" β€” the address must
                               # stay onboarding@resend.dev and only delivers to the email you signed
                               # up with, but the display name (the "FinTrack" part) is yours to set
    GEMINI_API_KEY=           # same Gemini key as EXPO_PUBLIC_GEMINI_API_KEY, without the EXPO_PUBLIC_ prefix
    EMAIL_LOGO_URL=           # any public URL to the FinTrack logo, used in the email header;
                               # omit this secret entirely to fall back to a text "FinTrack" wordmark
    

    SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY are provided automatically in the edge function runtime β€” Supabase reserves the SUPABASE_ prefix, so you can't (and don't need to) set these yourself.

  5. Schedule them β€” SQL Editor β†’ run scripts/cron_jobs.sql, after filling in your real project URL and service-role key in the two vault.create_secret(...) calls at the top.

βœ… Once done: check-budget-alerts runs daily at 9am UTC, weekly-tips runs Mondays at 9am UTC.

πŸ§ͺ Testing Without Waiting for the Schedule

Easiest way: dashboard β†’ Edge Functions β†’ select the function β†’ use the built-in test/invoke panel to trigger it on demand and see the response and logs right there.

CLI alternative: curl the deployed URL directly (this CLI version has no functions invoke subcommand):

curl -i --location --request POST 'https://<project-ref>.supabase.co/functions/v1/check-budget-alerts' \
  --header 'Authorization: Bearer <anon-or-service-role-key>'

A response of {"sent": 0} just means no user currently qualifies β€” for check-budget-alerts you need a test user whose spend is β‰₯80% of their budget, for weekly-tips a test user with a transaction in the last 7 days. last_alert_sent/last_alert_threshold block repeat sends within the same month, so reset them on your test budget row between runs if you want to re-trigger the same threshold.

Releases

Packages

Contributors

Languages