The System

A production-deployed, multi-tenant enterprise store management platform built for retail businesses that need structure, auditability, and role-based control across their operations.

What is Traqify?

Traqify is a full-stack TypeScript monorepo (a Next.js 14 App Router frontend + RESTful Express.js backend covering the complete retail lifecycle: product catalogue, POS order creation, inventory control, customer records, payment tracking, staff access management, public storefronts with Paystack checkout, financial reports, and a real-time analytics dashboard.

The system is live at https://traqify.vercel.app and its API at https://traqify-api.vercel.app.

Architecture

                              CLIENT
              +--------------------------------------+
              |    Next.js 14 (App Router)  :3000    |
              |  TypeScript + Tailwind + Framer      |
              |  Axios client (JWT + auto-refresh)   |
              +------------------+-------------------+
                                 |
                    HTTP/HTTPS   |   REST JSON API
                                 |
              +------------------v-------------------+
              |    Express.js API Server  :5000       |
              |  TypeScript + Prisma ORM              |
              |  JWT middleware + RBAC middleware      |
              +------+----------+--------------------+
                     |          |
         +-----------+          +-----------+
         |                                  |
+--------v-----------+          +-----------v-----------+
|  PostgreSQL (via   |          |  Supabase Storage      |
|  Supabase)         |          |  (product images,      |
|                    |          |   avatars)             |
|  Prisma ORM        |          +-----------------------+
+--------------------+
         |
+--------v-----------+
|  Nodemailer        |
|  Gmail SMTP        |
|  HTML email        |
|  templates         |
+--------------------+

  AUTHENTICATION FLOWS
  ----------------------
  Email/Password (OTP-first flow):
    1. POST /send-otp -> OTP email (works before user exists)
    2. POST /verify-email -> validate code -> redirect /register?verifiedEmail=...
    3. POST /register -> create user (emailVerified:true) -> JWT (React state only)
    4. POST /organizations -> create org -> POST /login -> fresh JWT with orgId
       -> redirect /dashboard/[slug]/overview

  Google OAuth 2.0:
    GET /google-redirect -> accounts.google.com
    -> GET /google-callback?code= -> exchange code -> userinfo
    -> upsert user -> JWT + redirect to /auth-callback

  Token Refresh:
    Axios 401 interceptor -> POST /auth/refresh -> new access token
    Automatic, transparent to all callers

Frontend

Next.js 14 (App Router), TypeScript, Tailwind CSS, Framer Motion, Recharts

Backend

Express.js, TypeScript, Prisma ORM, JWT, bcrypt

Database

PostgreSQL (hosted on Supabase)

Storage

Supabase Storage for product images and avatars

Tech Stack

ConcernTechnologyNotes
Frontend frameworkNext.js 14App Router, SSR + Client Components
LanguageTypeScript 5Strict mode, full type coverage
StylingTailwind CSS 3JIT, custom config
UI primitivesRadix UI / shadcn/uiAccessible, unstyled components
AnimationsFramer MotionPage transitions, scroll animations, charts
ChartsRechartsAreaChart, BarChart, PieChart
HTTP clientAxiosInterceptors for JWT + token refresh
Backend frameworkExpress.js 4TypeScript, modular routes
ORMPrismaType-safe queries, migrations via db push
DatabasePostgreSQLHosted on Supabase
AuthJWT + bcryptAccess + refresh token pair
OAuthGoogle OAuth 2.0Redirect-based (no popup)
File storageSupabase StorageProduct images, avatars
EmailNodemailerGmail SMTP, HTML templates
SecurityHelmet, rate-limitPer-route rate limiting on auth endpoints
File uploadMulter (memoryStorage)Images: JPG/PNG/WebP max 2 MB; downloadable files: any type max 4 MB
FontJostGoogle Fonts

Key Features

Multi-tenancy

Every database query — products, orders, customers, staff, payments, audit logs — is scoped by organizationId. No query runs without it. There is no global admin view; isolation is enforced at the ORM layer, not in frontend logic alone. The system supports creating multiple separate organizations (e.g., separate branches) under different slugs.

Role-Based Access Control (RBAC)

Four roles with granular middleware enforcement:

RoleCapabilities
OWNERFull access: all modules, staff management, org settings, audit logs
MANAGERProducts, inventory, orders, customers, staff invitations
CASHIERCreate orders, view own transactions, browse product catalog
AUDITORRead-only access to all data, audit logs, financial reports

Authentication

  • Email + password with OTP email verification (6-digit, 10-minute expiry)
  • Google OAuth 2.0 via redirect flow (no third-party popups)
  • JWT access token (7-day default) + refresh token pair
  • Axios interceptor catches 401 and silently refreshes the token
  • Account lock by admin with notification email

Products and Inventory

  • Create products with name, SKU, category, price, compare-at price, description
  • Product types: SIMPLE, DOWNLOADABLE (download URL or direct file upload), VARIABLE (attribute builder)
  • Multi-image support: up to 4 images per product; first image is cover; drag-to-reorder
  • Auto-SKU generation from product name
  • Image upload (JPG / PNG / WebP, max 2 MB) to Supabase Storage (products bucket)
  • Downloadable file upload via dedicated POST /api/products/upload-file endpoint (any MIME type, max 4 MB)
  • Edit page at /products/[id]/edit with full feature parity to Add Product; category is immutable after creation
  • Publish confirmation modal on Add Product when status is set to Published
  • Save confirmation modal on Edit Product before any save; adapts text for publish vs draft saves
  • Per-product inventory with configurable low-stock alert threshold
  • Inventory adjustment log
  • Low-stock dashboard badge
  • Category management page (/dashboard/[slug]/categories)

Orders

  • POS-style order creation: search products, set quantities, attach customers
  • Order status flow: PENDING → APPROVED → COMPLETED / CANCELLED
  • Clickable rows — click anywhere on a row to open the order detail modal
  • Approve confirmation modal before status change; delete confirmation modal
  • Inventory auto-decremented on order creation
  • Order detail modal with full item and customer breakdown
  • Email confirmation to customer with org branding (logo, org name)
  • Admin email notification to the org owner when any new order is placed (dashboard or store)

Public Store

  • Each org gets /store/[slug] as a public product catalog
  • Responsive: mobile off-canvas drawer menu with category nav, cart/wishlist counts
  • Store navbar: full-width logo (or text fallback), cart badge, wishlist badge, category tabs (desktop)
  • Store info section below products: org name, description, and contact details linkable via #store-info
  • Filters: keyword search, category (left sidebar), dual price range slider (min/max derived from actual product prices)
  • Sort bar: Newest first, Oldest, Price (low→high / high→low), Name (A–Z)
  • Product cards: hover image cycling, wishlist heart overlay, discount % badge; separate View / Add-to-cart / Wishlist actions; object-contain images
  • Product detail drawer: image gallery with thumbnail strip, wishlist toggle, Add to cart
  • Wishlist: localStorage + backend sync; email capture; reminder emails at 30min/2hr/1day/3days
  • Cart with quantity controls; scroll-to-top button
  • Checkout: breadcrumb navigation, arithmetic CAPTCHA security check, Paystack payment popup, Secured by Paystack badge
  • Paystack payment: inline popup (no redirect), backend verification before order creation; successful payments create orders as APPROVED
  • Confirmation email to customer on order placement

Analytics Dashboard

  • Live clock (HH:MM:SS) and timezone displayed next to the greeting and date — updates every second
  • Period filter on charts: 7 / 30 / 90 days (applies to revenue, orders, and customer growth charts simultaneously)
  • Open Storefront button — opens the public store in a new tab; shows a modal if the store is unpublished with a direct link to publish
  • Revenue area chart, order growth area chart, customer growth line chart — all powered by live API data
  • Low-stock alert banner when any product is at or below alert threshold
  • First-time welcome modal: role-aware — owners see setup instructions; invited members see their role and access scope
  • Welcome-back modal on every new session (sessionStorage-gated)

Audit Logs

  • Every create / update / delete / login event logged with: user ID, organization ID, action type, entity name, entity ID, human-readable detail, IP address, user agent, and timestamp
  • Captures: product creates/edits, order status changes, customer updates, staff invites sent/accepted/cancelled/expired, password changes, account restrictions, report exports
  • Searchable and paginated; clickable rows navigate to a full detail page
  • Read/unread state per log entry; bulk mark-read with confirmation modal
  • Notification bell in topbar: shows latest 3 unread audit events; click navigates to detail
  • Visible to OWNER and AUDITOR only

Security Model

Authentication layers

  • OTP email verification — every new account must verify their email before gaining access; the OTP is a 6-digit code with a 10-minute expiry and single-use enforcement
  • bcrypt password hashing — cost factor 12; no plain-text passwords stored anywhere
  • JWT access token — 7-day default lifetime; signed with JWT_SECRET; carries userId, email, organizationId, role
  • JWT refresh token — separate secret (JWT_REFRESH_SECRET); used by Axios interceptor to silently re-issue access tokens on 401
  • Google OAuth 2.0 — redirect-based flow (server-side code exchange); email/password and Google OAuth are not mutually exclusive
  • Invited user registration block — users with a pending staff invitation cannot create new accounts via registration or Google OAuth; they must use their invitation link or sign in

Authorization layers

  • authenticate middleware — verifies the JWT on every protected route; attaches req.user
  • requireOrg middleware — enforces that req.user.organizationId is set; prevents cross-org access
  • RBAC middleware — four guards: isOwnerOnly, isOwnerOrManager, isAtLeastAuditor, isAtLeastCashier; applied per-route, not per-controller
  • Data-layer isolation — all Prisma queries include organizationId in the where clause; no query trusts the frontend to scope data

Rate limiting

Auth endpoints (/api/auth/*) are rate-limited via express-rate-limit: 10 requests per 15 minutes per IP on sensitive routes (login, register, OTP send).

HTTP security headers

helmet is applied globally: sets X-Content-Type-Options, X-Frame-Options, Strict-Transport-Security, X-XSS-Protection, and Content Security Policy headers.

File upload security

  • Only image/jpeg, image/png, image/webp MIME types accepted (validated server-side)
  • Max file size: 5 MB (enforced by Multer before the handler runs)
  • Files stored in Supabase Storage (not the server filesystem); server never persists files to disk

Database Schema

User
  id, email, name, password (bcrypt), phone, avatarUrl
  emailVerified, signInMethod (EMAIL | GOOGLE)
  role (OWNER | MANAGER | CASHIER | AUDITOR)
  isActive Boolean                         -- account restriction flag
  organizationId (FK -> Organization)?
  invitedById (FK -> User)?                -- set when joined via invite
  lastLoginAt, createdAt

Organization
  id, name, slug (unique), email, phone, address, website
  industry, size, description?
  logoUrl?
  storePublished Boolean
  ownerId (FK -> User)

Product
  id, name, sku (unique per org)
  price, comparePrice?, description?
  imageUrl?, imageUrls String[]
  productType (SIMPLE | DOWNLOADABLE | VARIABLE)
  downloadUrl?
  status (published | draft), isActive Boolean
  categoryId (FK -> ProductCategory)
  organizationId

Inventory
  id, quantity Int, lowStockAlert Int
  productId (1:1 -> Product)

Order
  id, status (PENDING|APPROVED|COMPLETED|CANCELLED)
  totalAmount, paymentMethod?, notes?
  customerId?, organizationId, createdByUserId?

OrderItem
  id, productId, quantity, unitPrice, subtotal
  orderId

Payment
  id, amount Float, currency (default NGN)
  status (PENDING|COMPLETED|FAILED|REFUNDED)
  method?, reference?, notes?
  organizationId, orderId?

Customer
  id, name, email?, phone?, address?
  source (MANUAL | PURCHASE)
  organizationId

StaffInvite
  id, email, role, token (unique)
  status (PENDING | ACCEPTED | EXPIRED)
  expiresAt, organizationId, invitedById

Review
  id, orderId, productId, organizationId
  rating Int (1-5), comment?
  customerName, customerEmail?
  status (PENDING | APPROVED | REJECTED)
  @@unique([orderId, productId])

ProductCategory
  id, name, slug, description?
  organizationId

OTPCode
  id, email, code, expiresAt, used Boolean

PasswordResetToken
  id, email, token, expiresAt, used Boolean

AuditLog
  id, userId, organizationId
  action (CREATE | UPDATE | DELETE | LOGIN)
  entity String, entityId String, details String
  ipAddress?, userAgent?
  isRead Boolean (default false)
  createdAt

Wishlist
  id, sessionId, email?, productIds String[]
  slug, organizationId
  sent30min, sent2hr, sentDay1, sentDay3 Boolean
  createdAt

NewsletterSubscriber
  id, email, subscribedAt

Running Locally

Prerequisites

  • Node.js 18 or higher
  • npm 9 or higher
  • A free Supabase project (PostgreSQL + Storage)
  • A Gmail account with App Password enabled
  • A Google Cloud project with OAuth 2.0 Web Client credentials

Step 1: Clone the repository

git clone https://github.com/oyedokunken/traqify.git
cd traqify

Step 2: Set up the backend

cd backend
npm install
cp .env.example .env

Open .env and fill in the required environment variables (DATABASE_URL, DIRECT_URL, SMTP_USER, SMTP_PASS, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, JWT_SECRET, JWT_REFRESH_SECRET).

Push the schema to your database:

npx prisma db push

Start the backend dev server:

npm run dev

Step 3: Set up the frontend

cd frontend
npm install
cp .env.local.example .env.local

Fill in NEXT_PUBLIC_API_URL, NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY.

Start the frontend:

npm run dev