npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

rvx-cli

v1.2.2

Published

Scaffold a production-ready Vite + React project with permissions, routing, Redux, and shadcn/ui

Readme

rvx-cli

A CLI that scaffolds production-ready Vite + React projects with a built-in permission engine, protected routing, centralized API layer, Redux state management, module generator, shadcn/ui components, and a pluggable theme system — all wired together and ready to go.

npx rvx-cli my-app
cd my-app
npm run dev

v1.2.0 ships with two themes:

  • Default — neutral monochrome shadcn palette (unchanged from v1.0.x)
  • Atlas — full enterprise design system: 29 components (KPI cards, charts, command palette, toast, modal, stepper, accent picker), 18 element showcase pages, custom dashboard + auth, 6 swappable accent palettes (maroon, red, green, blue, purple, teal) × 2 styles (gradient / solid)

Table of Contents


Why rvx-cli?

Most React scaffolding tools give you a blank canvas. rvx-cli gives you an opinionated, enterprise-ready architecture out of the box:

  • Permission enginecan(), canAny(), canAll() with super admin bypass, baked into routes, sidebar, and buttons
  • Theme system — pluggable, overlay-based. Pick a theme at scaffold time. Drop in a new themes/<name>/ folder and the CLI auto-discovers it. Atlas ships with 29 components + 18 demo pages
  • Module generatornpm run hcorp:add product creates page, add page, service, slice, API endpoints, permissions, route, and sidebar entry in one command. Branches on theme — Atlas modules use ListPage + StatusPill + FormSection
  • Feature-selectable — deselect Tailwind, shadcn, Redux, or Router during setup and the CLI cleanly strips all related code, files, and packages
  • POST-only API layer — centralized endpoints with React Query hooks, Bearer token auth, automatic cache invalidation
  • Responsive layout — sidebar + top-nav with mobile overlay, auto-close on navigation
  • Zero config — everything is wired: providers, store, routes, permissions, menu, theme — just start building modules

Quick Start

# Create a new project
npx rvx-cli my-app

# Or pass the name directly
npx rvx-cli my-app

The CLI will:

  1. Prompt for a project name (or accept it as an argument)
  2. Let you select which features to include via interactive checkboxes
  3. Copy the template, strip deselected features, and adjust all code accordingly
  4. Run npm install automatically
  5. Output next steps
cd my-app
npm run dev       # Start dev server (default: http://localhost:5173)
npm run build     # Production build
npm run preview   # Preview production build

Feature Selection

During scaffolding, you choose which features to include:

? Select features:
  ◉ Tailwind CSS
  ◉ shadcn/ui
  ◉ Redux Toolkit
  ◉ React Router

? Choose theme:
❯ Default                        · Neutral monochrome shadcn palette
  Atlas — Enterprise Fintech     · Maroon gradient, slate neutrals, Inter typography, full component library

| Feature | Default | What happens when disabled | |---------|---------|---------------------------| | Tailwind CSS | On | Removes Tailwind packages, generates plain CSS reset, strips className attributes | | shadcn/ui | On | Removes Radix/lucide packages, replaces <Button> with plain <button>, removes components/ui/, lib/, hooks/alert/. Auto-enables Tailwind if shadcn is selected | | Redux Toolkit | On | Removes Redux packages, deletes src/features/ directory, module generator skips slice creation | | React Router | On | Removes router package, deletes src/layout/, generates state-based App.jsx with inline navigation |

The module generator (hcorp:add) reads hcorp.config.json (tailwind, shadcn, redux, router, theme) and adapts generated code accordingly.


Theme System

Themes are pluggable overlays. Pick one at scaffold time. Each theme defines tokens, components, layout, pages, and head-injection scripts. The CLI auto-discovers themes from template/themes/<name>/meta.json — drop in a new folder and it appears in the prompt.

Theme directory layout

template/themes/<theme>/
├── meta.json                # { id, name, description, version }
├── tokens.css               # Tailwind v4 @theme tokens — overrides src/index.css
├── head.html                # Optional — injected before </head> in index.html (fonts, init scripts)
├── components/              # Optional — overlays src/components/<theme>/
├── layout/                  # Optional — overlays src/layout/ (sidebar, top-nav, layout, custom-routes, menu.item)
├── pages/                   # Optional — overlays src/pages/ (dashboard, auth, elements showcase)
├── constants/               # Optional — overlays src/constants/ (menu.data.js etc)
└── lib/                     # Optional — overlays src/lib/ (helpers like accent.js)

