Skip to content

Aathirajan/flashbillr_admin

Repository files navigation

Flashbillr Admin

Dual-role admin dashboard for the Flashbillr multi-tenant fireworks SaaS. A single React SPA that serves both per-store managers (STOREADMIN) and platform operators (SUPERADMIN), each with completely separate navigation, data scopes, and analytics surfaces.

TypeScript React Vite TanStack Query


Overview

Flashbillr Admin is the management frontend for the Flashbillr backend. It talks exclusively to the backend's REST API over JWT and enforces role separation at the routing layer — a STOREADMIN and a SUPERADMIN land on completely different dashboards after login.

STOREADMIN manages their own store: products, inventory, orders, POS receipts, customers, invoices, and store-level analytics.

SUPERADMIN operates the platform: all stores, all users, subscriptions, and platform-wide revenue analytics.


Tech Stack

Layer Technology
Framework React 18 + TypeScript 5
Build Vite 7 (with rollup-plugin-visualizer for bundle analysis)
Styling Tailwind CSS 3
Routing React Router DOM 6
Server state TanStack Query 5 (caching, background refetch, pagination)
HTTP Axios
Forms React Hook Form 7 + Yup validation
Charts Recharts 2
Date utils date-fns 3
Icons Lucide React
Notifications react-hot-toast
Linting ESLint 9 + typescript-eslint

Architecture

This is a pure client-side SPA — all rendering happens in the browser, the backend is consumed exclusively via REST, and role-based routing is enforced in the React Router config with auth guards.

flashbillr_admin/
├── src/
│   ├── main.tsx                  # App entry point
│   ├── App.tsx                   # Router root + auth guards
│   ├── pages/
│   │   ├── auth/                 # Login page
│   │   ├── storeadmin/           # STOREADMIN role pages
│   │   │   ├── Dashboard.tsx     # Sales, revenue, stock summary
│   │   │   ├── Products.tsx      # Product list + create/edit
│   │   │   ├── Inventory.tsx     # Stock levels, low-stock alerts, bulk update
│   │   │   ├── Orders.tsx        # Order list, status management, shipping
│   │   │   ├── Customers.tsx     # Customer profiles + order history
│   │   │   ├── POS.tsx           # POS receipts + checkout
│   │   │   ├── Invoices.tsx      # Invoice list + detail
│   │   │   └── Notifications.tsx # In-app notification centre
│   │   └── superadmin/           # SUPERADMIN role pages
│   │       ├── Dashboard.tsx     # Platform analytics (all stores)
│   │       ├── Stores.tsx        # Store management + price list PDF
│   │       ├── Users.tsx         # User management + onboarding
│   │       └── Subscriptions.tsx # Subscription lifecycle management
│   ├── components/
│   │   ├── layout/               # Sidebar, Topbar, role-aware nav
│   │   ├── ui/                   # Shared primitives (Modal, Table, Badge, etc.)
│   │   └── charts/               # Recharts wrappers (revenue, stock, orders)
│   ├── hooks/                    # TanStack Query hooks per resource
│   ├── services/                 # Axios API clients (storeadmin, superadmin, auth)
│   ├── context/                  # AuthContext — JWT storage + role resolution
│   └── types/                    # Shared TypeScript interfaces
├── vite.config.ts                # Vite + manual chunk splitting + visualizer
├── tailwind.config.js
└── stats.html                    # Rollup bundle analysis (committed for reference)

Role-Based Navigation

Both roles share the same login page. After JWT decode the app redirects to the correct dashboard and mounts a role-specific sidebar — a STOREADMIN never sees super admin routes, and vice versa.

STOREADMIN views

Route Description
/storeadmin/dashboard Orders, revenue, customers, stock summary + recent orders + top products + low stock alerts
/storeadmin/products Full product catalogue — create, edit, soft delete, image upload
/storeadmin/inventory Stock levels with low-stock filter, bulk update
/storeadmin/orders Order list with status filter — ship (with LR doc upload), cancel
/storeadmin/customers Customer list + per-customer order history
/storeadmin/pos POS receipts list + new checkout flow
/storeadmin/invoices Invoice list + detail view
/storeadmin/notifications Mark-read notification centre

SUPERADMIN views

Route Description
/superadmin/dashboard Platform-wide: total stores, active stores, total orders, total revenue; monthly revenue chart; top 5 stores; recent orders
/superadmin/stores All stores — create, edit, soft delete, generate & send price list PDF
/superadmin/users All users — create store admin (triggers onboarding email), toggle active, soft delete
/superadmin/subscriptions Full subscription management — create, status transitions (ACTIVE / EXPIRED / CANCELLED), expiring-in-30-days view

