Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

201 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸš€ React Base Project

Roketin Banner

πŸ“– Storybook β€’ 🌐 R-KIT β€’ 🚧 R-KIT Dev

CI ESLint Tests TypeScript

Quality Gate Coverage Bugs Code Smells

πŸ’» A Modern Dashboard Starter powered by React, Vite, and Roketin conventions.


πŸ“š Table of Contents

  1. Overview
  2. Tech Stack
  3. Quick Start
  4. Available Scripts
  5. Configuration
  6. Localization & Sidebar Menus
  7. Feature Flags & Module Configs
  8. Module Generator
  9. Project Structure
  10. Conventions & Tooling

🧭 Overview

This repository provides a batteries-included dashboard scaffold using the Roketin design system. It ships with authenticated layouts, reusable UI primitives, strict linting, and testing utilities so teams can focus on feature delivery instead of project setup.


πŸ›  Tech Stack

  • Framework: React 19 + Vite 7
  • State & Data: Zustand, React Query, Immer
  • Routing & Auth: React Router v7, custom guards, permission helpers
  • UI & Styling: Tailwind CSS (via @tailwindcss/vite), Lucide icons
  • Forms & Validation: React Hook Form, Yup, reusable form primitives
  • Testing: Vitest, @testing-library, MSW
  • Quality: ESLint (strict config), Prettier, Husky hooks, lint-staged, Commitlint

⚑ Quick Start

Requires Node.js β‰₯ 18 and pnpm β‰₯ 9.

# Install dependencies
pnpm install

# Start the dev server
pnpm dev

# Create a production build
pnpm build

# Run the test suite
pnpm test

Environment variables live in .env (example values are committed). Define new feature switches in feature-flags.config.ts and flip them per environment with VITE_FEATURE_<FLAG> in your .env.


πŸ“œ Available Scripts

Command Description
pnpm dev Launches Vite dev server on port 5177.
pnpm build Type-checks and bundles the app for production.
pnpm preview Serves the production build locally.
pnpm lint Runs ESLint against the entire project.
pnpm test Executes unit/integration tests with Vitest.
pnpm test:coverage Generates coverage reports under coverage/.
pnpm test:ui Starts the Vitest UI runner.
pnpm commit Interactive commit message helper (Commitlint prompt).
pnpm roketin Custom CLI for scaffolding modules and associated files.
pnpm storybook Runs Storybook on http://localhost:6006 for base components.
pnpm storybook:build Produces the static Storybook build under storybook-static/.

πŸ”§ Configuration

Application-level knobs reside in roketin.config.ts. Adjusting this file lets you rebrand, resize the sidebar, change filter persistence, switch admin prefixes, and more without touching component code.

Section Keys Purpose
app name, shortName, tagline Populates branding components such as RBrand and copy used around the shell.
sidebar.settings stateStorage.type, stateStorage.key, width, widthMobile, widthIcon, keyboardShortcut Persists sidebar open/close state, controls widths for desktop/mobile/icon modes, and binds the keyboard toggle key.
filters.persistence enabled, strategy, keyPrefix, debounceMs Configures how RFilter remembers selections (e.g., local-storage, custom key prefixes, debounce window).
routes.admin basePath Base path prepended to every authenticated route (default /r-admin).
languages enabled, debug, supported[] (code, label, isDefault) Toggles multi-language support, i18n debug logging, and declares the supported locale list.

Config Breakdown

  • app: Supplies the product name, short label, and tagline shown in default headers/footers. Perfect for rebranding a white-label deployment.
  • sidebar.settings:
    • stateStorage.type: Where to persist open/closed state (local-storage by default).
    • stateStorage.key: Storage namespace so multiple apps can coexist on one domain.
    • width, widthMobile, widthIcon: CSS-ready values exposed as custom properties.
    • keyboardShortcut: Letter bound to ⌘/Ctrl + key to toggle the sidebar.
  • filters.persistence: Enable or disable persistence. Choose a storage strategy, optionally set a keyPrefix, and throttle writes with debounceMs.
  • routes.admin: Change the admin prefix (e.g., /app) to relocate every nested feature route.
  • languages:
    • enabled: Hides the language dropdown when set to false.
    • debug: Mirrors i18next debug mode; handy during localisation tweaks.
    • supported: Each locale entry must define a code (used by i18next), label (UI display), and optional isDefault.

🌐 Localization & Sidebar Menus

  • Update src/modules/app/locales/app.<lang>.json when adding languages to roketin.config.ts.
  • Each feature module owns its sidebar structure through <feature>.config.ts. The config exposes menu entries pointing to translation keys (e.g., sampleForm:title). AppSidebar aggregates every module config automatically, so translations just need to exist in the feature’s locale file. When a config sets parentModuleId, its menu is appended under the parent's menu tree (with strict inheritanceβ€”no parent, no child).
  • Example locale snippet:
{
  "menu": {
    "dashboard": "Dashboard",
    "sampleForm": "Sample Form"
  }
}

🧱 Feature Flags & Module Configs

  • Single source of truth: All flag definitions live in feature-flags.config.ts. Use defineFeatureFlags({ MY_FEATURE: { env: 'VITE_FEATURE_MY_FEATURE', defaultEnabled: true }, }) to register a key, optional description, and default behaviour.
  • Environment toggles: Set VITE_FEATURE_<FLAG> in .env, .env.local, or deployment secrets. Truthy values (true, 1, on) enable the feature; falsy values disable it. Missing entries fall back to defaultEnabled.
  • Runtime helpers: Import from @/modules/app/libs/feature-flag:
    • isFeatureEnabled('SOME_FLAG') for synchronous checks (menu generation, guards, data fetching).
    • useFeatureFlag('SOME_FLAG') hook for component rendering without duplicating logic.
  • Route guards: Add handle.featureFlag = 'SOME_FLAG' (optionally handle.featureFlagFallback) in any route object to automatically short-circuit to AppNotFound when the flag is disabledβ€”this happens before auth/permission guards run.
  • Module configs: Every module exports <module>.config.ts with defineModuleConfig({ moduleId, parentModuleId?, featureFlag, menu? }). The loader src/modules/app/libs/module-config.lib.ts globs @/modules/**/*.config.ts, so nested modules are supported out of the box (src/modules/billing/modules/invoice/invoice.config.ts, etc.).
  • Sidebar menus: menu entries live next to the module and can describe tree menus, icons, and permissions. Parent modules typically define the container menu (omit name to keep it non-clickable). Children inherit the parent automatically by declaring parentModuleId. You can omit the menu field or set it to false for child modules that shouldn't appear directly in the sidebar. Use order (lower first) to customize the ordering of menus at any level. AppSidebar consumes APP_MODULE_CONFIGS, nests children beneath their parent, and skips children whenever the parent is disabled.
  • CLI automation: pnpm roketin module ... (standard preset) now scaffolds <feature>.config.ts and automatically appends the corresponding VITE_FEATURE_<FLAG> entry to feature-flags.config.ts. Review the stub (icon, permissions, translation namespace) and adjust to your needs before shipping.

Example config:

// src/modules/sample-form/sample-form.config.ts
import { defineModuleConfig } from '@/modules/app/types/module-config.type';

export const sampleFormModuleConfig = defineModuleConfig({
  moduleId: 'sample-form',
  // parentModuleId: 'billing', // Optional: attach under another module’s menu.
  featureFlag: 'SAMPLE_FORM',
  // menu: false, // Uncomment to skip sidebar entries (useful for child modules).
  menu: {
    title: 'sampleForm:title',
    name: 'SampleFormIndex',
    order: 10,
    // icon: SomeIcon,
    // permission: 'SAMPLE_FORM_VIEW',
  },
});

Sidebar hierarchy example

// Parent module acts as a collapsible header (no `name` β†’ non-clickable).
export const userModuleConfig = defineModuleConfig({
  moduleId: 'user',
  featureFlag: 'USER',
  menu: {
    title: 'user:title',
    icon: Users,
    order: 1,
  },
});

// Child module is rendered under the parent (strict: hidden when parent is disabled).
export const userGuardModuleConfig = defineModuleConfig({
  moduleId: 'user-guard',
  parentModuleId: 'user',
  featureFlag: 'USER_GUARD',
  menu: {
    title: 'user:menu.guard',
    name: 'UserGuardIndex',
  },
});

Notes:

  • Children attach to the first menu entry defined by the parent config. Remove name on the parent menu to render it as a toggle-only container, or set name if the parent should be clickable too.
  • When a parent’s feature flag is false, all children referencing that parentModuleId are hidden automatically (strict inheritance).
  • Parents that act purely as containers (no name) disappear automatically when every child is filtered out (no orphan headers are shown).
  • Use the optional order field to enforce deterministic ordering (lower values first, ties fall back to declaration order).
  • CLI scaffolding: pnpm roketin module user/guard auto-populates parentModuleId and a child menu entry; tweak the title, icon, permissions, or order as needed.