What CLI does at scaffold time

  1. Copies template/ → target dir
  2. Writes hcorp.config.json with chosen feature flags + theme: "<chosen>"
  3. If theme ≠ default and Tailwind is enabled:
    • Copies themes/<theme>/tokens.csssrc/index.css
    • Overlays components/, layout/, pages/, constants/, lib/
    • Injects head.html snippet into index.html before </head>
  4. Strips themes/ directory from final scaffold (only chosen theme materialized)

Adding a new theme

  1. Create template/themes/<your-theme>/
  2. Add meta.json:
    { "id": "your-theme", "name": "Your Theme", "description": "...", "version": "0.1.0" }
  3. Add tokens.css with Tailwind v4 @theme { ... } block
  4. (Optional) Add components/, layout/, pages/, etc.
  5. CLI auto-detects on next run

hcorp.config.json — extended shape

{
  "tailwind": true,
  "shadcn": true,
  "redux": true,
  "router": true,
  "theme": "atlas"
}

The module generator branches on theme to scaffold theme-aware module code.


Atlas Theme

A full enterprise design system inspired by Hindalco V2 — premium B2B fintech aesthetic.

What it ships

  • Layout — Grid-based, sticky sidebar with collapse-to-icon mode + fly-out tooltips, blurred 60px topbar with breadcrumb / ⌘K search / notifications / help / profile pill
  • Dashboard — Featured KPI + 4 standard KPIs, line+bar chart, alerts list, top customers tile-list, activity timeline
  • Auth page — Split-panel design: gradient brand panel (logo + headline + stats) + form panel with show-password toggle, remember-me, animated submit
  • Element showcase/elements route with 18 demo pages, each with live preview + JSX code snippet + props table + extension notes
  • ⌘K command palette — global keyboard shortcut, items wired in layout.jsx for navigation
  • Toast system — bottom-right stack, 4 tones (success/warn/danger/info), auto-dismiss + manual close, useToast() hook
  • Tweaks fab — floating bottom-right button opens accent picker + density toggle
  • Density toggle[data-density="compact"] reduces row heights and gaps, persisted in localStorage
  • Inter + JetBrains Mono — loaded via Google Fonts preconnect (no FOUC)

Atlas color tokens

Tailwind utilities exposed by the active palette:

| Class | CSS var | Used for | |-------|---------|----------| | bg-primary / text-primary | --color-primary | Primary buttons, sidebar active, focus elements | | bg-accent / text-accent | --color-accent | Brighter accent — chips, highlights | | bg-atlas-1 / bg-atlas-2 / bg-atlas-deep | --color-atlas-* | Brand tones (gradient stops) | | bg-atlas-soft / bg-atlas-soft-2 | --color-atlas-soft* | 10% / 6% brand tints | | bg-atlas-{success,warn,danger,info} | --color-atlas-* | Semantic tones (constant across palettes) | | bg-atlas-{success,warn,danger,info}-soft | --color-atlas-*-soft | Semantic tints | | bg-atlas-surface-2 / surface-3 | --color-atlas-surface-* | Subtle layered backgrounds | | text-atlas-{fg,muted,subtle,faint} | --color-atlas-* | Text scale | | var(--atlas-gradient) | inline style | 135° brand gradient (or solid in solid mode) | | shadow-atlas-{sm,md,lg} | preset | Three shadow elevations |


Atlas Components

29 reusable components in src/components/atlas/. All exported via barrel import { ... } from "@/components/atlas".

Layout & content

| Component | Purpose | |-----------|---------| | Card, CardHead, CardBody, CardFoot | Surface container with optional head/body/foot | | PageHeader | Top-of-page header — eyebrow, title, subtitle, actions | | ViewHeader | Detail-page header with inline meta row | | KvGrid | Read-only field grid (1–4 cols) — { label, value, mono?, muted? }[] | | EmptyState | Placeholder when no records — icon + title + description + action |

Data display

| Component | Purpose | |-----------|---------| | DataTable | Sticky-header table with custom render + alignment + row click | | ListPage | DataTable + chip filters + search input shell | | TileList | Vertical list with title, subtitle, value, optional progress bar | | Timeline | Vertical timeline with connector + dot markers |

Status & metrics

| Component | Purpose | |-----------|---------| | StatusPill | Auto-toned status badge — pass any business status | | Badge | Generic counts/labels — 8 variants × 3 sizes, optional dot/icon | | KPI | Headline metric tile — label, value, delta pill, sparkline. Featured variant uses gradient | | Sparkline | Inline mini-chart (pure SVG) | | LineBarChart | Composite line + bar chart (pure SVG, no deps) |

Form primitives

