From 86c02e711148a6b29c840ea4ac42f609ef9aeb7d Mon Sep 17 00:00:00 2001 From: Firman Ramdhani <33869609+firmanramdhani@users.noreply.github.com> Date: Fri, 29 May 2026 13:43:31 +0700 Subject: [PATCH 1/5] feat: implement type-safe environment configuration and add .env.example files for web and landing apps --- apps/landing/.env.example | 1 + apps/landing/src/environment/env.ts | 6 ++++++ apps/web/.env.example | 2 ++ apps/web/src/environment/env.ts | 10 ++++++++++ 4 files changed, 19 insertions(+) create mode 100644 apps/landing/.env.example create mode 100644 apps/landing/src/environment/env.ts create mode 100644 apps/web/.env.example create mode 100644 apps/web/src/environment/env.ts diff --git a/apps/landing/.env.example b/apps/landing/.env.example new file mode 100644 index 0000000..36c47e4 --- /dev/null +++ b/apps/landing/.env.example @@ -0,0 +1 @@ +VITE_CMS_API_URL=http://localhost:8001/cms diff --git a/apps/landing/src/environment/env.ts b/apps/landing/src/environment/env.ts new file mode 100644 index 0000000..b54810f --- /dev/null +++ b/apps/landing/src/environment/env.ts @@ -0,0 +1,6 @@ +/** + * Type-safe Environment Wrapper for apps/landing. + */ +export const ENV = { + CMS_API_URL: import.meta.env.VITE_CMS_API_URL || 'http://localhost:8001/cms', +} as const; diff --git a/apps/web/.env.example b/apps/web/.env.example new file mode 100644 index 0000000..c0215e8 --- /dev/null +++ b/apps/web/.env.example @@ -0,0 +1,2 @@ +VITE_API_BASE_URL=http://localhost:8000/api +VITE_APP_ENV=development diff --git a/apps/web/src/environment/env.ts b/apps/web/src/environment/env.ts new file mode 100644 index 0000000..0755a0a --- /dev/null +++ b/apps/web/src/environment/env.ts @@ -0,0 +1,10 @@ +/** + * Type-safe Environment Wrapper for apps/web. + * DO NOT use `import.meta.env` directly in components. Import this `ENV` object instead. + */ +export const ENV = { + API_BASE_URL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000/api', + APP_ENV: (import.meta.env.VITE_APP_ENV || 'development') as 'development' | 'staging' | 'production', + IS_PROD: import.meta.env.VITE_APP_ENV === 'production', +} as const; + From 6712558eaffa7ca56e42c1a5d1cb58dbad72149b Mon Sep 17 00:00:00 2001 From: Firman Ramdhani <33869609+firmanramdhani@users.noreply.github.com> Date: Fri, 29 May 2026 15:35:14 +0700 Subject: [PATCH 2/5] feat: implement PouchDB storage layer with CRUD operations and add showcase UI component --- apps/web/.env.example | 3 + apps/web/package.json | 2 + apps/web/src/apps/showcase/pouch-sample.tsx | 237 ++++++++ apps/web/src/apps/showcase/showcase-view.tsx | 417 ++++++++------ apps/web/src/core/db/index.ts | 57 ++ apps/web/src/core/db/types.ts | 47 ++ apps/web/src/environment/env.ts | 6 +- apps/web/vite.config.ts | 10 + packages/core-storage/README.md | 225 ++++---- packages/core-storage/package.json | 13 +- packages/core-storage/src/index.ts | 2 + packages/core-storage/src/pouch.ts | 289 ++++++++++ packages/core-storage/src/storage.test.ts | 2 +- packages/core-storage/tests/pouch.test.ts | 257 +++++++++ pnpm-lock.yaml | 562 ++++++++++++++++++- 15 files changed, 1820 insertions(+), 309 deletions(-) create mode 100644 apps/web/src/apps/showcase/pouch-sample.tsx create mode 100644 apps/web/src/core/db/index.ts create mode 100644 apps/web/src/core/db/types.ts create mode 100644 packages/core-storage/src/pouch.ts create mode 100644 packages/core-storage/tests/pouch.test.ts diff --git a/apps/web/.env.example b/apps/web/.env.example index c0215e8..dcd4573 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -1,2 +1,5 @@ VITE_API_BASE_URL=http://localhost:8000/api VITE_APP_ENV=development +VITE_COUCHDB_BASE_URL=http://202.146.229.134:7700 +VITE_COUCHDB_USERNAME=root +VITE_COUCHDB_PASSWORD=password diff --git a/apps/web/package.json b/apps/web/package.json index fab2c64..c959bda 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -21,7 +21,9 @@ "@repo/utils": "workspace:*", "@tailwindcss/vite": "^4.1.18", "dayjs": "^1.11.19", + "events": "^3.3.0", "i18next": "^24.2.2", + "lucide-react": "^1.17.0", "react": "^19.2.3", "react-dom": "^19.2.3", "react-i18next": "^15.4.0", diff --git a/apps/web/src/apps/showcase/pouch-sample.tsx b/apps/web/src/apps/showcase/pouch-sample.tsx new file mode 100644 index 0000000..08ccfe4 --- /dev/null +++ b/apps/web/src/apps/showcase/pouch-sample.tsx @@ -0,0 +1,237 @@ +import { useEffect, useState, useCallback } from 'react'; +import { + Button, + Card, + Group, + Stack, + Title, + Text, + Table, + Badge, +} from '@repo/ui/components'; +import { itemDB, posConfigDB } from '../../core/db'; +import type { Item, POSConfiguration } from '../../core/db/types'; + +export default function PouchSample() { + const [configs, setConfigs] = useState([]); + const [items, setItems] = useState([]); + + // Load initial data + const loadData = useCallback(async () => { + try { + const allConfigs = await posConfigDB.find({ selector: {} }); + setConfigs(allConfigs); + + const allItems = await itemDB.find({ selector: {} }); + setItems(allItems); + console.log({allConfigs, allItems}) + } catch (err) { + console.error('Failed to load PouchDB data', err); + } + }, []); + + useEffect(() => { + // 1. Initial Data Load + loadData(); + + // 2. Setup Real-Time Listeners + const unsubscribeItems = itemDB.onChange(() => { + loadData(); + }); + + const unsubscribePos = posConfigDB.onChange(() => { + loadData(); + }); + + // 3. CRITICAL: Cleanup to prevent memory leaks on unmount + return () => { + unsubscribeItems(); + unsubscribePos(); + }; + }, [loadData]); + + // ─── POS Configuration Handlers ───────────────────────────────── + + const handleSeedConfig = async () => { + try { + const id = `pos-${Date.now()}`; + await posConfigDB.create({ + _id: id, + pos_number: '1111111111666', + pos_name: 'Premium Test POS TESTING COUNCH', + items: items, // mapping current items + payment_methods: [{ id: 'cash', name: 'Cash' }], + }); + loadData(); + } catch (err) { + console.error('Failed to seed config', err); + } + }; + + const handleDeleteConfig = async (id: string) => { + try { + await posConfigDB.delete(id); + loadData(); + } catch (err) { + console.error('Failed to delete config', err); + } + }; + + // ─── Items Inventory Handlers ─────────────────────────────────── + + const handleAddItem = async () => { + try { + const id = `item-${Date.now()}`; + await itemDB.create({ + _id: id, + name: 'PLAYGROUND ALL DAY TESTING POUCH', + base_price: '75000', + item_type: 'wahana', + usage_type: 'ticket', + item_category: [{ name: 'Entertainment' }], + item_rates: [ + { season_period: 'weekday', price: 50000 }, + { season_period: 'weekend', price: 75000 }, + ], + }); + + loadData(); + } catch (err) { + console.error('Failed to add item', err); + } + }; + + const handleDeleteItem = async (id: string) => { + try { + await itemDB.delete(id); + loadData(); + } catch (err) { + console.error('Failed to delete item', err); + } + }; + + const handleClearAll = async () => { + try { + await posConfigDB.cleanAllData(); + await itemDB.cleanAllData(); + loadData(); + } catch (err) { + console.error('Failed to clear data', err); + } + }; + + return ( + + + Enterprise PouchDB Sync + + + + {/* Items Inventory Table */} + + + Items Database + + + +
+ + + + ID + Name + Type + Base Price + Rates Count + Actions + + + + {items.length > 0 ? ( + items.map((item) => ( + + {item._id} + {item.name} + + + {item.item_type} + + + ${Number(item.base_price).toFixed(2)} + {item.item_rates?.length || 0} + + + + + )) + ) : ( + + + No items found. + + + )} + +
+
+
+ + {/* POS Configuration Table */} + + + POS Configurations + + + +
+ + + + ID + POS Name + POS Number + Mapped Items + Actions + + + + {configs.length > 0 ? ( + configs.map((cfg) => ( + + {cfg._id} + {cfg.pos_name} + {cfg.pos_number} + + + {cfg.items?.length || 0} Items + + + + + + + )) + ) : ( + + + No configurations found. + + + )} + +
+
+
+
+ ); +} diff --git a/apps/web/src/apps/showcase/showcase-view.tsx b/apps/web/src/apps/showcase/showcase-view.tsx index 5cf52bf..eda7047 100644 --- a/apps/web/src/apps/showcase/showcase-view.tsx +++ b/apps/web/src/apps/showcase/showcase-view.tsx @@ -1,3 +1,4 @@ +import { useState } from 'react'; import { ColorSchemeType, DensityType } from '@repo/ui/provider'; import { Button, @@ -18,10 +19,15 @@ import { Table, Badge, Divider, + Tabs, + Box, + Paper, } from '@repo/ui/components'; +import { ShieldCheck, Database, Lock, Layout, Activity, Printer } from 'lucide-react'; import PrinterList from './printer-list'; import ExamplePage from './example/example.page'; import EventsDemoPage from './events-demo'; +import PouchSample from './pouch-sample'; interface ShowcaseViewProps { colorScheme: ColorSchemeType; @@ -31,6 +37,8 @@ interface ShowcaseViewProps { } export default function ShowcaseView({ colorScheme, setColorScheme, density, setDensity }: ShowcaseViewProps) { + const [activeTab, setActiveTab] = useState('ui-components'); + // Mock data for the table const tableData = [ { id: 'ORD-001', customer: 'John Doe', status: 'Shipped', total: '$120.00' }, @@ -38,194 +46,245 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set { id: 'ORD-003', customer: 'Acme Corp', status: 'Delivered', total: '$1,250.00' }, ]; + const getSubtitle = () => { + 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 'events': return 'Global Event Bus Synchronization'; + case 'hardware': return 'Hardware Integration & Printers'; + default: return 'Architecture Showcase'; + } + }; + return ( - - - Super App UI Showcase + + + + + Eigen ERP + Architecture Showcase + - {/* ========================================= - CONTROL PANEL - ========================================= */} - - - Theme Controls - - - setDensity((val as DensityType) || 'standard')} - data={[ - { value: 'compact', label: 'Compact (ERP Mode)' }, - { value: 'standard', label: 'Standard (UI Mode)' }, - ]} - /> - - + }> + UI Components + + }> + Offline Storage + + }> + RBAC Engine + + }> + Auth & Security + + }> + Events + + }> + Hardware + + - {/* ========================================= - TAILWIND V4 BRIDGE TEST - ========================================= */} - - - Tailwind v4 Synchronization - - {/* This div purely uses Tailwind classes to prove it inherits Mantine's variables */} -
- Tailwind works! The padding (p-md), border-radius (rounded-md), text size - (text-base), and background color of this box are entirely controlled by the ThemeProvider's current state. -
-
- - {/* ========================================= - TYPOGRAPHY & BUTTONS - ========================================= */} - - -
- - Typography & Badges - - - This is dimmed small text indicating a subtitle. - - - This is standard text describing the components below. Watch how the font changes when you switch - density. - - - Brand Badge - - Success Status - - - Error State - - -
- - - -
- - Buttons - - - - - - - -
-
-
- - {/* ========================================= - COMPLEX FORMS (ERP STYLE) - ========================================= */} - - - Form Elements - - - - - + + {/* Header */} + + + + Architecture Showcase + {getSubtitle()} + + - - - - + {/* Scrollable Content Area */} + + + + {/* --- UI COMPONENTS TAB --- */} + {activeTab === 'ui-components' && ( + + {/* Control Panel */} + + Theme Controls + + setDensity((val as DensityType) || 'standard')} + data={[{ value: 'compact', label: 'Compact (ERP Mode)' }, { value: 'standard', label: 'Standard (UI Mode)' }]} + /> + + -