
Research
/Security News
Mini Shai-Hulud Campaign Hits Red Hat Cloud Services npm Packages
A mini Shai-Hulud campaign compromised Red Hat Cloud Services npm packages to steal developer and CI/CD secrets during installation.
Scaffold a production-ready Vite + React project with permissions, routing, Redux, and shadcn/ui
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:
Most React scaffolding tools give you a blank canvas. rvx-cli gives you an opinionated, enterprise-ready architecture out of the box:
can(), canAny(), canAll() with super admin bypass, baked into routes, sidebar, and buttonsthemes/<name>/ folder and the CLI auto-discovers it. Atlas ships with 29 components + 18 demo pagesnpm 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# Create a new project
npx rvx-cli my-app
# Or pass the name directly
npx rvx-cli my-app
The CLI will:
npm install automaticallycd my-app
npm run dev # Start dev server (default: http://localhost:5173)
npm run build # Production build
npm run preview # Preview production build
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.
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.
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)
template/ → target dirhcorp.config.json with chosen feature flags + theme: "<chosen>"themes/<theme>/tokens.css → src/index.csscomponents/, layout/, pages/, constants/, lib/head.html snippet into index.html before </head>themes/ directory from final scaffold (only chosen theme materialized)template/themes/<your-theme>/meta.json:
{ "id": "your-theme", "name": "Your Theme", "description": "...", "version": "0.1.0" }
tokens.css with Tailwind v4 @theme { ... } blockcomponents/, layout/, pages/, etc.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.
A full enterprise design system inspired by Hindalco V2 — premium B2B fintech aesthetic.
/elements route with 18 demo pages, each with live preview + JSX code snippet + props table + extension noteslayout.jsx for navigationuseToast() hook[data-density="compact"] reduces row heights and gaps, persisted in localStorageTailwind 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 |
29 reusable components in src/components/atlas/. All exported via barrel import { ... } from "@/components/atlas".
| 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 |
| 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 |
| 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) |
| 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 |
| 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 |
| Component | Purpose |
|---|---|
AccentPicker | Reusable swatch picker — palette + style toggle, persists to localStorage |
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).
Atlas ships with 6 accent palettes × 2 styles. Switch live via the floating tweaks fab (bottom-right) or programmatically via setAccent() from @/lib/accent.
| 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 |
gradient — 135° linear-gradient from light to dark stop (default)solid — flat dark stop only<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.
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.
[data-accent="<name>"] { ... } block in tokens.cssACCENTS in lib/accent.js:
{ id: "orange", name: "Orange", from: "#fb923c", to: "#c2410c" }
import { setAccent, setAccentStyle, getAccent, ACCENTS } from "@/lib/accent";
setAccent("blue"); // switch palette
setAccentStyle("solid"); // switch style
getAccent(); // → "blue"
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
The most powerful feature — generate a complete CRUD module with a single command:
npm run hcorp:add product
| 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 |
| 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 |
The generator reads hcorp.config.json and adapts:
useNavigate<button> instead of <Button>className attributesEvery 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
// src/constants/config/permissions.js
export const PERMISSIONS = {
CUSTOMER: {
VIEW: "customer.view",
ADD: "customer.button.add",
EDIT: "customer.button.edit",
DELETE: "customer.button.delete",
},
};
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
Roles super-admin, Super Admin, or superadmin (case-insensitive) automatically pass all permission checks.
| 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 |
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.
// 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"),
};
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.
Each module has a service file that exports React Query hooks. The hooks connect API endpoints to the useApi hook.
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.) |
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: "john@example.com" });
| 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 |
localStorage.getItem("token")JSON.stringify of request body// 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 Toolkit is used for client-side global state. Server/API state is handled by React Query.
// src/features/store.js
import { configureStore } from "@reduxjs/toolkit";
import customerReducer from "./master/customer/customer.slice";
export const store = configureStore({
reducer: {
customer: customerReducer,
},
});
// 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;
| 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 |
// 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();
const { login } = useAuth();
login({
name: "John",
email: "john@example.com",
role: "admin",
permissions: ["customer.view", "customer.button.add"],
token: "jwt-token-from-api",
});
// Token is stored in localStorage automatically
The template ships with a default super-admin user for development with all permissions pre-enabled. Replace this with your actual auth API integration.
/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
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;
}
// 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.
┌────────────────────────────────────────────────┐
│ 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.
// 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 },
],
},
];
permission: null to make an item always visible to authenticated userspermission: "module.view" to filter by permission — the sidebar auto-hides items the user can't accessAll 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;
}
<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>
import { useTheme } from "@/context/theme/theme.context";
const { theme, toggleTheme } = useTheme();
// theme is "light" or "dark"
// 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.
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:
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 |
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 |
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>
Since this is JavaScript (not TypeScript), npx shadcn@latest add won't work directly. Instead:
src/components/ui/npm install @radix-ui/react-*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 |
VITE_API_BASE_URL=http://localhost:3000
VITE_APP_NAME=HCorp App
VITE_PORT=5173
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.
npx vite build --mode staging
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.
| 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 |
use<Module>List → useCustomerList
use<Module>Create → useCustomerCreate
use<Module>Update → useCustomerUpdate
use<Module>Delete → useCustomerDelete
set<Module>s → setCustomers
add<Module> → addCustomer
update<Module> → updateCustomer
remove<Module> → removeCustomer
<module>.view → customer.view
<module>.button.add → customer.button.add
<module>.button.edit → customer.button.edit
<module>.button.delete → customer.button.delete
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
Every scaffolded project includes an interactive developer guide at /dev-guide. It covers:
hcorp:add) with generated file examplesThe guide is built as a React page with sidebar navigation — it's a living reference that stays in sync with the template.
| 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.
| 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) |
template/themes/<name>/meta.jsonuseToast() and useCommandPalette() hooks[data-density="compact"] reduces row heights and gapsSee CHANGELOG.md for full version history.
MIT
FAQs
Scaffold a production-ready Vite + React project with permissions, routing, Redux, and shadcn/ui
We found that rvx-cli demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
A mini Shai-Hulud campaign compromised Red Hat Cloud Services npm packages to steal developer and CI/CD secrets during installation.

Research
/Security News
The North Korean malware loader hides in a Packagist-listed package and its GitHub branch to fetch and execute remote code in a likely Contagious Interview-style lure.

Security News
The Rust project is moving toward formal rules on LLM use in contributions after months of internal debate over maintainer burden, code quality, and contributor experience.