| Component | Purpose | |-----------|---------| | Input | Text/email/password/etc — 38px height, focus glow, optional icon, error state | | Select | Native select with custom chevron arrow | | Textarea | Resizable textarea matching Input style | | Toggle | Switch — checked uses gradient, sm/md/lg sizes | | Checkbox | Custom checkbox — checked uses gradient | | Tabs, TabList, Tab, TabPanel | Underline-style tabs with optional badges | | Stepper | Multi-step wizard progress (numbered circles + connectors) | | FormSection, Field, FormGrid | Card shell + label/hint/error wrapper + responsive grid |

Overlays

| Component | Purpose | |-----------|---------| | Modal | Centered dialog with backdrop blur — sm/md/lg/xl sizes, esc + click-outside | | ToastProvider + useToast() | Bottom-right toast stack — 4 tones, auto-dismiss, persistent option | | Dropdown, DropdownItem, DropdownDivider, DropdownLabel | Click-outside menu | | CommandPaletteProvider + useCommandPalette() | ⌘K global search + actions | | TweaksFab | Floating bottom-right gear — opens accent picker + density toggle |

Theme controls

| Component | Purpose | |-----------|---------| | AccentPicker | Reusable swatch picker — palette + style toggle, persists to localStorage |

Element showcase

Atlas ships with /elements route — an interactive component catalog. Each tile links to /elements/<name> showing live preview + JSX code snippet + props table + usage notes. 18 pages total (Colors, PageHeader, ViewHeader, StatusPill, Badge, EmptyState, Table, ListPage, KPI, Chart, FormControls, Tabs, Stepper, Modal, Toast, Dropdown, CommandPalette + index).


Color Palettes & Live Switcher

Atlas ships with 6 accent palettes × 2 styles. Switch live via the floating tweaks fab (bottom-right) or programmatically via setAccent() from @/lib/accent.

Palettes

| ID | Name | From → To | |----|------|-----------| | maroon | Maroon (default) | #a34d51#8a1a1f | | red | Red | #ef4444#b91c1c | | green | Green | #22c55e#15803d | | blue | Blue | #3b82f6#1d4ed8 | | purple | Purple | #a855f7#7e22ce | | teal | Teal | #14b8a6#0f766e |

Styles

  • gradient — 135° linear-gradient from light to dark stop (default)
  • solid — flat dark stop only

How it works

<html data-accent="maroon" data-accent-style="gradient">

Each palette block in themes/atlas/tokens.css redefines --color-primary, --color-accent, --color-ring, --color-atlas-1/2/deep/soft/soft-2, --color-sidebar-primary/ring, --atlas-gradient, --atlas-shadow-glow. Tailwind utilities (bg-primary, text-atlas-2, etc.) auto-update at runtime — no rebuild.

Persistence

Inline init script in index.html reads localStorage.atlas-accent + atlas-accent-style and sets the data-attrs before React renders — no flash of unstyled content.

Adding a new palette

  1. Add a [data-accent="<name>"] { ... } block in tokens.css
  2. Add an entry to ACCENTS in lib/accent.js:
    { id: "orange", name: "Orange", from: "#fb923c", to: "#c2410c" }
  3. Picker auto-updates

Programmatic API

import { setAccent, setAccentStyle, getAccent, ACCENTS } from "@/lib/accent";

setAccent("blue");           // switch palette
setAccentStyle("solid");     // switch style
getAccent();                 // → "blue"

Project Structure