API Integration

All data fetching goes through TanStack Query hooks wrapping Axios. The JWT token from login is attached to every request via an Axios request interceptor. On 401, the interceptor clears the token and redirects to login.

Backend base URLs (from flashbillr_backend):

Development:  http://localhost:3000
Production:   https://api.flashbillr.com

Example hook pattern used throughout:

// hooks/useOrders.ts
export const useOrders = (filters: OrderFilters) =>
  useQuery({
    queryKey: ['orders', filters],
    queryFn: () => storeAdminApi.get('/orders', { params: filters }),
    staleTime: 30_000,
  });

// Mutation with optimistic toast feedback
export const useShipOrder = () => {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: ({ id, file }: ShipPayload) => {
      const form = new FormData();
      form.append('lrPhoto', file);
      return storeAdminApi.post(`/orders/${id}/ship`, form);
    },
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['orders'] });
      toast.success('Order marked as shipped');
    },
  });
};

Forms & Validation

All forms use React Hook Form with Yup schemas. File upload fields (product images, shipping documents, payment screenshots) are handled as controlled <input type="file"> wired through register, with file-type and size validation in the schema.


Bundle Optimisation

The Vite config splits the build into explicit manual chunks to minimise initial load time:

manualChunks: {
  'react-vendors':   ['react', 'react-dom'],
  'recharts-vendors': ['recharts', 'd3-array', 'd3-shape', 'd3-scale', ...],
  'lodash-vendors':  ['lodash'],
  'datefns-vendors': ['date-fns'],
}

The committed stats.html is the rollup-plugin-visualizer output — open it in a browser to inspect the full bundle treemap. The app version is injected at build time from package.json via import.meta.env.VITE_APP_VERSION.


Getting Started

Prerequisites

Local Setup

git clone https://github.com/Aathirajan/flashbillr_admin.git
cd flashbillr_admin

npm install

# Create a .env file
echo "VITE_API_BASE_URL=http://localhost:3000" > .env

npm run dev

App runs at http://localhost:5173.

Environment Variables

VITE_API_BASE_URL=http://localhost:3000   # Flashbillr backend base URL

Scripts

npm run dev       # Vite dev server with HMR
npm run build     # Production build (outputs to dist/)
npm run preview   # Preview the production build locally
npm run lint      # ESLint

To inspect the bundle after a build:

npm run build
# stats.html opens automatically (rollup-plugin-visualizer is configured with open: true)

Deployment

This is a pure static SPA — the dist/ output can be deployed to any static host.

Vercel / Netlify: Connect the repo, set VITE_API_BASE_URL as an environment variable, and set the build command to npm run build with publish directory dist. Add a rewrite rule so all paths serve index.html (required for client-side routing).

Nginx:

server {
  root /var/www/flashbillr-admin/dist;
  location / {
    try_files $uri $uri/ /index.html;
  }
}

Relationship to Backend

This dashboard is the frontend counterpart to flashbillr_backend. Every feature in the admin maps directly to a backend route group:

Admin module Backend namespace
Store Admin — Products, Inventory, Orders, POS, Customers, Invoices, Notifications /api/storeadmin/*
Super Admin — Stores, Users, Subscriptions, Dashboard /api/superadmin/*
Login / profile /api/auth/*

Key Design Decisions

Single SPA, two roles — Rather than maintaining two separate frontends, role resolution happens once after login and drives all subsequent routing and sidebar rendering. This keeps shared infrastructure (API client, auth context, UI components, charts) in one place while keeping role-specific pages fully isolated in their own directories.

TanStack Query for all server state — No Redux, no Zustand for remote data. TanStack Query's stale-while-revalidate model means dashboards feel instantly responsive on revisit (cached data shown immediately, silently refreshed in the background), which matters for a multi-tenant admin where managers check the same screens repeatedly throughout the day.

Manual chunk splitting — Recharts pulls in several D3 sub-packages and is a significant chunk. Splitting it into recharts-vendors means users who navigate to non-chart pages never download it. The committed stats.html makes this visible and keeps bundle regressions easy to catch.

React Hook Form + Yup — Form state is kept local to each form component, not in global state. This avoids the common pattern of routing form dirty-state through a global store, which becomes a maintenance problem as the number of forms grows. Yup schemas are co-located with their forms for the same reason.


License

MIT © Aathirajan

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages