diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index f759ae2..5d1677a 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -18,7 +18,8 @@
// 4173: VitePress Docs (Preview)
// 5173: Web App (Must be strictly 5173 for Electron IPC compatibility)
// 3000: Landing App (Isolated from Vite's default 517x blast radius)
- "forwardPorts": [6060, 4173, 5173, 3000],
+ // 3005: Showcase App
+ "forwardPorts": [6060, 4173, 5173, 3000, 3005],
"portsAttributes": {
"6060": {
@@ -33,6 +34,10 @@
"label": "Web App (Electron Target)",
"onAutoForward": "notify"
},
+ "3005": {
+ "label": "Showcase App",
+ "onAutoForward": "notify"
+ },
"3000": {
"label": "Landing App (Public)",
"onAutoForward": "notify"
diff --git a/apps/docs-dev/src/apps/desktop/CONFIGURATION.md b/apps/docs-dev/src/apps/desktop/CONFIGURATION.md
index ba63405..2bb648a 100644
--- a/apps/docs-dev/src/apps/desktop/CONFIGURATION.md
+++ b/apps/docs-dev/src/apps/desktop/CONFIGURATION.md
@@ -230,7 +230,7 @@ In the target web app's entry point (e.g., `apps/web/src/apps/index.tsx`):
}
```
-All routes transition to hash-based addressing: `#/app/dashboard`, `#/auth/login`, `#/showcase`.
+All routes transition to hash-based addressing: `#/app/dashboard`, `#/auth/login`.
### Step 2: Decommission the Custom Protocol — Main Process
diff --git a/apps/docs-dev/src/packages/core-api/transformers.md b/apps/docs-dev/src/packages/core-api/transformers.md
index 614eb39..9a1b1a7 100644
--- a/apps/docs-dev/src/packages/core-api/transformers.md
+++ b/apps/docs-dev/src/packages/core-api/transformers.md
@@ -317,7 +317,7 @@ A full working example is available in the showcase booking feature:
| File | Description |
| ---- | ----------- |
-| `apps/web/.../booking/data/booking.transformer.ts` | Basic transformer with snake_case ↔ camelCase mapping |
-| `apps/web/.../booking/data/booking.data-services.ts` | Data service with injected transformer |
-| `apps/web/.../booking/data/advanced-booking.transformer.ts` | Extended transformer with custom chart method |
-| `apps/web/.../booking/data/advanced-booking.data-services.ts` | Extended service with custom `getAvailabilityChart()` |
+| `apps/showcase/.../booking/data/booking.transformer.ts` | Basic transformer with snake_case ↔ camelCase mapping |
+| `apps/showcase/.../booking/data/booking.data-services.ts` | Data service with injected transformer |
+| `apps/showcase/.../booking/data/advanced-booking.transformer.ts` | Extended transformer with custom chart method |
+| `apps/showcase/.../booking/data/advanced-booking.data-services.ts` | Extended service with custom `getAvailabilityChart()` |
diff --git a/apps/docs-dev/src/packages/ui/CORE-APP-SHELL.md b/apps/docs-dev/src/packages/ui/CORE-APP-SHELL.md
index 98d34da..ca9c16b 100644
--- a/apps/docs-dev/src/packages/ui/CORE-APP-SHELL.md
+++ b/apps/docs-dev/src/packages/ui/CORE-APP-SHELL.md
@@ -418,7 +418,7 @@ function App() {
### Interactive Config Builder
-The showcase demo at `apps/web/src/apps/showcase/shell-demo/` demonstrates a live, interactive config builder where every feature toggle and variant switch updates the layout in real-time. The key pattern is managing `config` state externally and passing it as a prop:
+The showcase demo at `apps/showcase/src/pages/showcase-original/shell-demo/` demonstrates a live, interactive config builder where every feature toggle and variant switch updates the layout in real-time. The key pattern is managing `config` state externally and passing it as a prop:
```tsx
import { useState, useMemo } from 'react';
diff --git a/apps/showcase/.eslintrc.cjs b/apps/showcase/.eslintrc.cjs
new file mode 100644
index 0000000..a779c57
--- /dev/null
+++ b/apps/showcase/.eslintrc.cjs
@@ -0,0 +1,5 @@
+/** @type {import("eslint").Linter.Config} */
+module.exports = {
+ root: true,
+ extends: ['@repo/eslint-config/react.js'],
+};
diff --git a/apps/showcase/index.html b/apps/showcase/index.html
new file mode 100644
index 0000000..e09f575
--- /dev/null
+++ b/apps/showcase/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Showcase
+
+
+
+
+
+
diff --git a/apps/showcase/package.json b/apps/showcase/package.json
new file mode 100644
index 0000000..def6253
--- /dev/null
+++ b/apps/showcase/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "showcase",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite --clearScreen false",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "lint": "eslint \"src/**/*.ts\"",
+ "test": "vitest run",
+ "test:watch": "vitest --watch",
+ "typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@hookform/resolvers": "^5.0.1",
+ "@repo/core-api": "workspace:*",
+ "@repo/core-events": "workspace:*",
+ "@repo/core-i18n": "workspace:*",
+ "@repo/core-storage": "workspace:*",
+ "@repo/ui": "workspace:*",
+ "@repo/utils": "workspace:*",
+ "@tailwindcss/vite": "^4.1.18",
+ "dayjs": "^1.11.19",
+ "events": "^3.3.0",
+ "i18next": "^24.2.2",
+ "lucide-react": "^1.22.0",
+ "react": "^19.2.3",
+ "react-dom": "^19.2.3",
+ "react-hook-form": "^7.56.4",
+ "react-i18next": "^15.4.0",
+ "react-router-dom": "^7.11.0",
+ "tailwindcss": "^4.1.18",
+ "zod": "^3.25.36",
+ "zustand": "^5.0.14"
+ },
+ "devDependencies": {
+ "@repo/eslint-config": "workspace:*",
+ "@repo/typescript-config": "workspace:*",
+ "@types/react": "^19.2.7",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^5.1.2",
+ "eslint": "^8.57.1",
+ "typescript": "5.5.4",
+ "vite": "^5.1.4",
+ "vitest": "^4.0.17"
+ }
+}
diff --git a/apps/showcase/src/App.tsx b/apps/showcase/src/App.tsx
new file mode 100644
index 0000000..e145187
--- /dev/null
+++ b/apps/showcase/src/App.tsx
@@ -0,0 +1,49 @@
+import { useState, Suspense } from 'react';
+import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
+import { ThemeProvider, DensityType } from '@repo/ui/provider';
+import { AgGridProvider } from '@repo/ui/components';
+import { useThemeStore } from './core/stores/theme.store';
+import ShowcaseLayout from './layouts/ShowcaseLayout';
+
+// Pages
+import UiComponentsPage from './pages/ui-components';
+import FormsPage from './pages/forms';
+import StoragePage from './pages/storage';
+import EventsPage from './pages/events';
+import HardwarePage from './pages/hardware';
+import RbacPage from './pages/rbac';
+import AuthPage from './pages/auth';
+import ShellDemoPage from './pages/shell-demo';
+import ActionToolsPage from './pages/action-tools';
+import AgGridPage from './pages/ag-grid';
+
+export default function App() {
+ const colorScheme = useThemeStore((s) => s.colorScheme);
+ const [density, setDensity] = useState('compact');
+
+ return (
+
+
+
+ Loading...}>
+
+ }>
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ } />
+
+
+
+
+
+ );
+}
diff --git a/apps/showcase/src/core/constants/events.ts b/apps/showcase/src/core/constants/events.ts
new file mode 100644
index 0000000..0cdd2f4
--- /dev/null
+++ b/apps/showcase/src/core/constants/events.ts
@@ -0,0 +1,21 @@
+export const LAYOUT_EVENTS = {
+ TOGGLE_HISTORY_DRAWER: 'LAYOUT:TOGGLE_HISTORY_DRAWER',
+ TOGGLE_BOOKMARK_DRAWER: 'LAYOUT:TOGGLE_BOOKMARK_DRAWER',
+} as const;
+
+export const DEVICE_EVENTS = {
+ PRINT_RECEIPT: 'DEVICE:PRINT_RECEIPT',
+} as const;
+
+export const WS_EVENTS = {
+ STOCK_UPDATE: 'WS:STOCK_UPDATE',
+} as const;
+
+export const AUTH_EVENTS = {
+ PROFILE_UPDATED: 'AUTH:PROFILE_UPDATED',
+} as const;
+
+export const APP_EVENTS = {
+ INITIALIZED: 'APP:INITIALIZED',
+ ERROR: 'APP:ERROR',
+} as const;
diff --git a/apps/showcase/src/core/hooks/use-electron-printer.ts b/apps/showcase/src/core/hooks/use-electron-printer.ts
new file mode 100644
index 0000000..bf732b4
--- /dev/null
+++ b/apps/showcase/src/core/hooks/use-electron-printer.ts
@@ -0,0 +1,110 @@
+import { useState, useCallback } from 'react';
+import { useIsElectron } from './use-is-electron';
+
+// ─── Types ──────────────────────────────────────────────────────
+
+export interface UseElectronPrinterReturn {
+ /** List of available printers (populated after calling `refreshPrinters`) */
+ printers: ElectronPrinterInfo[];
+ /** Whether a printer operation is in progress */
+ loading: boolean;
+ /** Last error message, if any */
+ error: string | null;
+ /** Whether the app is running inside Electron */
+ isElectron: boolean;
+ /** Fetch the current list of available printers */
+ refreshPrinters: () => Promise;
+ /** Print with the given options. Returns success/failure. */
+ print: (options?: ElectronPrintOptions) => Promise;
+}
+
+/**
+ * React hook for Electron printer integration.
+ *
+ * Provides methods to list available printers and trigger print jobs
+ * via the secure `window.electronAPI` bridge.
+ *
+ * Safe to use in both Electron and browser environments.
+ *
+ * @example
+ * ```tsx
+ * function PrintButton() {
+ * const { printers, refreshPrinters, print, loading } = useElectronPrinter();
+ *
+ * useEffect(() => { refreshPrinters(); }, []);
+ *
+ * const handlePrint = async () => {
+ * const result = await print({ silent: true, deviceName: printers[0]?.name });
+ * if (!result.success) alert(`Print failed: ${result.failureReason}`);
+ * };
+ *
+ * return (
+ *
+ * );
+ * }
+ * ```
+ */
+export function useElectronPrinter(): UseElectronPrinterReturn {
+ const [printers, setPrinters] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const isElectron = useIsElectron();
+
+ const refreshPrinters = useCallback(async (): Promise => {
+ if (!window.electronAPI) {
+ setError('Not running in Electron');
+ return [];
+ }
+
+ setLoading(true);
+ setError(null);
+ try {
+ const result = await window.electronAPI.getPrinters();
+ setPrinters(result);
+ return result;
+ } catch (err) {
+ const message = err instanceof Error ? err.message : 'Failed to get printers';
+ setError(message);
+ return [];
+ } finally {
+ setLoading(false);
+ }
+ }, []);
+
+ const print = useCallback(
+ async (options?: ElectronPrintOptions): Promise => {
+ if (!window.electronAPI) {
+ return { success: false, failureReason: 'Not running in Electron' };
+ }
+
+ setLoading(true);
+ setError(null);
+ try {
+ const result = await window.electronAPI.print(options);
+ if (!result.success && result.failureReason) {
+ setError(result.failureReason);
+ }
+ return result;
+ } catch (err) {
+ const message = err instanceof Error ? err.message : 'Print failed';
+ setError(message);
+ return { success: false, failureReason: message };
+ } finally {
+ setLoading(false);
+ }
+ },
+ [],
+ );
+
+ return {
+ printers,
+ loading,
+ error,
+ isElectron,
+ refreshPrinters,
+ print,
+ };
+}
diff --git a/apps/showcase/src/core/hooks/use-electron-updater.ts b/apps/showcase/src/core/hooks/use-electron-updater.ts
new file mode 100644
index 0000000..1fa8cf5
--- /dev/null
+++ b/apps/showcase/src/core/hooks/use-electron-updater.ts
@@ -0,0 +1,140 @@
+import { useState, useEffect, useCallback } from 'react';
+import { useIsElectron } from './use-is-electron';
+
+// ─── Types ──────────────────────────────────────────────────────
+
+export type UpdateStatus =
+ | 'idle'
+ | 'checking'
+ | 'available'
+ | 'not-available'
+ | 'downloading'
+ | 'ready'
+ | 'error';
+
+export interface UseElectronUpdaterReturn {
+ /** Current status of the auto-updater lifecycle */
+ status: UpdateStatus;
+ /** Download progress percentage (0–100) */
+ progress: number;
+ /** Download speed in bytes per second */
+ bytesPerSecond: number;
+ /** Information about the available/downloaded update */
+ updateInfo: ElectronUpdateInfo | null;
+ /** Error message if the updater encountered an issue */
+ errorMessage: string | null;
+ /** Whether the app is running inside Electron */
+ isElectron: boolean;
+ /** Trigger a manual update check */
+ checkForUpdates: () => void;
+ /** Quit the app and install the downloaded update */
+ installUpdate: () => void;
+}
+
+/**
+ * React hook for the Electron auto-updater.
+ *
+ * Subscribes to all update lifecycle events via `window.electronAPI`
+ * and provides reactive state for building an update notification UI.
+ *
+ * Safe to use in both Electron and browser environments — all
+ * Electron-specific calls are gated behind `window.electronAPI` checks.
+ *
+ * @example
+ * ```tsx
+ * function UpdateBanner() {
+ * const { status, progress, updateInfo, checkForUpdates, installUpdate } = useElectronUpdater();
+ *
+ * if (status === 'available') {
+ * return Update {updateInfo?.version} available! Downloading...
;
+ * }
+ * if (status === 'downloading') {
+ * return Downloading... {progress.toFixed(0)}%
;
+ * }
+ * if (status === 'ready') {
+ * return ;
+ * }
+ * return ;
+ * }
+ * ```
+ */
+export function useElectronUpdater(): UseElectronUpdaterReturn {
+ const [status, setStatus] = useState('idle');
+ const [progress, setProgress] = useState(0);
+ const [bytesPerSecond, setBytesPerSecond] = useState(0);
+ const [updateInfo, setUpdateInfo] = useState(null);
+ const [errorMessage, setErrorMessage] = useState(null);
+
+ const isElectron = useIsElectron();
+
+ useEffect(() => {
+ if (!window.electronAPI) return;
+
+ const api = window.electronAPI;
+
+ const unsubChecking = api.onUpdateChecking(() => {
+ setStatus('checking');
+ setErrorMessage(null);
+ });
+
+ const unsubAvailable = api.onUpdateAvailable((info) => {
+ setStatus('available');
+ setUpdateInfo(info);
+ });
+
+ const unsubNotAvailable = api.onUpdateNotAvailable((info) => {
+ setStatus('not-available');
+ setUpdateInfo(info);
+ });
+
+ const unsubProgress = api.onDownloadProgress((progressInfo) => {
+ setStatus('downloading');
+ setProgress(progressInfo.percent);
+ setBytesPerSecond(progressInfo.bytesPerSecond);
+ });
+
+ const unsubDownloaded = api.onUpdateDownloaded((info) => {
+ setStatus('ready');
+ setProgress(100);
+ setUpdateInfo(info);
+ });
+
+ const unsubError = api.onUpdateError((error) => {
+ setStatus('error');
+ setErrorMessage(error);
+ });
+
+ // Cleanup all listeners on unmount
+ return () => {
+ unsubChecking();
+ unsubAvailable();
+ unsubNotAvailable();
+ unsubProgress();
+ unsubDownloaded();
+ unsubError();
+ };
+ }, []);
+
+ const checkForUpdates = useCallback(() => {
+ if (!window.electronAPI) return;
+ setStatus('checking');
+ setErrorMessage(null);
+ window.electronAPI.checkForUpdates();
+ }, []);
+
+ const installUpdate = useCallback(() => {
+ if (!window.electronAPI) return;
+ window.electronAPI.installUpdate();
+ }, []);
+
+ return {
+ status,
+ progress,
+ bytesPerSecond,
+ updateInfo,
+ errorMessage,
+ isElectron,
+ checkForUpdates,
+ installUpdate,
+ };
+}
diff --git a/apps/showcase/src/core/hooks/use-is-electron.ts b/apps/showcase/src/core/hooks/use-is-electron.ts
new file mode 100644
index 0000000..7f8bbb9
--- /dev/null
+++ b/apps/showcase/src/core/hooks/use-is-electron.ts
@@ -0,0 +1,7 @@
+/**
+ * A utility hook to determine if the React application is running
+ * inside the Electron desktop wrapper or a standard web browser.
+ */
+export function useIsElectron(): boolean {
+ return typeof window !== 'undefined' && !!window.electronAPI;
+}
diff --git a/apps/showcase/src/core/lib/api-client.ts b/apps/showcase/src/core/lib/api-client.ts
new file mode 100644
index 0000000..0fea5e4
--- /dev/null
+++ b/apps/showcase/src/core/lib/api-client.ts
@@ -0,0 +1,43 @@
+import { createHttpClient } from '@repo/core-api/http-client';
+import { faroAdapter } from '@repo/core-api/observability';
+import { AppStorageKey, secureStorage } from '../storage/local';
+
+/**
+ * Enterprise HTTP client for showcase.
+ */
+export const apiClient = createHttpClient(
+ {
+ baseURL: 'http://localhost:8000/api',
+ timeout: 15000,
+ observability: faroAdapter,
+ },
+ {
+ // ── Auth Interceptor ──────────────────────────────────────────
+ onRequest: async (config) => {
+ config.headers['ex-app-name'] = 'showcase';
+ config.headers['ex-app-version'] = '1.0.0';
+ config.headers['ex-timezone-offset-minutes'] = new Date().getTimezoneOffset();
+ config.headers['ex-timezone-offset-hours'] = Math.floor(new Date().getTimezoneOffset() / 60);
+
+ const language = await secureStorage.getItem(AppStorageKey.LANGUAGE);
+ config.headers['ex-language'] = language ?? 'en';
+
+ // const token = localStorage.getItem('access_token');
+ const token =
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImM1OWY4MTFlLTg3M2MtNDQ3Mi1iZDU4LTIxYzExMTkwMjExNCIsIm5hbWUiOiJzdXBlcmFkbWluIiwidXNlcm5hbWUiOiJzdXBlcmFkbWluIiwicm9sZSI6InN1cGVyYWRtaW4iLCJ1c2VyX3ByaXZpbGVnZV9pZCI6bnVsbCwic291cmNlIjoiUE9TX0FETUlOIiwiaWF0IjoxNzg0Nzc4MDM5LCJleHAiOjE3ODQ4MjEyMzl9.5Wa0Ypz4mM8jYFkdGHxXi1YQzRN3JWcmAl3kCvcLDTo';
+ if (token) config.headers.Authorization = `Bearer ${token}`;
+
+ return config;
+ },
+
+ // ── Error Interceptor ─────────────────────────────────────────
+ onResponseError: async (error) => {
+ if (error.response?.status === 401) {
+ // Clear stale token and redirect to login
+ localStorage.removeItem('access_token');
+ window.location.href = '/auth';
+ }
+ throw error;
+ },
+ },
+);
diff --git a/apps/showcase/src/core/storage/local/index.ts b/apps/showcase/src/core/storage/local/index.ts
new file mode 100644
index 0000000..6f38f55
--- /dev/null
+++ b/apps/showcase/src/core/storage/local/index.ts
@@ -0,0 +1,44 @@
+import { createLocalStorage, createIndexedDB } from '@repo/core-storage';
+
+export const AppStorageKey = {
+ USER_PROFILE: 'user_profile',
+ LANGUAGE: 'app_language',
+ THEME: 'app_theme',
+ ACCESS_TOKEN: 'access_token',
+ REFRESH_TOKEN: 'refresh_token',
+ MOCK_DB_COMPANY_A: 'mock_db_company_a',
+ OFFLINE_DRAFT: 'offline_draft',
+ SYSTEM_SETTINGS: 'system_settings',
+ HISTORY_PAGE: 'history_page',
+ BOOKMARK_PAGE: 'bookmark_page',
+} as const;
+
+export type AppStorageKeyValue = (typeof AppStorageKey)[keyof typeof AppStorageKey];
+
+export const ENCRYPTED_KEYS = new Set([
+ AppStorageKey.USER_PROFILE,
+ AppStorageKey.ACCESS_TOKEN,
+ AppStorageKey.REFRESH_TOKEN,
+]);
+
+export const PLAIN_KEYS = new Set([
+ AppStorageKey.LANGUAGE,
+ AppStorageKey.THEME,
+ AppStorageKey.MOCK_DB_COMPANY_A,
+ AppStorageKey.OFFLINE_DRAFT,
+ AppStorageKey.SYSTEM_SETTINGS,
+ AppStorageKey.HISTORY_PAGE,
+ AppStorageKey.BOOKMARK_PAGE,
+]);
+
+export const secureStorage = createLocalStorage({
+ encryptedKeys: ENCRYPTED_KEYS,
+ plainTextKeys: PLAIN_KEYS,
+});
+
+export const secureIndexedDB = createIndexedDB({
+ dbName: 'e_apps_db',
+ storeName: 'web_store',
+ encryptedKeys: ENCRYPTED_KEYS,
+ plainTextKeys: PLAIN_KEYS,
+});
diff --git a/apps/showcase/src/core/storage/pouch-db/entities/index.ts b/apps/showcase/src/core/storage/pouch-db/entities/index.ts
new file mode 100644
index 0000000..686679f
--- /dev/null
+++ b/apps/showcase/src/core/storage/pouch-db/entities/index.ts
@@ -0,0 +1,3 @@
+export * from './item.pouchdb.entity';
+export * from './pos-configuration.pouchdb.entity';
+export * from './new-item.pouchdb.entity';
diff --git a/apps/showcase/src/core/storage/pouch-db/entities/item.pouchdb.entity.ts b/apps/showcase/src/core/storage/pouch-db/entities/item.pouchdb.entity.ts
new file mode 100644
index 0000000..feefa14
--- /dev/null
+++ b/apps/showcase/src/core/storage/pouch-db/entities/item.pouchdb.entity.ts
@@ -0,0 +1,27 @@
+interface ItemRateEntity {
+ season_period?: string | null;
+ price: string | number;
+}
+
+interface ItemCategoryEntity {
+ _id?: string;
+ name?: string;
+ [key: string]: any;
+}
+
+/**
+ * Represents a sellable product or service.
+ */
+export interface ItemEntity {
+ _id: string;
+ _rev?: string;
+ name: string;
+ base_price: string | number;
+ item_type: string;
+ usage_type?: string;
+ item_category?: ItemCategoryEntity[] | ItemCategoryEntity | string;
+ item_rates?: ItemRateEntity[];
+
+ // Allow for other ERP-specific fields
+ [key: string]: any;
+}
diff --git a/apps/showcase/src/core/storage/pouch-db/entities/new-item.pouchdb.entity.ts b/apps/showcase/src/core/storage/pouch-db/entities/new-item.pouchdb.entity.ts
new file mode 100644
index 0000000..c03b4a4
--- /dev/null
+++ b/apps/showcase/src/core/storage/pouch-db/entities/new-item.pouchdb.entity.ts
@@ -0,0 +1,51 @@
+export interface SeasonType {
+ name: string;
+}
+
+export interface SeasonPeriod {
+ start_date: string;
+ end_date: string;
+ season_type: SeasonType;
+}
+
+export interface ItemRate {
+ id: string;
+ price: string;
+ season_period: SeasonPeriod;
+}
+
+export interface ItemCategory {
+ id: string;
+ name: string;
+}
+
+/**
+ * Detailed interface for an Item entity, based on the provided JSON sample.
+ * This represents the business data payload (`data` property in the envelope).
+ */
+export interface NewItemData {
+ id: string;
+ creator_id: string;
+ creator_name: string;
+ created_at: string;
+ updated_at: string;
+ status: 'active' | 'inactive' | string;
+ item_type: string;
+ hpp: string;
+ sales_margin: string;
+ share_profit: string;
+ total_price: number;
+ base_price: string;
+ play_estimation: number;
+ use_queue: boolean;
+ show_to_booking: boolean;
+ breakdown_bundling: boolean;
+ limit_type: string;
+ limit_value: number;
+ item_category_id: string;
+ item_category: ItemCategory;
+ item_rates: ItemRate[];
+ price: string;
+ qty: number;
+ name: string;
+}
diff --git a/apps/showcase/src/core/storage/pouch-db/entities/pos-configuration.pouchdb.entity.ts b/apps/showcase/src/core/storage/pouch-db/entities/pos-configuration.pouchdb.entity.ts
new file mode 100644
index 0000000..3bed0c3
--- /dev/null
+++ b/apps/showcase/src/core/storage/pouch-db/entities/pos-configuration.pouchdb.entity.ts
@@ -0,0 +1,16 @@
+import { ItemEntity } from './item.pouchdb.entity';
+
+/**
+ * Represents the configuration and assigned data for a specific Point of Sale terminal.
+ */
+export interface POSConfigurationEntity {
+ _id: string;
+ _rev?: string;
+ pos_number: string;
+ pos_name: string;
+ items: ItemEntity[];
+ payment_methods?: any[];
+
+ // Allow for other ERP-specific fields
+ [key: string]: any;
+}
diff --git a/apps/showcase/src/core/storage/pouch-db/index.ts b/apps/showcase/src/core/storage/pouch-db/index.ts
new file mode 100644
index 0000000..8e3ff4b
--- /dev/null
+++ b/apps/showcase/src/core/storage/pouch-db/index.ts
@@ -0,0 +1,45 @@
+/**
+ * Multi-Database PouchDB Configuration for apps/web.
+ *
+ * This module demonstrates the IoC pattern: the consuming app decides
+ * which databases to create and where they sync to. The core engine
+ * (`PouchDBManager` and `PouchEnvelopeManager`) has zero knowledge of business domains.
+ */
+import { PouchDBManager, PouchEnvelopeDBManager } from '@repo/core-storage';
+import { ItemEntity, POSConfigurationEntity } from './entities';
+
+// ─── Manager Singletons ──────────────────────────────────────────
+
+export const dbManager = new PouchDBManager();
+export const envelopeDbManager = new PouchEnvelopeDBManager();
+
+// ─── Helper: Build Secure Remote URL ────────────────────────────
+
+function buildRemoteUrl(dbName: string): string | undefined {
+ // In Showcase, we default to offline-only mode since there's no ENV
+ console.info(`[DB Config] Showcase running "${dbName}" in offline-only mode.`);
+ return undefined;
+}
+
+// ─── Register Application Databases ─────────────────────────────
+
+/** POS Configuration database — stores device settings, theme, etc. */
+export const posConfigDB = dbManager.register({
+ localName: 'pos_configuration',
+ remoteUrl: buildRemoteUrl('pos_configuration'),
+});
+
+/** Items database — products available for sale in POS. */
+export const itemDB = dbManager.register({
+ localName: 'item',
+ remoteUrl: buildRemoteUrl('item'),
+});
+
+/** New Items database using the Envelope Service pattern. */
+export const newItemDB = envelopeDbManager.register(
+ {
+ localName: 'master_data',
+ remoteUrl: buildRemoteUrl('master_data'),
+ },
+ 'item',
+);
diff --git a/apps/showcase/src/core/stores/theme.store.ts b/apps/showcase/src/core/stores/theme.store.ts
new file mode 100644
index 0000000..d780321
--- /dev/null
+++ b/apps/showcase/src/core/stores/theme.store.ts
@@ -0,0 +1,30 @@
+import { create } from 'zustand';
+import { persist, createJSONStorage } from 'zustand/middleware';
+import type { ColorSchemeType } from '@repo/ui/provider';
+import { AppStorageKey } from '../storage/local';
+
+interface ThemeState {
+ colorScheme: ColorSchemeType;
+ setColorScheme: (scheme: ColorSchemeType) => void;
+}
+
+/**
+ * Global theme store with localStorage persistence.
+ *
+ * Uses the Zustand `persist` middleware so the chosen color scheme
+ * survives page refreshes. The storage key (`app_theme`) is
+ * intentionally kept in sync with `AppStorageKey.THEME` — both
+ * write to the same localStorage entry.
+ */
+export const useThemeStore = create()(
+ persist(
+ (set) => ({
+ colorScheme: 'light',
+ setColorScheme: (scheme) => set({ colorScheme: scheme }),
+ }),
+ {
+ name: AppStorageKey.THEME, // matches AppStorageKey.THEME
+ storage: createJSONStorage(() => localStorage),
+ },
+ ),
+);
diff --git a/apps/showcase/src/layouts/ShowcaseLayout.tsx b/apps/showcase/src/layouts/ShowcaseLayout.tsx
new file mode 100644
index 0000000..6a96b27
--- /dev/null
+++ b/apps/showcase/src/layouts/ShowcaseLayout.tsx
@@ -0,0 +1,131 @@
+import { AppShell, NavLink, Group, Title, Box, Select, Text, Paper } from '@repo/ui/components';
+import { Outlet, useLocation, useNavigate } from 'react-router-dom';
+import { useTranslation } from '@repo/core-i18n';
+import { Layout, FileText, Database, ShieldCheck, Lock, Activity, Printer, Globe, Layers, Table2 } from 'lucide-react';
+import { DensityType } from '@repo/ui/provider';
+import { useThemeStore } from '../core/stores/theme.store';
+import { ColorSchemeType } from '@repo/ui/provider';
+
+interface ShowcaseLayoutProps {
+ density: DensityType;
+ setDensity: (val: DensityType) => void;
+}
+
+export default function ShowcaseLayout({ density, setDensity }: ShowcaseLayoutProps) {
+ const navigate = useNavigate();
+ const location = useLocation();
+ const { i18n } = useTranslation();
+ const { colorScheme, setColorScheme } = useThemeStore();
+
+ const navItems = [
+ { label: 'UI Components', path: '/ui-components', icon: Layout },
+ { label: 'Layout Engine', path: '/shell-demo', icon: Layout },
+ { label: 'Form Engine', path: '/forms', icon: FileText },
+ { label: 'Offline Storage', path: '/storage', icon: Database },
+ { label: 'RBAC Engine', path: '/rbac', icon: ShieldCheck },
+ { label: 'Auth & Security', path: '/auth', icon: Lock },
+ { label: 'Events', path: '/events', icon: Activity },
+ { label: 'Hardware', path: '/hardware', icon: Printer },
+ { label: 'Action Tools', path: '/action-tools', icon: Layers },
+ { label: 'AG Grid', path: '/ag-grid', icon: Table2 },
+ ];
+
+ const getSubtitle = () => {
+ const currentPath = location.pathname.replace('/', '');
+
+ switch (currentPath) {
+ case 'rbac': return 'Role-Based Access Control and Permissions';
+ case 'storage': return 'Offline-First PouchDB Synchronization';
+ case 'auth': return 'Authentication & Security Layers';
+ case 'ui-components': return 'Theme, Typography, Forms & Data Grids';
+ case 'forms': return 'Enterprise Form Engine & Zod Validation';
+ case 'events': return 'Global Event Bus Synchronization';
+ case 'hardware': return 'Hardware Integration & Printers';
+ case 'layout-engine': return 'Core Layout Engine & Variants';
+ case 'action-tools': return 'Showcase for PageActions and RowActions components';
+ case 'ag-grid': return 'Enterprise Data Grid with Mantine Theme Integration';
+ default: return 'Architecture Showcase';
+ }
+ };
+
+ return (
+
+
+
+ Eigen ERP
+ Architecture Showcase
+
+
+ {navItems.map((item) => (
+ }
+ onClick={() => navigate(item.path)}
+ variant="filled"
+ style={{ borderRadius: 'var(--mantine-radius-md)', marginBottom: 4 }}
+ />
+ ))}
+
+
+
+
+
+
+
+
+
+
+ Architecture Showcase
+ {getSubtitle()}
+
+ }
+ data={[
+ { value: 'en', label: 'English' },
+ { value: 'id', label: 'Bahasa Indonesia' },
+ ]}
+ value={i18n.resolvedLanguage || i18n.language}
+ onChange={(val) => val && i18n.changeLanguage(val)}
+ />
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/showcase/src/main.css b/apps/showcase/src/main.css
new file mode 100644
index 0000000..1a6a0b5
--- /dev/null
+++ b/apps/showcase/src/main.css
@@ -0,0 +1,3 @@
+@import 'tailwindcss';
+@import '@repo/ui/theme.css';
+@source "../../../packages/ui/src";
diff --git a/apps/showcase/src/main.tsx b/apps/showcase/src/main.tsx
new file mode 100644
index 0000000..8cf0c7b
--- /dev/null
+++ b/apps/showcase/src/main.tsx
@@ -0,0 +1,17 @@
+import './main.css';
+import { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
+import { setupI18n } from '@repo/core-i18n';
+import App from './App';
+
+async function bootstrap() {
+ await setupI18n({}, 'en'); // Minimal i18n setup
+
+ createRoot(document.getElementById('app')!).render(
+
+
+ ,
+ );
+}
+
+bootstrap();
diff --git a/apps/web/src/apps/showcase/components/ActionToolsShowcase.tsx b/apps/showcase/src/pages/action-tools/index.tsx
similarity index 100%
rename from apps/web/src/apps/showcase/components/ActionToolsShowcase.tsx
rename to apps/showcase/src/pages/action-tools/index.tsx
diff --git a/apps/web/src/apps/showcase/components/AgGridShowcase.tsx b/apps/showcase/src/pages/ag-grid/index.tsx
similarity index 100%
rename from apps/web/src/apps/showcase/components/AgGridShowcase.tsx
rename to apps/showcase/src/pages/ag-grid/index.tsx
diff --git a/apps/showcase/src/pages/auth/index.tsx b/apps/showcase/src/pages/auth/index.tsx
new file mode 100644
index 0000000..0cb1bf1
--- /dev/null
+++ b/apps/showcase/src/pages/auth/index.tsx
@@ -0,0 +1,14 @@
+import { Stack, Container, Card, Title, Text } from '@repo/ui/components';
+
+export default function AuthPage() {
+ return (
+
+
+
+ Auth & Security
+ Auth Demo Component Coming Soon...
+
+
+
+ );
+}
diff --git a/apps/web/src/apps/showcase/events-demo/auth-sync/profile-settings.ui.tsx b/apps/showcase/src/pages/events/components/events-demo/auth-sync/profile-settings.ui.tsx
similarity index 96%
rename from apps/web/src/apps/showcase/events-demo/auth-sync/profile-settings.ui.tsx
rename to apps/showcase/src/pages/events/components/events-demo/auth-sync/profile-settings.ui.tsx
index 5e774c4..a72a1d0 100644
--- a/apps/web/src/apps/showcase/events-demo/auth-sync/profile-settings.ui.tsx
+++ b/apps/showcase/src/pages/events/components/events-demo/auth-sync/profile-settings.ui.tsx
@@ -1,7 +1,7 @@
import { useState } from 'react';
import { usePublishEvent } from '@repo/core-events';
import { Button, Group, Stack, TextInput, Badge } from '@repo/ui/components';
-import { AUTH_EVENTS } from '../../../../core/constants/events';
+import { AUTH_EVENTS } from '../../../../../core/constants/events';
// ─── ProfileSettingsUI ──────────────────────────────────────────
diff --git a/apps/web/src/apps/showcase/events-demo/auth-sync/storage-sync.listener.tsx b/apps/showcase/src/pages/events/components/events-demo/auth-sync/storage-sync.listener.tsx
similarity index 90%
rename from apps/web/src/apps/showcase/events-demo/auth-sync/storage-sync.listener.tsx
rename to apps/showcase/src/pages/events/components/events-demo/auth-sync/storage-sync.listener.tsx
index 88f61f5..e138a5a 100644
--- a/apps/web/src/apps/showcase/events-demo/auth-sync/storage-sync.listener.tsx
+++ b/apps/showcase/src/pages/events/components/events-demo/auth-sync/storage-sync.listener.tsx
@@ -1,7 +1,7 @@
import { useAppEvent } from '@repo/core-events';
-import { AUTH_EVENTS } from '../../../../core/constants/events';
+import { AUTH_EVENTS } from '../../../../../core/constants/events';
import type { ProfileUpdatedPayload } from '@repo/core-events';
-import { secureIndexedDB, AppStorageKey } from '../../../../core/storage/local';
+import { secureIndexedDB, AppStorageKey } from '../../../../../core/storage/local';
// ─── Props ──────────────────────────────────────────────────────
diff --git a/apps/web/src/apps/showcase/events-demo/index.tsx b/apps/showcase/src/pages/events/components/events-demo/index.tsx
similarity index 98%
rename from apps/web/src/apps/showcase/events-demo/index.tsx
rename to apps/showcase/src/pages/events/components/events-demo/index.tsx
index 86b7e10..0f4947b 100644
--- a/apps/web/src/apps/showcase/events-demo/index.tsx
+++ b/apps/showcase/src/pages/events/components/events-demo/index.tsx
@@ -14,7 +14,7 @@ import { PrinterListener } from './printer/printer.listener';
import { LiveStockGrid } from './stock-grid/live-stock-grid.ui';
import { ProfileSettingsUI } from './auth-sync/profile-settings.ui';
import { StorageSyncListener } from './auth-sync/storage-sync.listener';
-import { DEVICE_EVENTS, AUTH_EVENTS } from '../../../core/constants/events';
+import { DEVICE_EVENTS, AUTH_EVENTS } from '../../../../core/constants/events';
// ─── Events Demo Page ───────────────────────────────────────────
diff --git a/apps/web/src/apps/showcase/events-demo/printer/cashier.ui.tsx b/apps/showcase/src/pages/events/components/events-demo/printer/cashier.ui.tsx
similarity index 97%
rename from apps/web/src/apps/showcase/events-demo/printer/cashier.ui.tsx
rename to apps/showcase/src/pages/events/components/events-demo/printer/cashier.ui.tsx
index 4e5bef6..9b66188 100644
--- a/apps/web/src/apps/showcase/events-demo/printer/cashier.ui.tsx
+++ b/apps/showcase/src/pages/events/components/events-demo/printer/cashier.ui.tsx
@@ -10,7 +10,7 @@ import {
Table,
Badge,
} from '@repo/ui/components';
-import { DEVICE_EVENTS } from '../../../../core/constants/events';
+import { DEVICE_EVENTS } from '../../../../../core/constants/events';
// ─── Mock Receipt Data ──────────────────────────────────────────
diff --git a/apps/web/src/apps/showcase/events-demo/printer/printer.listener.tsx b/apps/showcase/src/pages/events/components/events-demo/printer/printer.listener.tsx
similarity index 97%
rename from apps/web/src/apps/showcase/events-demo/printer/printer.listener.tsx
rename to apps/showcase/src/pages/events/components/events-demo/printer/printer.listener.tsx
index bddc46c..eb52f3e 100644
--- a/apps/web/src/apps/showcase/events-demo/printer/printer.listener.tsx
+++ b/apps/showcase/src/pages/events/components/events-demo/printer/printer.listener.tsx
@@ -1,5 +1,5 @@
import { useAppEvent } from '@repo/core-events';
-import { DEVICE_EVENTS } from '../../../../core/constants/events';
+import { DEVICE_EVENTS } from '../../../../../core/constants/events';
import type { PrintReceiptPayload } from '@repo/core-events';
// ─── Props ──────────────────────────────────────────────────────
diff --git a/apps/web/src/apps/showcase/events-demo/stock-grid/live-stock-grid.ui.tsx b/apps/showcase/src/pages/events/components/events-demo/stock-grid/live-stock-grid.ui.tsx
similarity index 100%
rename from apps/web/src/apps/showcase/events-demo/stock-grid/live-stock-grid.ui.tsx
rename to apps/showcase/src/pages/events/components/events-demo/stock-grid/live-stock-grid.ui.tsx
diff --git a/apps/web/src/apps/showcase/events-demo/stock-grid/mock-websocket.service.ts b/apps/showcase/src/pages/events/components/events-demo/stock-grid/mock-websocket.service.ts
similarity index 97%
rename from apps/web/src/apps/showcase/events-demo/stock-grid/mock-websocket.service.ts
rename to apps/showcase/src/pages/events/components/events-demo/stock-grid/mock-websocket.service.ts
index b83c961..d841e80 100644
--- a/apps/web/src/apps/showcase/events-demo/stock-grid/mock-websocket.service.ts
+++ b/apps/showcase/src/pages/events/components/events-demo/stock-grid/mock-websocket.service.ts
@@ -1,5 +1,5 @@
import { publish } from '@repo/core-events';
-import { WS_EVENTS } from '../../../../core/constants/events';
+import { WS_EVENTS } from '../../../../../core/constants/events';
// ─── Stock Tickers ──────────────────────────────────────────────
diff --git a/apps/web/src/apps/showcase/events-demo/stock-grid/stock-row.ui.tsx b/apps/showcase/src/pages/events/components/events-demo/stock-grid/stock-row.ui.tsx
similarity index 97%
rename from apps/web/src/apps/showcase/events-demo/stock-grid/stock-row.ui.tsx
rename to apps/showcase/src/pages/events/components/events-demo/stock-grid/stock-row.ui.tsx
index f5d489d..8d38047 100644
--- a/apps/web/src/apps/showcase/events-demo/stock-grid/stock-row.ui.tsx
+++ b/apps/showcase/src/pages/events/components/events-demo/stock-grid/stock-row.ui.tsx
@@ -1,6 +1,6 @@
import { memo, useState, useRef } from 'react';
import { useAppEvent } from '@repo/core-events';
-import { WS_EVENTS } from '../../../../core/constants/events';
+import { WS_EVENTS } from '../../../../../core/constants/events';
// ─── Props ──────────────────────────────────────────────────────
diff --git a/apps/web/src/apps/showcase/example/example.page.tsx b/apps/showcase/src/pages/events/components/example/example.page.tsx
similarity index 100%
rename from apps/web/src/apps/showcase/example/example.page.tsx
rename to apps/showcase/src/pages/events/components/example/example.page.tsx
diff --git a/apps/web/src/apps/showcase/example/features/booking/data/advanced-booking.data-services.ts b/apps/showcase/src/pages/events/components/example/features/booking/data/advanced-booking.data-services.ts
similarity index 98%
rename from apps/web/src/apps/showcase/example/features/booking/data/advanced-booking.data-services.ts
rename to apps/showcase/src/pages/events/components/example/features/booking/data/advanced-booking.data-services.ts
index 929c940..b5c24d3 100644
--- a/apps/web/src/apps/showcase/example/features/booking/data/advanced-booking.data-services.ts
+++ b/apps/showcase/src/pages/events/components/example/features/booking/data/advanced-booking.data-services.ts
@@ -1,6 +1,6 @@
import { BaseRemoteDataServices } from '@repo/core-api/data-services';
import type { ApiResponse } from '@repo/core-api/http-client';
-import { apiClient } from '../../../../../../core/lib/api-client';
+import { apiClient } from '../../../../../../../core/lib/api-client';
import type { BookingEntity } from './booking.data-services';
import type { BookingDTO } from './booking.transformer';
import {
diff --git a/apps/web/src/apps/showcase/example/features/booking/data/advanced-booking.transformer.ts b/apps/showcase/src/pages/events/components/example/features/booking/data/advanced-booking.transformer.ts
similarity index 100%
rename from apps/web/src/apps/showcase/example/features/booking/data/advanced-booking.transformer.ts
rename to apps/showcase/src/pages/events/components/example/features/booking/data/advanced-booking.transformer.ts
diff --git a/apps/web/src/apps/showcase/example/features/booking/data/booking.data-services.ts b/apps/showcase/src/pages/events/components/example/features/booking/data/booking.data-services.ts
similarity index 96%
rename from apps/web/src/apps/showcase/example/features/booking/data/booking.data-services.ts
rename to apps/showcase/src/pages/events/components/example/features/booking/data/booking.data-services.ts
index b65ce28..2cd74ce 100644
--- a/apps/web/src/apps/showcase/example/features/booking/data/booking.data-services.ts
+++ b/apps/showcase/src/pages/events/components/example/features/booking/data/booking.data-services.ts
@@ -1,6 +1,6 @@
import { CommonRemoteDataServices } from '@repo/core-api/data-services';
import type { BaseEntity } from '@repo/core-api/data-services';
-import { apiClient } from '../../../../../../core/lib/api-client';
+import { apiClient } from '../../../../../../../core/lib/api-client';
import { BookingTransformer } from './booking.transformer';
import type { BookingDTO } from './booking.transformer';
diff --git a/apps/web/src/apps/showcase/example/features/booking/data/booking.transformer.ts b/apps/showcase/src/pages/events/components/example/features/booking/data/booking.transformer.ts
similarity index 100%
rename from apps/web/src/apps/showcase/example/features/booking/data/booking.transformer.ts
rename to apps/showcase/src/pages/events/components/example/features/booking/data/booking.transformer.ts
diff --git a/apps/web/src/apps/showcase/example/features/booking/presentation/BookingSample.tsx b/apps/showcase/src/pages/events/components/example/features/booking/presentation/BookingSample.tsx
similarity index 100%
rename from apps/web/src/apps/showcase/example/features/booking/presentation/BookingSample.tsx
rename to apps/showcase/src/pages/events/components/example/features/booking/presentation/BookingSample.tsx
diff --git a/apps/web/src/apps/showcase/example/features/i18n/languages/en/booking.json b/apps/showcase/src/pages/events/components/example/features/i18n/languages/en/booking.json
similarity index 100%
rename from apps/web/src/apps/showcase/example/features/i18n/languages/en/booking.json
rename to apps/showcase/src/pages/events/components/example/features/i18n/languages/en/booking.json
diff --git a/apps/web/src/apps/showcase/example/features/i18n/languages/id/booking.json b/apps/showcase/src/pages/events/components/example/features/i18n/languages/id/booking.json
similarity index 100%
rename from apps/web/src/apps/showcase/example/features/i18n/languages/id/booking.json
rename to apps/showcase/src/pages/events/components/example/features/i18n/languages/id/booking.json
diff --git a/apps/web/src/apps/showcase/example/features/i18n/presentation/I18nSample.tsx b/apps/showcase/src/pages/events/components/example/features/i18n/presentation/I18nSample.tsx
similarity index 99%
rename from apps/web/src/apps/showcase/example/features/i18n/presentation/I18nSample.tsx
rename to apps/showcase/src/pages/events/components/example/features/i18n/presentation/I18nSample.tsx
index e42e37a..8a04f9b 100644
--- a/apps/web/src/apps/showcase/example/features/i18n/presentation/I18nSample.tsx
+++ b/apps/showcase/src/pages/events/components/example/features/i18n/presentation/I18nSample.tsx
@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback } from 'react';
import { useTranslation, changeLanguage, applyTenantOverrides, i18n } from '@repo/core-i18n';
-import { secureIndexedDB, AppStorageKey } from '../../../../../../core/storage/local';
+import { secureIndexedDB, AppStorageKey } from '../../../../../../../core/storage/local';
// Decentralized languages imports
import bookingId from '../languages/id/booking.json';
diff --git a/apps/web/src/apps/showcase/example/features/storage/presentation/StorageSample.tsx b/apps/showcase/src/pages/events/components/example/features/storage/presentation/StorageSample.tsx
similarity index 99%
rename from apps/web/src/apps/showcase/example/features/storage/presentation/StorageSample.tsx
rename to apps/showcase/src/pages/events/components/example/features/storage/presentation/StorageSample.tsx
index 58e9d7b..2b87b2b 100644
--- a/apps/web/src/apps/showcase/example/features/storage/presentation/StorageSample.tsx
+++ b/apps/showcase/src/pages/events/components/example/features/storage/presentation/StorageSample.tsx
@@ -1,5 +1,5 @@
import { useState, useCallback } from 'react';
-import { secureStorage, secureIndexedDB, AppStorageKey } from '../../../../../../core/storage/local';
+import { secureStorage, secureIndexedDB, AppStorageKey } from '../../../../../../../core/storage/local';
// ─── Demo Data ──────────────────────────────────────────────────
diff --git a/apps/showcase/src/pages/events/index.tsx b/apps/showcase/src/pages/events/index.tsx
new file mode 100644
index 0000000..e5a4893
--- /dev/null
+++ b/apps/showcase/src/pages/events/index.tsx
@@ -0,0 +1,20 @@
+import { Stack, Container, Card, Title, Text } from '@repo/ui/components';
+import ExamplePage from './components/example/example.page';
+import EventsDemoPage from './components/events-demo';
+
+export default function EventsPage() {
+ return (
+
+
+
+ Nested Showcase Example
+ This is an example of a nested showcase component.
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/apps/showcase/example/features/form-demo/components/all-fields-demo.tsx b/apps/showcase/src/pages/forms/components/form-demo/components/all-fields-demo.tsx
similarity index 100%
rename from apps/web/src/apps/showcase/example/features/form-demo/components/all-fields-demo.tsx
rename to apps/showcase/src/pages/forms/components/form-demo/components/all-fields-demo.tsx
diff --git a/apps/web/src/apps/showcase/example/features/form-demo/components/reactive-watch-demo.tsx b/apps/showcase/src/pages/forms/components/form-demo/components/reactive-watch-demo.tsx
similarity index 100%
rename from apps/web/src/apps/showcase/example/features/form-demo/components/reactive-watch-demo.tsx
rename to apps/showcase/src/pages/forms/components/form-demo/components/reactive-watch-demo.tsx
diff --git a/apps/web/src/apps/showcase/example/features/form-demo/components/validation-bank-demo.tsx b/apps/showcase/src/pages/forms/components/form-demo/components/validation-bank-demo.tsx
similarity index 100%
rename from apps/web/src/apps/showcase/example/features/form-demo/components/validation-bank-demo.tsx
rename to apps/showcase/src/pages/forms/components/form-demo/components/validation-bank-demo.tsx
diff --git a/apps/web/src/apps/showcase/example/features/form-demo/form-demo-view.tsx b/apps/showcase/src/pages/forms/components/form-demo/form-demo-view.tsx
similarity index 100%
rename from apps/web/src/apps/showcase/example/features/form-demo/form-demo-view.tsx
rename to apps/showcase/src/pages/forms/components/form-demo/form-demo-view.tsx
diff --git a/apps/web/src/apps/showcase/example/features/form-demo/i18n/en.json b/apps/showcase/src/pages/forms/components/form-demo/i18n/en.json
similarity index 100%
rename from apps/web/src/apps/showcase/example/features/form-demo/i18n/en.json
rename to apps/showcase/src/pages/forms/components/form-demo/i18n/en.json
diff --git a/apps/web/src/apps/showcase/example/features/form-demo/i18n/id.json b/apps/showcase/src/pages/forms/components/form-demo/i18n/id.json
similarity index 100%
rename from apps/web/src/apps/showcase/example/features/form-demo/i18n/id.json
rename to apps/showcase/src/pages/forms/components/form-demo/i18n/id.json
diff --git a/apps/web/src/apps/showcase/example/features/form-demo/i18n/useFormDemoTranslation.ts b/apps/showcase/src/pages/forms/components/form-demo/i18n/useFormDemoTranslation.ts
similarity index 100%
rename from apps/web/src/apps/showcase/example/features/form-demo/i18n/useFormDemoTranslation.ts
rename to apps/showcase/src/pages/forms/components/form-demo/i18n/useFormDemoTranslation.ts
diff --git a/apps/web/src/apps/showcase/example/features/form-demo/index.ts b/apps/showcase/src/pages/forms/components/form-demo/index.ts
similarity index 100%
rename from apps/web/src/apps/showcase/example/features/form-demo/index.ts
rename to apps/showcase/src/pages/forms/components/form-demo/index.ts
diff --git a/apps/showcase/src/pages/forms/index.tsx b/apps/showcase/src/pages/forms/index.tsx
new file mode 100644
index 0000000..461dcef
--- /dev/null
+++ b/apps/showcase/src/pages/forms/index.tsx
@@ -0,0 +1,12 @@
+import { Stack, Container } from '@repo/ui/components';
+import FormDemoView from './components/form-demo';
+
+export default function FormsPage() {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/apps/web/src/apps/showcase/printer-list.tsx b/apps/showcase/src/pages/hardware/components/printer-list.tsx
similarity index 86%
rename from apps/web/src/apps/showcase/printer-list.tsx
rename to apps/showcase/src/pages/hardware/components/printer-list.tsx
index a03df5a..2995161 100644
--- a/apps/web/src/apps/showcase/printer-list.tsx
+++ b/apps/showcase/src/pages/hardware/components/printer-list.tsx
@@ -1,7 +1,7 @@
// 1. Import hooks yang baru saja dibuat Opus
import { Button } from '@repo/ui/components';
-import { useElectronPrinter } from '../../core/hooks/use-electron-printer';
-import { useElectronUpdater } from '../../core/hooks/use-electron-updater';
+import { useElectronPrinter } from '../../../core/hooks/use-electron-printer';
+import { useElectronUpdater } from '../../../core/hooks/use-electron-updater';
export default function App() {
// 2. Panggil hooks-nya
diff --git a/apps/showcase/src/pages/hardware/index.tsx b/apps/showcase/src/pages/hardware/index.tsx
new file mode 100644
index 0000000..0747a63
--- /dev/null
+++ b/apps/showcase/src/pages/hardware/index.tsx
@@ -0,0 +1,14 @@
+import { Stack, Container, Card } from '@repo/ui/components';
+import PrinterList from './components/printer-list';
+
+export default function HardwarePage() {
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/showcase/src/pages/rbac/index.tsx b/apps/showcase/src/pages/rbac/index.tsx
new file mode 100644
index 0000000..bcd45dd
--- /dev/null
+++ b/apps/showcase/src/pages/rbac/index.tsx
@@ -0,0 +1,14 @@
+import { Stack, Container, Card, Title, Text } from '@repo/ui/components';
+
+export default function RbacPage() {
+ return (
+
+
+
+ RBAC Engine
+ RBAC Demo Component Coming Soon...
+
+
+
+ );
+}
diff --git a/apps/web/src/apps/showcase/shell-demo/index.tsx b/apps/showcase/src/pages/shell-demo/index.tsx
similarity index 99%
rename from apps/web/src/apps/showcase/shell-demo/index.tsx
rename to apps/showcase/src/pages/shell-demo/index.tsx
index b744f99..f0fbacc 100644
--- a/apps/web/src/apps/showcase/shell-demo/index.tsx
+++ b/apps/showcase/src/pages/shell-demo/index.tsx
@@ -246,7 +246,7 @@ export default function ShellDemo() {
Layout Engine Interactive Demo
- } variant="default">
+ } variant="default">
Back to Showcase
diff --git a/apps/web/src/apps/showcase/pouch-sample.tsx b/apps/showcase/src/pages/storage/components/pouch-sample.tsx
similarity index 98%
rename from apps/web/src/apps/showcase/pouch-sample.tsx
rename to apps/showcase/src/pages/storage/components/pouch-sample.tsx
index 07b4e1c..e68ef4b 100644
--- a/apps/web/src/apps/showcase/pouch-sample.tsx
+++ b/apps/showcase/src/pages/storage/components/pouch-sample.tsx
@@ -1,8 +1,8 @@
import { useEffect, useState, useCallback } from 'react';
import { Button, Card, Group, Stack, Title, Text, Table, Badge } from '@repo/ui/components';
-import { itemDB, posConfigDB, newItemDB } from '../../core/storage/pouch-db';
-import type { ItemEntity, POSConfigurationEntity } from '../../core/storage/pouch-db/entities';
+import { itemDB, posConfigDB, newItemDB } from '../../../core/storage/pouch-db';
+import type { ItemEntity, POSConfigurationEntity } from '../../../core/storage/pouch-db/entities';
export default function PouchSample() {
const [configs, setConfigs] = useState([]);
diff --git a/apps/showcase/src/pages/storage/index.tsx b/apps/showcase/src/pages/storage/index.tsx
new file mode 100644
index 0000000..5c14eb9
--- /dev/null
+++ b/apps/showcase/src/pages/storage/index.tsx
@@ -0,0 +1,12 @@
+import { Stack, Container } from '@repo/ui/components';
+import PouchSample from './components/pouch-sample';
+
+export default function StoragePage() {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/apps/showcase/src/pages/ui-components/index.tsx b/apps/showcase/src/pages/ui-components/index.tsx
new file mode 100644
index 0000000..80aa45c
--- /dev/null
+++ b/apps/showcase/src/pages/ui-components/index.tsx
@@ -0,0 +1,124 @@
+import { Stack, Card, Title, Group, Select, Text, Badge, Divider, Button, TextInput, NumberInput, PasswordInput, Textarea, Checkbox, Switch, Table, StatusBadge, STATUS_DATA, Container } from '@repo/ui/components';
+import { useThemeStore } from '../../core/stores/theme.store';
+import { ColorSchemeType } from '@repo/ui/provider';
+
+const tableData = [
+ { id: 'ORD-001', customer: 'John Doe', status: 'Shipped', total: '$120.00' },
+ { id: 'ORD-002', customer: 'Jane Smith', status: 'Pending', total: '$85.50' },
+ { id: 'ORD-003', customer: 'Acme Corp', status: 'Delivered', total: '$1,250.00' },
+];
+
+export default function UiComponentsPage() {
+ const { colorScheme, setColorScheme } = useThemeStore();
+
+ return (
+
+
+ {/* Control Panel */}
+
+ Theme Controls
+
+ setColorScheme((val as ColorSchemeType) || 'light')}
+ data={[
+ { value: 'light', label: 'Light' },
+ { value: 'dark', label: 'Dark' },
+ ]}
+ />
+
+
+
+ {/* Typography & Buttons */}
+
+
+
+
Typography & Badges
+ This is dimmed small text indicating a subtitle.
+
+ Brand Badge
+ Success Status
+ Error State
+
+
+
+
+
Enterprise Status Badges
+ Pre-configured status badges for transaction and master data.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Buttons
+
+
+
+
+
+
+
+
+
+
+ {/* Forms */}
+
+ Form Elements
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Data Grid */}
+
+ Data Grid
+
+
+
+ Order ID
+ Customer
+ Status
+ Total
+
+
+
+ {tableData.map((row) => (
+
+ {row.id}
+ {row.customer}
+
+
+ {row.status}
+
+
+ {row.total}
+
+ ))}
+
+
+
+
+
+ );
+}
diff --git a/apps/showcase/src/types/electron.d.ts b/apps/showcase/src/types/electron.d.ts
new file mode 100644
index 0000000..c26dfa8
--- /dev/null
+++ b/apps/showcase/src/types/electron.d.ts
@@ -0,0 +1,98 @@
+/**
+ * Type declarations for the Electron preload API.
+ *
+ * When running inside Electron, `window.electronAPI` is defined.
+ * When running in a regular browser, it is `undefined`.
+ *
+ * Usage:
+ * if (window.electronAPI) {
+ * const printers = await window.electronAPI.getPrinters();
+ * }
+ */
+
+// ─── Printer types ──────────────────────────────────────────────
+
+interface ElectronPrinterInfo {
+ name: string;
+ displayName: string;
+ description: string;
+ status: number;
+ isDefault: boolean;
+ options?: Record;
+}
+
+interface ElectronPrintOptions {
+ silent?: boolean;
+ printBackground?: boolean;
+ deviceName?: string;
+ color?: boolean;
+ margins?: {
+ marginType?: 'default' | 'none' | 'printableArea' | 'custom';
+ top?: number;
+ bottom?: number;
+ left?: number;
+ right?: number;
+ };
+ landscape?: boolean;
+ scaleFactor?: number;
+ pagesPerSheet?: number;
+ collate?: boolean;
+ copies?: number;
+ pageRanges?: Array<{ from: number; to: number }>;
+ duplexMode?: 'simplex' | 'shortEdge' | 'longEdge';
+ header?: string;
+ footer?: string;
+}
+
+interface ElectronPrintResult {
+ success: boolean;
+ failureReason?: string;
+}
+
+// ─── Auto-Update types ──────────────────────────────────────────
+
+interface ElectronUpdateInfo {
+ version: string;
+ releaseDate: string;
+ releaseName?: string | null;
+ releaseNotes?: string | null;
+}
+
+interface ElectronProgressInfo {
+ total: number;
+ delta: number;
+ transferred: number;
+ percent: number;
+ bytesPerSecond: number;
+}
+
+// ─── ElectronAPI interface ──────────────────────────────────────
+
+interface ElectronAPI {
+ // Printing
+ getPrinters: () => Promise;
+ print: (options?: ElectronPrintOptions) => Promise;
+
+ // Auto-Update: Commands
+ checkForUpdates: () => void;
+ installUpdate: () => void;
+
+ // Auto-Update: Event Subscriptions
+ // Each returns an unsubscribe function.
+ onUpdateChecking: (callback: () => void) => () => void;
+ onUpdateAvailable: (callback: (info: ElectronUpdateInfo) => void) => () => void;
+ onUpdateNotAvailable: (callback: (info: ElectronUpdateInfo) => void) => () => void;
+ onDownloadProgress: (callback: (progress: ElectronProgressInfo) => void) => () => void;
+ onUpdateDownloaded: (callback: (info: ElectronUpdateInfo) => void) => () => void;
+ onUpdateError: (callback: (error: string) => void) => () => void;
+}
+
+// ─── Augment the global Window interface ────────────────────────
+
+interface Window {
+ /**
+ * Available only when running inside Electron.
+ * Always check `if (window.electronAPI)` before use.
+ */
+ electronAPI?: ElectronAPI;
+}
diff --git a/apps/showcase/src/types/events.d.ts b/apps/showcase/src/types/events.d.ts
new file mode 100644
index 0000000..b94bffb
--- /dev/null
+++ b/apps/showcase/src/types/events.d.ts
@@ -0,0 +1,80 @@
+/**
+ * App-level event registry for `apps/web`.
+ *
+ * This file uses TypeScript Declaration Merging (Module Augmentation)
+ * to extend the open `AppEventRegistry` interface exported by
+ * `@repo/core-events`. This is the IoC pattern in action:
+ *
+ * - `@repo/core-events` provides the bus, hooks, and helpers (the tool).
+ * - `apps/web` defines which events exist and their payload shapes (the contract).
+ *
+ * The core package has zero knowledge of these events. If `apps/web`
+ * is removed from the monorepo, the core package remains unchanged.
+ *
+ * **Adding new events**: Simply add new entries to `AppEventRegistry`
+ * below. TypeScript will automatically provide autocomplete and
+ * type safety across every `publish()` / `useAppEvent()` call in
+ * the web app.
+ *
+ * @see packages/core-events/src/events.registry.ts
+ */
+
+// This import turns this file from an ambient declaration into a
+// module augmentation. Without it, `declare module` would REPLACE
+// the module signature instead of merging into it.
+import type {} from '@repo/core-events';
+
+declare module '@repo/core-events' {
+
+ // ─── Payload Types ──────────────────────────────────────────
+
+ interface ReceiptItem {
+ name: string;
+ qty: number;
+ price: number;
+ }
+
+ interface PrintReceiptPayload {
+ receiptId: string;
+ items: ReceiptItem[];
+ total: number;
+ cashierName: string;
+ timestamp: number;
+ }
+
+ interface StockUpdatePayload {
+ id: string;
+ price: number;
+ change: number;
+ volume: number;
+ }
+
+ interface ProfileUpdatedPayload {
+ id: string;
+ name: string;
+ email: string;
+ avatar: string;
+ updatedAt: number;
+ }
+
+ // ─── Event Registry ─────────────────────────────────────────
+
+ interface AppEventRegistry {
+ // ── Device / Hardware ─────────────────────────────────────
+ 'DEVICE:PRINT_RECEIPT': PrintReceiptPayload;
+
+ // ── WebSocket / Real-Time ────────────────────────────────
+ 'WS:STOCK_UPDATE': StockUpdatePayload;
+
+ // ── Auth / User ──────────────────────────────────────────
+ 'AUTH:PROFILE_UPDATED': ProfileUpdatedPayload;
+
+ // ── App Lifecycle ────────────────────────────────────────
+ 'APP:INITIALIZED': undefined;
+ 'APP:ERROR': { message: string; code?: string };
+
+ // ── Layout ───────────────────────────────────────────────
+ 'LAYOUT:TOGGLE_HISTORY_DRAWER': undefined;
+ 'LAYOUT:TOGGLE_BOOKMARK_DRAWER': undefined;
+ }
+}
diff --git a/apps/showcase/src/vite-env.d.ts b/apps/showcase/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/apps/showcase/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/apps/showcase/tsconfig.json b/apps/showcase/tsconfig.json
new file mode 100644
index 0000000..697b3f5
--- /dev/null
+++ b/apps/showcase/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "@repo/typescript-config/react-app.json",
+ "include": ["src"],
+ "compilerOptions": {
+ "jsx": "react-jsx"
+ }
+}
diff --git a/apps/showcase/vite.config.ts b/apps/showcase/vite.config.ts
new file mode 100644
index 0000000..d3b94fb
--- /dev/null
+++ b/apps/showcase/vite.config.ts
@@ -0,0 +1,19 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import tailwindcss from '@tailwindcss/vite';
+
+export default defineConfig({
+ plugins: [react(), tailwindcss()],
+ server: {
+ port: 3005,
+ strictPort: true,
+ },
+ define: {
+ global: 'window',
+ },
+ resolve: {
+ alias: {
+ events: 'events',
+ },
+ },
+});
diff --git a/apps/web/src/apps/index.tsx b/apps/web/src/apps/index.tsx
index 7d0920d..bbf9c1d 100644
--- a/apps/web/src/apps/index.tsx
+++ b/apps/web/src/apps/index.tsx
@@ -1,6 +1,6 @@
-import { lazy, Suspense, useEffect, useState } from 'react';
+import { lazy, Suspense, useEffect } from 'react';
import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
-import { ThemeProvider, DensityType } from '@repo/ui/provider';
+import { ThemeProvider } from '@repo/ui/provider';
import { NotFound, Forbidden, Maintenance, ComingSoon, AgGridProvider } from '@repo/ui/components';
import { LoadingScreen } from '../core/components/loading-screen';
import { useThemeStore } from '../core/stores/theme.store';
@@ -8,12 +8,9 @@ import { initializeAndPurgeHistoryBackground } from './modules/layouts/hooks/use
const AuthModule = lazy(() => import('./auth'));
const AppModule = lazy(() => import('./modules'));
-const ShowcaseView = lazy(() => import('./showcase/showcase-view'));
-const ShellDemo = lazy(() => import('./showcase/shell-demo'));
export default function App() {
const colorScheme = useThemeStore((s) => s.colorScheme);
- const [density, setDensity] = useState('compact');
useEffect(() => {
// Execution runs purely in the background (fire and forget)
@@ -22,20 +19,18 @@ export default function App() {
}, []);
return (
-
+
}>
} />
} />
- } />
- } />
} />
} />
} />
} />
- } />
+ } />
} />
diff --git a/apps/web/src/apps/showcase/registry.ts b/apps/web/src/apps/showcase/registry.ts
deleted file mode 100644
index c342c72..0000000
--- a/apps/web/src/apps/showcase/registry.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { lazy } from 'react';
-import { Layers, Table2 } from 'lucide-react';
-
-export const COMPONENTS_REGISTRY = {
- actionTools: {
- id: 'action-tools',
- name: 'Action Tools',
- description: 'Showcase for PageActions and RowActions components',
- icon: Layers,
- component: lazy(() => import('./components/ActionToolsShowcase')),
- },
- agGrid: {
- id: 'ag-grid',
- name: 'AG Grid',
- description: 'Enterprise Data Grid with Mantine Theme Integration',
- icon: Table2,
- component: lazy(() => import('./components/AgGridShowcase')),
- },
-};
-
diff --git a/apps/web/src/apps/showcase/showcase-view.tsx b/apps/web/src/apps/showcase/showcase-view.tsx
deleted file mode 100644
index d8cfe3c..0000000
--- a/apps/web/src/apps/showcase/showcase-view.tsx
+++ /dev/null
@@ -1,437 +0,0 @@
-import { useState, Suspense } from 'react';
-import { useNavigate } from 'react-router-dom';
-import { ColorSchemeType, DensityType } from '@repo/ui/provider';
-import { useThemeStore } from '../../core/stores/theme.store';
-import {
- Button,
- Card,
- Container,
- Select,
- Group,
- Stack,
- Text,
- Title,
- TextInput,
- Checkbox,
- PasswordInput,
- NumberInput,
- Textarea,
- Switch,
- Table,
- Badge,
- Divider,
- Tabs,
- Box,
- Paper,
- StatusBadge,
- STATUS_DATA,
-} from '@repo/ui/components';
-import { ShieldCheck, Database, Lock, Layout, Activity, Printer, FileText, LayoutDashboard } from 'lucide-react';
-import { Globe } from 'lucide-react';
-import { useTranslation } from '@repo/core-i18n';
-import PrinterList from './printer-list';
-import ExamplePage from './example/example.page';
-import EventsDemoPage from './events-demo';
-import PouchSample from './pouch-sample';
-import FormDemoView from './example/features/form-demo';
-import { COMPONENTS_REGISTRY } from './registry';
-
-interface ShowcaseViewProps {
- density: DensityType;
- setDensity: (val: DensityType) => void;
-}
-
-export default function ShowcaseView({ density, setDensity }: ShowcaseViewProps) {
- const { colorScheme, setColorScheme } = useThemeStore();
- const [activeTab, setActiveTab] = useState('ui-components');
- const { i18n } = useTranslation();
- const navigate = useNavigate();
-
- // Mock data for the table
- const tableData = [
- { id: 'ORD-001', customer: 'John Doe', status: 'Shipped', total: '$120.00' },
- { id: 'ORD-002', customer: 'Jane Smith', status: 'Pending', total: '$85.50' },
- { id: 'ORD-003', customer: 'Acme Corp', status: 'Delivered', total: '$1,250.00' },
- ];
-
- const getSubtitle = () => {
- const registryItem = Object.values(COMPONENTS_REGISTRY).find((item) => item.id === activeTab);
- if (registryItem) {
- return registryItem.description;
- }
-
- switch (activeTab) {
- case 'rbac':
- return 'Role-Based Access Control and Permissions';
- case 'storage':
- return 'Offline-First PouchDB Synchronization';
- case 'auth':
- return 'Authentication & Security Layers';
- case 'ui-components':
- return 'Theme, Typography, Forms & Data Grids';
- case 'forms':
- return 'Enterprise Form Engine & Zod Validation';
- case 'events':
- return 'Global Event Bus Synchronization';
- case 'hardware':
- return 'Hardware Integration & Printers';
- case 'layout-engine':
- return 'Core Layout Engine & Variants';
- default:
- return 'Architecture Showcase';
- }
- };
-
- return (
-
- {
- if (val === 'layout-engine') {
- navigate('/shell-demo');
- } else if (val === 'apps') {
- navigate('/app');
- } else {
- setActiveTab(val);
- }
- }}
- variant="pills"
- radius="md"
- className="h-screen"
- styles={{
- root: { display: 'flex', height: '100vh', overflow: 'hidden' },
- list: {
- minWidth: 260,
- padding: '1rem',
- borderRight: '1px solid var(--app-shell-border-color)',
- backgroundColor: 'var(--mantine-color-default-element-bg)',
- },
- panel: { flex: 1, display: 'flex', flexDirection: 'column', height: '100vh', overflow: 'hidden' },
- tab: { justifyContent: 'flex-start' }, // Ensure the entire tab content aligns left
- tabLabel: { textAlign: 'left', flex: 1 }, // Ensure the text pushes to fill and aligns left
- }}
- >
-
-
-
- Eigen ERP
-
-
- Architecture Showcase
-
-
-
- }>
- UI Components
-
- }>
- Layout Engine
-
- }>
- Form Engine
-
- }>
- Offline Storage
-
- }>
- RBAC Engine
-
- }>
- Auth & Security
-
- }>
- Events
-
- }>
- Hardware
-
- }>
- Apps
-
-
- {Object.values(COMPONENTS_REGISTRY).map((registryItem) => {
- const Icon = registryItem.icon;
- return (
- }>
- {registryItem.name}
-
- );
- })}
-
-
-
- {/* Header */}
-
-
-
- Architecture Showcase
-
- {getSubtitle()}
-
-
- }
- data={[
- { value: 'en', label: 'English' },
- { value: 'id', label: 'Bahasa Indonesia' },
- ]}
- value={i18n.resolvedLanguage || i18n.language}
- onChange={(val) => val && i18n.changeLanguage(val)}
- />
-
-
-
- {/* Scrollable Content Area */}
-
-
- {/* --- UI COMPONENTS TAB --- */}
- {activeTab === 'ui-components' && (
-
- {/* Control Panel */}
-
-
- Theme Controls
-
-
- setColorScheme((val as ColorSchemeType) || 'light')}
- data={[
- { value: 'light', label: 'Light' },
- { value: 'dark', label: 'Dark' },
- ]}
- />
- setDensity((val as DensityType) || 'standard')}
- data={[
- { value: 'compact', label: 'Compact (ERP Mode)' },
- { value: 'standard', label: 'Standard (UI Mode)' },
- ]}
- />
-
-
-
- {/* Typography & Buttons */}
-
-
-
-
- Typography & Badges
-
-
- This is dimmed small text indicating a subtitle.
-
-
- Brand Badge
-
- Success Status
-
-
- Error State
-
-
-
-
-
-
- Enterprise Status Badges
-
-
- Pre-configured status badges for transaction and master data.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Buttons
-
-
-
-
-
-
-
-
-
-
-
- {/* Forms */}
-
-
- Form Elements
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* Data Grid */}
-
-
- Data Grid
-
-
-
-
- Order ID
- Customer
- Status
- Total
-
-
-
- {tableData.map((row) => (
-
- {row.id}
- {row.customer}
-
-
- {row.status}
-
-
- {row.total}
-
- ))}
-
-
-
-
- )}
-
- {/* --- FORMS TAB --- */}
- {activeTab === 'forms' && (
-
-
-
- )}
-
- {/* --- STORAGE TAB --- */}
- {activeTab === 'storage' && (
-
-
-
- )}
-
- {/* --- RBAC TAB --- */}
- {activeTab === 'rbac' && (
-
-
-
- RBAC Engine
-
- RBAC Demo Component Coming Soon...
-
-
- )}
-
- {/* --- AUTH TAB --- */}
- {activeTab === 'auth' && (
-
-
-
- Auth & Security
-
- Auth Demo Component Coming Soon...
-
-
- )}
-
- {/* --- EVENTS TAB --- */}
- {activeTab === 'events' && (
-
-
-
- Nested Showcase Example
-
- This is an example of a nested showcase component.
-
-
-
-
-
-
- )}
-
- {/* --- HARDWARE TAB --- */}
- {activeTab === 'hardware' && (
-
-
-
-
-
- )}
-
- {/* --- DYNAMIC COMPONENTS FROM REGISTRY --- */}
- {Object.values(COMPONENTS_REGISTRY).map((registryItem) => {
- const Component = registryItem.component;
- return (
- activeTab === registryItem.id && (
-
- Loading {registryItem.name}...}>
-
-
-
- )
- );
- })}
-
-
-
-
-
- );
-}
diff --git a/package.json b/package.json
index 4cec90d..c04febc 100644
--- a/package.json
+++ b/package.json
@@ -8,11 +8,13 @@
"build:landing": "turbo run build --filter=landing",
"build:docs-dev": "turbo run build --filter=docs-dev",
"build:desktop": "turbo run build --filter=desktop",
+ "build:showcase": "turbo run build --filter=showcase",
"dev": "turbo run dev",
"dev:web": "turbo run dev --filter=web",
"dev:landing": "turbo run dev --filter=landing",
"dev:docs-dev": "turbo run dev --filter=docs-dev",
"dev:desktop": "turbo run dev --filter=web --filter=desktop --parallel",
+ "dev:showcase": "turbo run dev --filter=showcase",
"package:desktop": "pnpm run build:desktop && pnpm --filter desktop run package",
"package:mac": "pnpm run build:desktop && pnpm --filter desktop run package:mac",
"package:win": "pnpm run build:desktop && pnpm --filter desktop run package:win",
@@ -24,6 +26,7 @@
"typecheck:web": "turbo run typecheck --filter=web",
"typecheck:landing": "turbo run typecheck --filter=landing",
"typecheck:desktop": "turbo run typecheck --filter=desktop",
+ "typecheck:showcase": "turbo run typecheck --filter=showcase",
"check:all": "turbo run lint typecheck test",
"clean:workspaces": "rm -rf node_modules **/*/node_modules .turbo **/*/.turbo dist **/*/dist out **/*/out web-dist **/*/web-dist release **/*/release",
"nuke": "pnpm run clean:workspaces && pnpm install && pnpm build"
diff --git a/packages/ui/src/foundations/enterprise-module/components/data-table/index.tsx b/packages/ui/src/foundations/enterprise-module/components/data-table/index.tsx
index b423fae..a8bc8f0 100644
--- a/packages/ui/src/foundations/enterprise-module/components/data-table/index.tsx
+++ b/packages/ui/src/foundations/enterprise-module/components/data-table/index.tsx
@@ -44,7 +44,7 @@ import { BulkActionMenu } from './components/bulk-actions';
import { ActionConfirmationModal, ACTION_TRANSLATION_MAP } from '../action-confirmation-modal';
import { BulkActionConfirmationModal } from '../bulk-action-confirmation';
import { TableFilterDrawer, TableFilterConfig } from './components/table-filter-drawer';
-import { TableSettingDrawer } from './components/table-setting-drawer';
+// import { TableSettingDrawer } from './components/table-setting-drawer';
import { EntityId } from '../../../../../../core-api/src/data-services/types';
export * from 'ag-grid-community';
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 00ed30e..fd04a69 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -147,6 +147,97 @@ importers:
specifier: ^5.1.4
version: 5.4.17(@types/node@22.19.3)
+ apps/showcase:
+ dependencies:
+ '@hookform/resolvers':
+ specifier: ^5.0.1
+ version: 5.4.0(react-hook-form@7.79.0)
+ '@repo/core-api':
+ specifier: workspace:*
+ version: link:../../packages/core-api
+ '@repo/core-events':
+ specifier: workspace:*
+ version: link:../../packages/core-events
+ '@repo/core-i18n':
+ specifier: workspace:*
+ version: link:../../packages/core-i18n
+ '@repo/core-storage':
+ specifier: workspace:*
+ version: link:../../packages/core-storage
+ '@repo/ui':
+ specifier: workspace:*
+ version: link:../../packages/ui
+ '@repo/utils':
+ specifier: workspace:*
+ version: link:../../packages/utils
+ '@tailwindcss/vite':
+ specifier: ^4.1.18
+ version: 4.1.18(vite@5.4.17)
+ dayjs:
+ specifier: ^1.11.19
+ version: 1.11.19
+ events:
+ specifier: ^3.3.0
+ version: 3.3.0
+ i18next:
+ specifier: ^24.2.2
+ version: 24.2.3(typescript@5.5.4)
+ lucide-react:
+ specifier: ^1.22.0
+ version: 1.22.0(react@19.2.3)
+ react:
+ specifier: ^19.2.3
+ version: 19.2.3
+ react-dom:
+ specifier: ^19.2.3
+ version: 19.2.3(react@19.2.3)
+ react-hook-form:
+ specifier: ^7.56.4
+ version: 7.79.0(react@19.2.3)
+ react-i18next:
+ specifier: ^15.4.0
+ version: 15.7.4(i18next@24.2.3)(react-dom@19.2.3)(react@19.2.3)(typescript@5.5.4)
+ react-router-dom:
+ specifier: ^7.11.0
+ version: 7.11.0(react-dom@19.2.3)(react@19.2.3)
+ tailwindcss:
+ specifier: ^4.1.18
+ version: 4.1.18
+ zod:
+ specifier: ^3.25.36
+ version: 3.25.76
+ zustand:
+ specifier: ^5.0.14
+ version: 5.0.14(@types/react@19.2.7)(react@19.2.3)
+ devDependencies:
+ '@repo/eslint-config':
+ specifier: workspace:*
+ version: link:../../packages/configs/eslint
+ '@repo/typescript-config':
+ specifier: workspace:*
+ version: link:../../packages/configs/typescript
+ '@types/react':
+ specifier: ^19.2.7
+ version: 19.2.7
+ '@types/react-dom':
+ specifier: ^19.2.3
+ version: 19.2.3(@types/react@19.2.7)
+ '@vitejs/plugin-react':
+ specifier: ^5.1.2
+ version: 5.1.2(vite@5.4.17)
+ eslint:
+ specifier: ^8.57.1
+ version: 8.57.1
+ typescript:
+ specifier: 5.5.4
+ version: 5.5.4
+ vite:
+ specifier: ^5.1.4
+ version: 5.4.17(@types/node@22.19.3)
+ vitest:
+ specifier: ^4.0.17
+ version: 4.0.17(jsdom@26.1.0)
+
apps/web:
dependencies:
'@hookform/resolvers':
@@ -236,7 +327,7 @@ importers:
version: 5.4.17(@types/node@22.19.3)
vitest:
specifier: ^4.0.17
- version: 4.0.17(@opentelemetry/api@1.9.1)
+ version: 4.0.17(jsdom@26.1.0)
packages/configs/eslint:
dependencies:
@@ -433,7 +524,7 @@ importers:
version: 5.5.4
vitest:
specifier: ^4.0.17
- version: 4.0.17(@opentelemetry/api@1.9.1)
+ version: 4.0.17(jsdom@26.1.0)
packages/ui:
dependencies:
@@ -597,7 +688,7 @@ importers:
version: 5.5.4
vitest:
specifier: ^4.0.17
- version: 4.0.17(@opentelemetry/api@1.9.1)
+ version: 4.0.17(jsdom@26.1.0)
packages:
@@ -2103,7 +2194,7 @@ packages:
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
dependencies:
'@gar/promisify': 1.1.3
- semver: 7.7.1
+ semver: 7.7.4
dev: true
/@npmcli/git@5.0.8:
@@ -9557,7 +9648,7 @@ packages:
resolution: {integrity: sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
dependencies:
- semver: 7.7.1
+ semver: 7.7.4
dev: false
/npm-normalize-package-bin@3.0.1:
@@ -9571,7 +9662,7 @@ packages:
dependencies:
hosted-git-info: 7.0.2
proc-log: 4.2.0
- semver: 7.7.1
+ semver: 7.7.4
validate-npm-package-name: 5.0.1
dev: false
@@ -9582,7 +9673,7 @@ packages:
npm-install-checks: 6.3.0
npm-normalize-package-bin: 3.0.1
npm-package-arg: 11.0.3
- semver: 7.7.1
+ semver: 7.7.4
dev: false
/npmlog@6.0.2:
@@ -10962,7 +11053,6 @@ packages:
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
engines: {node: '>=10'}
hasBin: true
- dev: false
/serialize-error@7.0.1:
resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==}