src/
├── components/ui/                    # shadcn/ui components (Button, AlertDialog, Switch)
├── constants/
│   ├── api/
│   │   └── api.js                    # Centralized API endpoints with crud() helper
│   ├── config/
│   │   ├── permissions.js            # Permission constants per module
│   │   ├── colors.js                 # Color palette for JS usage (charts, inline styles)
│   │   └── text.js                   # UI text/label constants
│   └── data/
│       └── menu.data.js              # Sidebar menu structure with permission filtering
├── context/
│   ├── auth/auth.context.jsx         # Auth state + permission engine (can, canAny, canAll)
│   ├── theme/theme.context.jsx       # Light/dark theme toggle
│   └── mobile/mobile.context.jsx     # Mobile detection + sidebar state
├── features/
│   ├── store.js                      # Redux store configuration
│   └── master/
│       └── <module>/
│           └── <module>.slice.js     # Redux slice with CRUD reducers
├── hooks/
│   ├── alert/use-alert.jsx           # Alert dialog state management hook
│   └── api/use-api.jsx               # React Query hooks (useApiQuery, useApiMutation)
├── layout/
│   ├── layout.jsx                    # Main layout (sidebar + top-nav + Outlet)
│   ├── top-nav.jsx                   # Header with hamburger, user info, logout
│   ├── sidebar.jsx                   # Permission-filtered navigation sidebar
│   ├── custom-routes.jsx             # Route definitions with ProtectedRoute wrapper
│   └── menu.item.jsx                 # NavLink menu item with active state
├── lib/
│   └── utils.js                      # cn() utility for Tailwind class merging
├── pages/views/
│   ├── dashboard.jsx                 # Dashboard landing page
│   ├── auth/
│   │   ├── auth.jsx                  # Login page with form
│   │   └── auth.service.js           # Auth API hooks (login, logout, me)
│   ├── admin/
│   │   └── permissions.jsx           # Permission toggle admin page (live testing)
│   ├── master/
│   │   └── <module>/
│   │       ├── <module>.jsx          # Module list page
│   │       ├── <module>.service.js   # Module API hooks (React Query)
│   │       └── add/
│   │           └── add.<module>.jsx  # Module add/create page
│   ├── dev-guide/
│   │   ├── dev-guide.jsx             # Interactive developer documentation
│   │   └── sections.jsx              # All documentation sections
│   └── errors/
│       ├── not-found.jsx             # 404 page
│       ├── server-error.jsx          # 500 page
│       ├── unauthorized.jsx          # 403 page
│       └── countdown-redirect.jsx    # Auto-redirect with countdown timer
├── main.jsx                          # Entry point with all providers stacked
└── index.css                         # Tailwind v4 theme variables + base styles

Module Generator

The most powerful feature — generate a complete CRUD module with a single command:

npm run hcorp:add product

What it creates

| File | Path | |------|------| | List page | src/pages/views/master/product/product.jsx | | Add page | src/pages/views/master/product/add/add.product.jsx | | Service file | src/pages/views/master/product/product.service.js | | Redux slice | src/features/master/product/product.slice.js |

What it updates automatically

| File | Change | |------|--------| | constants/api/api.js | Adds PRODUCT: crud("product") — generates LIST, CREATE, UPDATE, DELETE, SEARCH endpoints | | constants/config/permissions.js | Adds PRODUCT: { VIEW, ADD, EDIT, DELETE } permission block | | features/store.js | Imports and registers productReducer | | constants/data/menu.data.js | Adds "Product" to the Master menu group with product.view permission | | layout/custom-routes.jsx | Adds protected routes for /master/product and /master/product/add | | context/auth/auth.context.jsx | Adds product.view, product.button.add, product.button.edit, product.button.delete to default permissions |

Config-aware generation

The generator reads hcorp.config.json and adapts:

  • No Redux — skips slice file and store registration
  • No Router — skips route registration and useNavigate
  • No shadcn — uses plain <button> instead of <Button>
  • No Tailwind — omits all className attributes

Permission System

Permission format

Every module follows a strict 4-permission pattern:

<module>.view              → View the module page
<module>.button.add        → Show add button / access add page
<module>.button.edit       → Show edit button
<module>.button.delete     → Show delete button

Permission constants

// src/constants/config/permissions.js
export const PERMISSIONS = {
  CUSTOMER: {
    VIEW: "customer.view",
    ADD: "customer.button.add",
    EDIT: "customer.button.edit",
    DELETE: "customer.button.delete",
  },
};

Three permission functions

import { useAuth } from "@/context/auth/auth.context";

const { can, canAny, canAll } = useAuth();

can("customer.view")                                    // single check
canAny(["customer.button.add", "customer.button.edit"]) // has ANY of these
canAll(["customer.view", "customer.button.delete"])      // has ALL of these

Super admin bypass

Roles super-admin, Super Admin, or superadmin (case-insensitive) automatically pass all permission checks.

Where permissions are enforced

| Layer | How | |-------|-----| | Buttons | {can(PERMISSIONS.CUSTOMER.ADD) && <Button>Add</Button>} | | Menu items | permission: "customer.view" in menu.data.js — sidebar auto-filters | | Routes | <ProtectedRoute permission="customer.view"> wrapper | | Pages | can() / canAny() / canAll() in component logic |

Admin permissions page

Navigate to /admin/permissions to toggle permissions in real-time with switch toggles. Shows all registered permissions with a live JSON view — useful for testing permission-based UI behavior during development.


API Layer

Endpoint definitions

// src/constants/api/api.js
const BASE_URL = import.meta.env.VITE_API_BASE_URL || "";

function crud(module) {
  return {
    LIST:   `${BASE_URL}/api/${module}/list`,
    CREATE: `${BASE_URL}/api/${module}/create`,
    UPDATE: `${BASE_URL}/api/${module}/update`,
    DELETE: `${BASE_URL}/api/${module}/delete`,
    SEARCH: `${BASE_URL}/api/${module}/search`,
  };
}

export const API = {
  AUTH: {
    LOGIN:  `${BASE_URL}/api/auth/login`,
    LOGOUT: `${BASE_URL}/api/auth/logout`,
    ME:     `${BASE_URL}/api/auth/me`,
  },
  CUSTOMER: crud("customer"),
};

Custom endpoints

export const API = {
  // ... existing
  REPORT: {
    SALES: `${BASE_URL}/api/report/sales`,
    INVENTORY: `${BASE_URL}/api/report/inventory`,
  },
};

The crud() helper generates 5 standard endpoints per module. The module generator auto-adds MODULE: crud("module") when you run hcorp:add.


Service Files & React Query

Each module has a service file that exports React Query hooks. The hooks connect API endpoints to the useApi hook.

useApiQuery — Fetching data

import { useApiQuery } from "@/hooks/api/use-api";
import { API } from "@/constants/api/api";

const { data, isLoading, error } = useApiQuery(
  ["customer", "list"],     // cache key
  API.CUSTOMER.LIST,        // endpoint URL
  { page: 1, limit: 10 }   // POST body (optional)
);

| Param | Type | Description | |-------|------|-------------| | key | string \| string[] | React Query cache key | | endpoint | string | Full API URL from api.js | | body | object | POST body (auto JSON.stringify) | | options | object | React Query options (enabled, staleTime, etc.) |

useApiMutation — Create/Update/Delete

import { useApiMutation } from "@/hooks/api/use-api";
import { API } from "@/constants/api/api";

const { mutate, isPending } = useApiMutation(
  API.CUSTOMER.CREATE,
  {
    invalidateKeys: [["customer", "list"]],  // auto-refresh list after success
    onSuccess: (data) => { /* handle */ },
    onError: (error) => { /* handle */ },
  }
);

mutate({ name: "John", email: "[email protected]" });

| Param | Type | Description | |-------|------|-------------| | endpoint | string | Full API URL from api.js | | options.invalidateKeys | string[][] | Cache keys to invalidate on success | | options.onSuccess | function | Success callback | | options.onError | function | Error callback |

What the hook handles automatically

  • All requests use POST method
  • Bearer token from localStorage.getItem("token")
  • Auto JSON.stringify of request body
  • Auto JSON parse of response
  • Error extraction from response body
  • Cache invalidation on mutation success
  • Loading / error / data states via React Query

Service file pattern

// src/pages/views/master/customer/customer.service.js
import { useApiQuery, useApiMutation } from "@/hooks/api/use-api";
import { API } from "@/constants/api/api";

export function useCustomerList(params) {
  return useApiQuery(["customer", "list", params], API.CUSTOMER.LIST, params);
}

export function useCustomerCreate(options = {}) {
  return useApiMutation(API.CUSTOMER.CREATE, {
    invalidateKeys: [["customer", "list"]],
    ...options,
  });
}

export function useCustomerUpdate(options = {}) {
  return useApiMutation(API.CUSTOMER.UPDATE, {
    invalidateKeys: [["customer", "list"]],
    ...options,
  });
}

export function useCustomerDelete(options = {}) {
  return useApiMutation(API.CUSTOMER.DELETE, {
    invalidateKeys: [["customer", "list"]],
    ...options,
  });
}

Redux Store

Redux Toolkit is used for client-side global state. Server/API state is handled by React Query.

Store configuration

// src/features/store.js
import { configureStore } from "@reduxjs/toolkit";
import customerReducer from "./master/customer/customer.slice";

export const store = configureStore({
  reducer: {
    customer: customerReducer,
  },
});

Slice pattern

// src/features/master/customer/customer.slice.js
import { createSlice } from "@reduxjs/toolkit";

const customerSlice = createSlice({
  name: "customer",
  initialState: { list: [], loading: false, error: null },
  reducers: {
    setCustomers: (state, action) => { state.list = action.payload; },
    addCustomer: (state, action) => { state.list.push(action.payload); },
    updateCustomer: (state, action) => {
      const index = state.list.findIndex(item => item.id === action.payload.id);
      if (index !== -1) state.list[index] = action.payload;
    },
    removeCustomer: (state, action) => {
      state.list = state.list.filter(item => item.id !== action.payload);
    },
    setLoading: (state, action) => { state.loading = action.payload; },
    setError: (state, action) => { state.error = action.payload; },
  },
});

export const { setCustomers, addCustomer, updateCustomer, removeCustomer, setLoading, setError } = customerSlice.actions;
export default customerSlice.reducer;

When to use what

| Use Case | Technology | |----------|-----------| | API data (fetch, create, update, delete) | React Query (useApiQuery / useApiMutation) | | Client-only global state (UI state, selections) | Redux Toolkit | | Auth / Theme / Mobile state | Context API |


Auth Context

// src/context/auth/auth.context.jsx
import { useAuth } from "@/context/auth/auth.context";

const {
  user,                    // { name, email, role, permissions } | null
  isAuthenticated,         // boolean
  can(permission),         // check single permission
  canAny([permissions]),   // check if user has ANY
  canAll([permissions]),   // check if user has ALL
  isSuperAdmin(),          // check super admin role
  login(userData),         // set user + save token to localStorage
  logout(),                // clear user + token
  updatePermissions([]),   // update permission array
  updateRole(role),        // update user role
} = useAuth();

Login flow

const { login } = useAuth();

login({
  name: "John",
  email: "[email protected]",
  role: "admin",
  permissions: ["customer.view", "customer.button.add"],
  token: "jwt-token-from-api",
});
// Token is stored in localStorage automatically

Default demo user

The template ships with a default super-admin user for development with all permissions pre-enabled. Replace this with your actual auth API integration.


Routing & Protected Routes

Route structure

/auth                    → Login page (public, redirects if authenticated)
/                        → Dashboard (authenticated)
/master/customer         → Customer list (permission: customer.view)
/master/customer/add     → Add customer (permission: customer.button.add)
/admin/permissions       → Permission admin (authenticated)
/dev-guide               → Developer guide (authenticated)
/error/unauthorized      → 403 page
/error/server-error      → 500 page
*                        → 404 page

ProtectedRoute wrapper

function ProtectedRoute({ children, permission }) {
  const { isAuthenticated, can } = useAuth();

  if (!isAuthenticated) return <Navigate to="/auth" replace />;
  if (permission && !can(permission)) return <Navigate to="/error/unauthorized" replace />;

  return children;
}

Adding a route manually

// In custom-routes.jsx
import Product from "@/pages/views/master/product/product";

<Route
  path="master/product"
  element={
    <ProtectedRoute permission="product.view">
      <Product />
    </ProtectedRoute>
  }
/>

Or use npm run hcorp:add product to do this automatically.


Layout System

┌────────────────────────────────────────────────┐
│                   TopNav                        │
│  [☰]           App Name           [User] [Out] │
├──────────┬─────────────────────────────────────┤
│          │                                     │
│ Sidebar  │           <Outlet />                │
│  (w-64)  │        (page content)               │
│          │                                     │
│  Menu    │                                     │
│  Items   │                                     │
│          │                                     │
└──────────┴─────────────────────────────────────┘

| File | Purpose | |------|---------| | layout/layout.jsx | Main wrapper — renders Sidebar + TopNav + <Outlet /> for child routes | | layout/top-nav.jsx | Header with hamburger toggle, user display, and logout button | | layout/sidebar.jsx | Navigation sidebar with permission-filtered menu items, grouped sections | | layout/menu.item.jsx | Individual NavLink item with active state styling |

The sidebar width is w-64 (256px). On desktop it pushes content via ml-64. On mobile it overlays with a backdrop.


Sidebar & Menu Configuration

// src/constants/data/menu.data.js
import { LayoutDashboard, Users, Shield } from "lucide-react";

export const MENU_ITEMS = [
  {
    title: "Dashboard",
    items: [
      { label: "Dashboard", path: "/", icon: LayoutDashboard, permission: null },
    ],
  },
  {
    title: "Master",
    items: [
      { label: "Customer", path: "/master/customer", icon: Users, permission: "customer.view" },
    ],
  },
  {
    title: "Admin",
    items: [
      { label: "Permissions", path: "/admin/permissions", icon: Shield, permission: null },
    ],
  },
];
  • Set permission: null to make an item always visible to authenticated users
  • Set permission: "module.view" to filter by permission — the sidebar auto-hides items the user can't access
  • Icons are from lucide-react
  • The module generator automatically adds entries to the Master group

Theme & Styling

Tailwind CSS v4 theme variables

All theme colors are defined as CSS variables in src/index.css:

@theme {
  --color-background: #ffffff;
  --color-foreground: #0a0a0a;
  --color-primary: #171717;
  --color-primary-foreground: #fafafa;
  --color-secondary: #f5f5f5;
  --color-muted: #f5f5f5;
  --color-muted-foreground: #737373;
  --color-accent: #f5f5f5;
  --color-destructive: #ef4444;
  --color-border: #e5e5e5;
  --color-sidebar-background: #fafafa;
  --color-sidebar-foreground: #404040;
  --radius-sm: 0.25rem;
  --radius-md: 0.375rem;
  --radius-lg: 0.5rem;
}

Using theme colors

<div className="bg-primary text-primary-foreground">Primary</div>
<div className="bg-destructive">Error</div>
<div className="text-muted-foreground">Subtle text</div>
<div className="bg-sidebar-background">Sidebar</div>

Theme toggle (light/dark)

import { useTheme } from "@/context/theme/theme.context";

const { theme, toggleTheme } = useTheme();
// theme is "light" or "dark"

Color constants for JavaScript

// For charts, inline styles, etc.
import { COLORS } from "@/constants/config/colors";
// COLORS.primary, COLORS.danger, COLORS.success, etc.

To change the entire app's color scheme, edit the CSS variables in index.css — all components reference these variables.


Mobile & Responsive

import { useMobile } from "@/context/mobile/mobile.context";

const { isMobile, sidebarOpen, setSidebarOpen, toggleSidebar } = useMobile();

| Property | Type | Description | |----------|------|-------------| | isMobile | boolean | true when viewport < 768px | | sidebarOpen | boolean | Current sidebar state | | setSidebarOpen | function | Set sidebar state directly | | toggleSidebar | function | Toggle sidebar open/close |

Behavior:

  • Desktop: sidebar starts open, pushes main content
  • Mobile: sidebar starts closed, opens as overlay with backdrop
  • Auto-closes on mobile when a menu item is clicked
  • Listens to window resize events

Alert Dialog System

Use the useAlert hook instead of window.alert() or window.confirm():

import { useAlert } from "@/hooks/alert/use-alert";
import {
  AlertDialog, AlertDialogContent, AlertDialogHeader,
  AlertDialogTitle, AlertDialogDescription,
  AlertDialogFooter, AlertDialogAction, AlertDialogCancel,
} from "@/components/ui/alert-dialog";

function MyComponent() {
  const { alertState, showAlert, handleConfirm, handleCancel } = useAlert();

  const handleDelete = () => {
    showAlert({
      title: "Delete Customer",
      description: "Are you sure? This cannot be undone.",
      onConfirm: () => { /* delete logic */ },
    });
  };

  return (
    <>
      <Button onClick={handleDelete}>Delete</Button>

      <AlertDialog open={alertState.open}>
        <AlertDialogContent>
          <AlertDialogHeader>
            <AlertDialogTitle>{alertState.title}</AlertDialogTitle>
            <AlertDialogDescription>{alertState.description}</AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel onClick={handleCancel}>Cancel</AlertDialogCancel>
            <AlertDialogAction onClick={handleConfirm}>Confirm</AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>
    </>
  );
}

| Method | Description | |--------|-------------| | showAlert({ title, description, onConfirm, onCancel }) | Open the dialog | | handleConfirm() | Execute onConfirm and close | | handleCancel() | Execute onCancel and close | | closeAlert() | Close without running callbacks | | alertState.open | Boolean — is dialog currently open |


shadcn/ui Components

Pre-installed components in src/components/ui/:

| Component | File | Radix Primitive | |-----------|------|----------------| | Button | button.jsx | @radix-ui/react-slot | | AlertDialog | alert-dialog.jsx | @radix-ui/react-alert-dialog | | Switch | switch.jsx | @radix-ui/react-switch |

Button variants

import { Button } from "@/components/ui/button";

<Button>Default</Button>
<Button variant="destructive">Delete</Button>
<Button variant="outline">Cancel</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="link">Link</Button>
<Button size="sm">Small</Button>
<Button size="lg">Large</Button>
<Button size="icon"><Icon /></Button>

Adding more shadcn components

Since this is JavaScript (not TypeScript), npx shadcn@latest add won't work directly. Instead:

  1. Copy the component source from shadcn/ui docs
  2. Create the file in src/components/ui/
  3. Remove TypeScript types
  4. Install the required Radix primitive: npm install @radix-ui/react-*

Environment Variables

All env vars must be prefixed with VITE_ to be exposed to client code.

| File | When Loaded | Git Tracked | |------|-------------|-------------| | .env | Always (base defaults) | Yes | | .env.local | Always, overrides .env | No | | .env.staging | When --mode staging | Yes | | .env.production | When building for prod | Yes |

Default variables

VITE_API_BASE_URL=http://localhost:3000
VITE_APP_NAME=HCorp App
VITE_PORT=5173

Accessing in code

const baseUrl = import.meta.env.VITE_API_BASE_URL;
const appName = import.meta.env.VITE_APP_NAME;

VITE_API_BASE_URL is used inside constants/api/api.js to construct all endpoint URLs — you never need to reference it directly elsewhere.

Staging build

npx vite build --mode staging

Configuration File

After scaffolding, hcorp.config.json is created in the project root:

{
  "tailwind": true,
  "shadcn": true,
  "redux": true,
  "router": true
}

This file tells the module generator which features are available. Do not manually change it after project creation — it reflects what was installed during scaffolding.


Naming Conventions

File names

| Type | Pattern | Example | |------|---------|---------| | Page | <module>.jsx | customer.jsx | | Add Page | add.<module>.jsx | add.customer.jsx | | Service | <module>.service.js | customer.service.js | | Slice | <module>.slice.js | customer.slice.js | | Context | <name>.context.jsx | auth.context.jsx | | Hook | use-<name>.jsx | use-api.jsx | | Data | <name>.data.js | menu.data.js |

Service hook names

use<Module>List     → useCustomerList
use<Module>Create   → useCustomerCreate
use<Module>Update   → useCustomerUpdate
use<Module>Delete   → useCustomerDelete

Redux slice actions

set<Module>s    → setCustomers
add<Module>     → addCustomer
update<Module>  → updateCustomer
remove<Module>  → removeCustomer

Permission names

<module>.view              → customer.view
<module>.button.add        → customer.button.add
<module>.button.edit       → customer.button.edit
<module>.button.delete     → customer.button.delete

Folder structure per module

src/
├── constants/api/api.js             → API.CUSTOMER: crud("customer")
├── constants/config/permissions.js  → PERMISSIONS.CUSTOMER
├── features/master/customer/        → customer.slice.js
└── pages/views/master/customer/
    ├── customer.jsx                 → List page
    ├── customer.service.js          → React Query hooks
    └── add/
        └── add.customer.jsx         → Add page

Built-in Dev Guide

Every scaffolded project includes an interactive developer guide at /dev-guide. It covers:

  • Overview & tech stack
  • Installation & setup
  • Project structure & naming conventions
  • Environment configuration
  • API layer & endpoint definitions
  • useApiQuery & useApiMutation hook reference
  • Service file patterns
  • Redux store & slice patterns
  • Auth context & login flow
  • Permission system (format, constants, functions, UI usage)
  • Protected routes
  • Layout system
  • Sidebar & menu configuration
  • shadcn/ui components
  • Alert hook
  • Module generator (hcorp:add) with generated file examples
  • Theme & Tailwind configuration
  • hcorp.config.json reference
  • Vite configuration

The guide is built as a React page with sidebar navigation — it's a living reference that stays in sync with the template.


Error Pages

| Page | Route | Description | |------|-------|-------------| | Not Found | /error/not-found or * | 404 page with countdown redirect to home | | Server Error | /error/server-error | 500 page with countdown redirect | | Unauthorized | /error/unauthorized | 403 page with countdown redirect |

All error pages use the CountdownRedirect component that displays a countdown timer and auto-redirects to the home page.


Tech Stack

| Technology | Version | Purpose | |-----------|---------|---------| | Vite | 6.0 | Build tool & dev server | | React | 18.3 | UI library (JavaScript only, no TypeScript) | | Tailwind CSS | 4.1 | Utility-first styling via Vite plugin (@theme tokens, runtime data-attr palette swap) | | shadcn/ui | — | Accessible UI components (Radix + Tailwind) | | Redux Toolkit | 2.5 | Client-side global state management | | React Router | 7.1 | Client-side routing with protected routes | | TanStack React Query | 5.62 | Server state, caching, API call management | | Lucide React | 0.468 | Icon library | | Radix UI | — | Accessible primitives (AlertDialog, Switch, Slot) | | Inter + JetBrains Mono | latest | Atlas typography (loaded via Google Fonts preconnect) |


What's New in 1.2.0

  • Theme system — pluggable, overlay-based, auto-discovered from template/themes/<name>/meta.json
  • Atlas theme — 29 components, 18 element showcase pages, custom dashboard + auth, full Hindalco V2 design language (maroon gradient, 60px header, Inter typography)
  • 6 accent palettes × 2 styles — switchable at runtime via TweaksFab (bottom-right floating button), persists to localStorage
  • ⌘K command palette — global keyboard search wired to topbar
  • Toast / Modal / Dropdown — overlay system with useToast() and useCommandPalette() hooks
  • Density toggle[data-density="compact"] reduces row heights and gaps
  • Module generator branches on theme — Atlas modules use ListPage + StatusPill + FormSection
  • Default theme remains byte-identical to v1.0.x output when chosen

See CHANGELOG.md for full version history.


License

MIT