🧩 Module Generator

The CLI backing pnpm roketin lives in bin/roketin.js and scaffolds feature modules under src/modules. It understands nested structures, child routes, and optional assets.

Basic Usage

# Create a new feature module (e.g., src/modules/reporting)
pnpm roketin module reporting

# Create a nested module (prompts whether it should be treated as a child route)
pnpm roketin module reporting/summary

# Force explicit child route generation via the legacy alias
pnpm roketin module-child reporting/summary

# Inspect registered generators, presets, and restricted modules
pnpm roketin info

Workflow

  1. Greeting: The CLI renders a Roketin banner with CFonts.
  2. Path Parsing: It splits the provided path (e.g., reporting/summary) and builds the target directory. Nested segments are placed under a cascading modules folder (src/modules/reporting/modules/summary/…) so each feature can contain its own sub-modules.
  3. Generation Mode: You choose between:
    • Standard: Generates module config + feature flag entry, pages, routes, locale stub, types, and services (the default set).
    • All folders: Scaffolds every supported artifact (hooks, contexts, stores, etc.).
    • Custom: Lets you pick specific file types via a checkbox prompt.
  4. Child Routes & Auto-Linking: For nested paths, the CLI asks if the final segment should be treated as a child route. All routes use a unified naming convention (<module>.routes.tsx) regardless of nesting level. The generator automatically imports and spreads child routes inside the parent route file (including grandparents, recursively). If you already customized the parent route structure, skim the resulting diff to confirm the insertion landed where you expect.
  5. Auto-Scaffold Parents: When a parent route/config is missing (e.g., you run pnpm roketin module master-data/sales before master-data exists), the CLI now creates both the lightweight parent route scaffold and the parent module config (complete with its own feature flag). Only minimal shells are generatedβ€”no placeholder page componentβ€”so you retain full control over the actual content and menu labels.
  6. Idempotent Files: Existing files are never overwritten unless you opted in at the overwrite prompt. Skipped items are logged for visibility.
  7. Feature flags on autopilot: Whenever the config generator runs, the CLI adds (or reuses) a VITE_FEATURE_<FLAG> entry in feature-flags.config.ts so toggling a module is as simple as flipping the env value.

Route Discovery

The application uses a smart route discovery system:

  • Top-level modules (src/modules/*/routes/*.routes.tsx) are auto-discovered by app.routes.tsx
  • Nested modules (src/modules/*/modules/*/routes/*.routes.tsx) are imported by their parent module's route file
  • All route files use the same naming convention: <module>.routes.tsx (no .child suffix needed)

This means when you create a nested module like master-data/client, the CLI:

  1. Creates src/modules/master-data/modules/client/routes/client.routes.tsx
  2. Auto-imports clientRoutes into master-data.routes.tsx
  3. The parent route is discovered by app.routes.tsx, which loads all children automatically

Generated Artifacts

Depending on your selections, the generator can produce:

  • components/pages/<feature>.tsx – Page skeleton.
  • routes/<feature>.routes.tsx – Route config using createAppRoutes.
  • locales/<feature>.en.json – Locale stub for feature-specific translations.
  • services/<feature>.service.ts – API placeholder.
  • types/<feature>.type.ts – Type definitions scaffold.
  • Optional extras: hooks, contexts, stores, libs, constants, etc.

All generated route containers ship with a handle.breadcrumbOptions.disabled flag. This keeps parent breadcrumbs (e.g., β€œMaster Data”) displayed but non-clickable until you intentionally enable navigation for them.

Moving Modules

Use module:move to relocate modules between different locations:

# Promote a child module to standalone (top-level)
pnpm roketin module:move master-data/client client

# Move a module to a different parent
pnpm roketin module:move master-data/client user-management/client

# Demote a standalone module to become a child
pnpm roketin module:move client master-data/client

# Interactive mode (prompts for destination)
pnpm roketin module:move master-data/client

The command automatically handles:

  • Moving the folder to the new location
  • Updating all import paths across the codebase
  • Removing the module from its old parent route (if it was a child)
  • Adding the module to its new parent route (if becoming a child)
  • Updating route file comments to reflect the new location
  • Cleaning up empty directories

🍞 Breadcrumbs

Routes can drive breadcrumbs through the handle property, and components can register dynamic labels with a lightweight hook.

Route configuration

export const productsRoutes = createAppRoutes([
  {
    path: 'products/:sku',
    element: <ProductDetail />,
    handle: {
      breadcrumb: (match) => ({
        type: 'product',
        id: match.params.sku ?? '',
      }),
      breadcrumbOptions: {
        disabled: false,
        hide: false,
      },
    },
  },
]);
  • breadcrumb accepts either a translation key/string or a function returning { type, id } for resolver-based labels.
  • breadcrumbOptions.disabled keeps the crumb visible but non-clickable.
  • breadcrumbOptions.hide removes the crumb entirely.

Component resolver hook

import { useBreadcrumbLabel } from '@/modules/app/hooks/use-breadcrumb-resolver';

export default function ProductDetail({ sku }: { sku: string }) {
  useBreadcrumbLabel('product', (id) => productCache[id]?.name ?? id);

  return <ProductDetailView sku={sku} />;
}

useBreadcrumbLabel(type, resolver) registers the resolver while the component is mounted and cleans it up automatically. Call useResetBreadcrumbResolvers() when leaving a flow that should clear every resolver.

Optional per-page overrides

Routes remain the primary source of truth, but sometimes a page needs extra context (dynamic breadcrumbs, title overrides, or even feature guards). The optional useOverridePageConfig hook lets you inject those hints straight from the page component:

import { useOverridePageConfig } from '@/modules/app/hooks/use-page-config';

export default function SampleFormPage() {
  useOverridePageConfig(({ params }) => ({
    title: params?.id ? 'sampleForm:actions.edit' : 'sampleForm:menu.createNew',
    breadcrumbs: [
      { label: 'sampleForm:title', href: '/admin/sample-form' },
      {
        label: params?.id
          ? 'sampleForm:actions.edit'
          : 'sampleForm:menu.createNew',
      },
    ],
    permissions: ['SAMPLE_FORM_VIEW'],
    featureFlag: 'SAMPLE_FORM',
  }));

  return <div>...</div>;
}
  • The hook is optional: if you skip it, the layout falls back to the route handles defined in *.routes.tsx.
  • Returning permissions or featureFlag automatically renders AppForbidden / AppNotFound when access is denied, so sensitive pages stay guarded even without duplicating route metadata.
  • Breadcrumbs accept arrays or (ctx) => [], and they’re merged into the global breadcrumb component for a consistent UX.

πŸ“ Foldering Approaches

Two structures are supported; pick the one that fits your team’s workflow.

1. Hierarchical modules (recommended)

src/modules/
└── config/
    β”œβ”€β”€ config.config.ts
    β”œβ”€β”€ routes/config.routes.tsx
    └── modules/
        └── user/
            β”œβ”€β”€ user.config.ts
            β”œβ”€β”€ routes/user.routes.tsx
            └── components/pages/user.tsx
  • Every level mirrors the eventual route segment (config β†’ user β†’ detail), so React Router automatically nests segments and RBreadcrumbs can infer a fallback trail directly from the URL when neither the route handle nor useOverridePageConfig overrides exist.
  • moduleId/parentModuleId match the folder hierarchy, so sidebar ordering is deterministic and global feature-flag inheritance (parent off β†’ children hidden) works without extra bookkeeping.
  • New pages only need to live inside the relevant module folder: the glob loader picks up *.routes.tsx, and breadcrumbs/title fallbacks already align with the directory structure.

2. Flat modules with manual overrides

src/modules/
β”œβ”€β”€ config/
β”œβ”€β”€ user/
└── role/
  • Keep modules top-level but express nesting through defineModuleConfig:
// src/modules/user/user.config.ts
export default defineModuleConfig({
  moduleId: 'config-user',
  parentModuleId: 'config',
  menu: {
    title: 'user:title',
    name: 'UserIndex',
    order: 20,
  },
});
  • Because the file system no longer mirrors URL depth, add explicit breadcrumbs either via the route handle or, for richer/dynamic needs, useOverridePageConfig. This ensures RBreadcrumbs renders the intended hierarchy even though the folders are flat.
// src/modules/user/components/pages/user-detail.tsx
import { useOverridePageConfig } from '@/modules/app/hooks/use-page-config';

export default function UserDetailPage() {
  useOverridePageConfig(({ params }) => ({
    breadcrumbs: [
      { label: 'Config', href: '/admin/config' },
      { label: 'user:title', href: '/admin/config/user' },
      { label: params?.id ?? 'Detail' },
    ],
  }));

  return <div>...</div>;
}

Module configs still drive the menus (parent/child relationships + order control). Breadcrumbs are stitched together via the snippets above so even though folders are flat, the UI shows the desired nesting.

  • Menus are still ordered via the order field inside each config, so you can rearrange siblings without touching the folder tree.

Both approaches can coexist: legacy modules can stay flat while newer ones adopt the hierarchical layout for auto-generated breadcrumbs.

πŸ—‚ Project Structure

reactjs-base-project/
β”œβ”€β”€ roketin.config.ts          # Central app/theme/sidebar configuration
β”œβ”€β”€ package.json
β”œβ”€β”€ pnpm-lock.yaml
β”œβ”€β”€ vite.config.ts             # Vite + Vitest configuration
β”œβ”€β”€ tsconfig.json              # Root TS config (references app/node configs)
β”œβ”€β”€ .husky/                    # Git hooks (Commitlint, lint-staged)
β”œβ”€β”€ bin/
β”‚   └── roketin.js             # Module generator CLI
β”œβ”€β”€ public/                    # Static assets served by Vite
β”œβ”€β”€ dist/                      # Build output (generated)
β”œβ”€β”€ coverage/                  # Test coverage reports (generated)
β”œβ”€β”€ tests/
β”‚   β”œβ”€β”€ setup.ts               # Vitest setup
β”‚   β”œβ”€β”€ setup-msw.ts           # MSW server bootstrap
β”‚   └── unit/                  # Example unit tests
└── src/
    β”œβ”€β”€ main.tsx               # App entry point
    β”œβ”€β”€ vite-env.d.ts
    β”œβ”€β”€ @types/                # Project-level TypeScript declarations
    β”œβ”€β”€ plugins/               # Custom Vite plugins (e.g., i18n type generator)
    └── modules/
        β”œβ”€β”€ app/               # App shell, layouts, shared libs, locales
        β”œβ”€β”€ auth/              # Authentication routes and layouts
        β”œβ”€β”€ dashboard/         # Example dashboard module
        └── sample-form/       # Rich form examples and reusable widgets

modules/[feature] breakdown

  • assets/ # [Only app folder] static assets scoped to the shell (e.g., global CSS).
  • components/
    • base/ # [Only app folder] reusable atoms/molecules prefixed with r- (r-form.tsx, r-filter.tsx, etc.).
    • layouts/ # top-level layout pieces (app-layout.tsx, app-sidebar.tsx).
    • pages/ # entry-point screens for the shell (app-entry-point.tsx, error states).
    • ui/ # [Only app folder] shared components; variant tokens live under ui/variants/.
  • constants/ # shared constants and enums (permissions, menus) using kebab-case filenames.
  • contexts/ # React contexts; filename convention: <feature>-context.ts.
  • hooks/ # custom hooks (use-*.ts/x), camel-cased after use.
  • libs/ # utility functions (storage, crypto, routing) using kebab-case names.
  • locales/ # i18n resources (app.<lang>.json, validation bundles).
  • routes/ # shell route aggregator (app.routes.tsx).
  • stores/ # Zustand stores (*.store.ts suffix).
  • types/ # shared TypeScript definitions (*.type.ts suffix).
  • validators/ # schema or validation helpers (*.validator.ts).

Directories such as dist/, coverage/, and node_modules/ are generated and can be cleaned safely.


βœ… Conventions & Tooling

  • Linting: ESLint with strict TypeScript rules and hook validation.
  • Formatting: Prettier runs automatically via Husky and lint-staged on staged files.
  • Commits: Commitlint enforces Conventional Commit messages; use pnpm commit for an interactive flow.
  • Testing: Vitest mimics Jest APIs, with MSW mocking network calls. Tests bootstrap via tests/setup.ts.
  • Module Generation: pnpm roketin module <feature>/[sub-feature] scaffolds new modules following project conventions (config, feature flags, hierarchy-aware menus).

Have fun building! Contributions and issues are welcome. πŸŽ‰

About

Production-ready React boilerplate with opinionated folder structure, hooks-based state management, and testing setup.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages