Skip to main content
This page describes the technical architecture of Simple Money from a deployment and development perspective. It covers the tech stack, directory layout, authentication model, data flow, and storage.

Tech stack

Next.js (App Router)

Version 16 with the App Router, React Server Components, and output: 'standalone' for containerized deployment. TypeScript throughout.

Supabase

Provides PostgreSQL (data), Auth (sessions), and Storage (file uploads) as a managed backend. Accessed via @supabase/supabase-js.

Tailwind CSS

Version 4 via PostCSS. Custom theme variables and shared utility classes (.glass-card, etc.) are defined in src/app/globals.css.

Vercel

Recommended deployment target. Standalone output mode means the app can also run in any Docker-compatible environment.
Additional runtime dependencies include Framer Motion (animations), GSAP (task-completion effects), Lucide React (icons), and qrcode.react (wallet QR codes).

Directory structure

src/
├── app/
│   ├── (auth)/               # Public routes: /login, /signup
│   ├── (user)/               # Protected routes (require auth session)
│   │   ├── home/             # Main dashboard
│   │   ├── start/            # Task execution portal
│   │   ├── levels/           # VIP tier overview
│   │   ├── deposit/          # Fund deposit with network selection
│   │   ├── withdraw/         # Withdrawal requests
│   │   ├── wallet/           # Wallet management
│   │   ├── record/           # Transaction history
│   │   │   ├── deposit/
│   │   │   └── withdraw/
│   │   ├── invite/           # Referral dashboard
│   │   ├── profile/          # User settings and security
│   │   │   ├── wallet/       # Bind withdrawal address
│   │   │   ├── security/     # Security PIN
│   │   │   └── settings/     # Language, currency, notifications
│   │   ├── notifications/    # In-app notification feed
│   │   ├── faq/              # Frequently asked questions
│   │   └── rules/            # Platform policies
│   ├── admin/                # Admin panel (subdomain-isolated)
│   │   ├── users/
│   │   ├── deposits/
│   │   ├── withdrawals/
│   │   ├── transactions/
│   │   ├── tasks/
│   │   ├── levels/
│   │   ├── referrals/
│   │   └── bundles/
│   ├── api/
│   │   ├── admin/            # Server-side admin mutations (service role)
│   │   │   ├── create-user/
│   │   │   ├── delete-user/
│   │   │   ├── assign-bundle/
│   │   │   └── bundles/
│   │   └── auth/
│   │       └── lookup/
│   ├── globals.css
│   └── layout.tsx            # Root layout — mounts all context providers
├── components/
│   ├── Sidebar.tsx           # Desktop side navigation
│   ├── Header.tsx            # Top navigation bar
│   ├── BottomNav.tsx         # Mobile bottom navigation
│   ├── AuthProvider.tsx      # Supabase Auth context wrapper
│   ├── Providers.tsx         # Combines all context providers
│   └── LoadingScreen.tsx     # Full-screen loading state
├── context/
│   ├── AuthContext.tsx       # Session + profile data
│   ├── CurrencyContext.tsx   # Currency formatting
│   ├── LanguageContext.tsx   # i18n string lookup
│   ├── ThemeContext.tsx      # Dark / light mode toggle
│   ├── NotificationContext.tsx  # In-app notification state
│   └── SettingsContext.tsx   # User preferences
└── lib/
    ├── supabase.ts           # Singleton Supabase client
    ├── types.ts              # Shared TypeScript interfaces
    └── utils.ts              # Helper utilities (class merging, etc.)

Authentication flow

Simple Money uses Supabase Auth with browser-persisted sessions (localStorage, key sb-auth-token-money).
1

Sign-up / sign-in

The user submits credentials on /login or /signup. Supabase Auth validates them and writes a JWT session to localStorage via the supabase-js client.
2

Session detection

On every page load, AuthContext calls supabase.auth.getSession(). If a valid session exists it fetches the corresponding row from profiles and exposes it via the useAuth() hook.
3

Protected route enforcement

The (user) route group layout (src/app/(user)/layout.tsx) checks AuthContext on the client. If there is no active session the user is redirected to /login.
4

Admin panel isolation

The middleware in src/middleware.ts rewrites requests to the admin. subdomain into the /admin route segment, and redirects direct /admin access on the main domain to the subdomain. Admin auth is validated inside src/app/admin/layout.tsx.
5

Sign-out

Calling supabase.auth.signOut() clears the localStorage session. AuthContext responds to the SIGNED_OUT event and redirects to /login.
The middleware runs at the Edge and cannot read localStorage. As a result it does not perform token validation — that responsibility falls entirely to the client-side layouts. If you migrate to cookie-based sessions using @supabase/ssr, you can uncomment the server-side auth check in src/middleware.ts.

Data flow

Read operations

Client components query Supabase directly using the singleton client from src/lib/supabase.ts:
import { supabase } from '@/lib/supabase';

const { data, error } = await supabase
  .from('transactions')
  .select('*')
  .eq('user_id', userId)
  .order('created_at', { ascending: false });
Row-level security ensures each user only receives their own rows without any server-side filtering code.

Write operations (user actions)

Task completions, deposit submissions, and withdrawal requests also go through the supabase-js client directly, writing rows that satisfy the RLS insert policies. Commission calculations use Supabase RPC calls to run atomic balance updates in the database.

Write operations (admin actions)

Sensitive mutations — creating users, deleting accounts, assigning bundles — are handled by Next.js API routes under /api/admin/. These routes instantiate a separate Supabase client with the SUPABASE_SERVICE_ROLE_KEY, which bypasses RLS:
import { createClient } from '@supabase/supabase-js';

const adminClient = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
);
Never use the service role client in client components or expose SUPABASE_SERVICE_ROLE_KEY to the browser. It grants unrestricted database access and bypasses all RLS policies.

File storage

Deposit payment receipts are uploaded to a Supabase Storage bucket named deposit_proofs. The upload happens client-side at /deposit using the anon key; a storage policy restricts reads to the file owner and admins.
const { data, error } = await supabase.storage
  .from('deposit_proofs')
  .upload(`${userId}/${filename}`, file);
The resulting public or signed URL is stored in the transactions row as a reference for admin review.

Context providers

All providers are mounted in src/app/layout.tsx via the <Providers> wrapper component. They are available to every page in the app via their respective hooks.
ContextHookResponsibility
AuthContextuseAuth()Supabase session, Profile object, sign-out helper
CurrencyContextuseCurrency()Format numbers as currency strings based on user preference
LanguageContextuseLanguage()Translate UI strings based on profile.language
ThemeContextuseTheme()Toggle and persist dark / light mode
NotificationContextuseNotifications()In-app notification queue (read, dismiss, mark-all-read)
SettingsContextuseSettings()General user preference helpers

Build docs developers (and LLMs) love