feat: add showcase example component and integrate booking and storage demos

This commit is contained in:
Firman Ramdhani
2026-05-23 06:55:58 +07:00
parent 4655362ff4
commit b6ba41e120
10 changed files with 80 additions and 43 deletions
+1 -2
View File
@@ -33,8 +33,7 @@ export default function App() {
<Route path="/403" element={<Forbidden />} /> <Route path="/403" element={<Forbidden />} />
<Route path="/maintenance" element={<Maintenance />} /> <Route path="/maintenance" element={<Maintenance />} />
<Route path="/coming-soon" element={<ComingSoon />} /> <Route path="/coming-soon" element={<ComingSoon />} />
{/* <Route path="/" element={<Navigate to="/showcase" />} /> */} <Route path="/" element={<Navigate to="/showcase" />} />
<Route path="/" element={<Navigate to="/app" />} />
<Route path="*" element={<Navigate to="/404" />} /> <Route path="*" element={<Navigate to="/404" />} />
</Routes> </Routes>
</Suspense> </Suspense>
@@ -1,19 +1,3 @@
import BookingSample from "./features/booking/presentation/BookingSample";
import StorageSample from "./features/storage/presentation/StorageSample";
import I18nSample from "./features/i18n/presentation/I18nSample";
export default function ExamplePage() { export default function ExamplePage() {
return <div className="bg-amber-200">example return <div className="bg-amber-200">example</div>;
<div className="p-8">
<h1 className="text-2xl font-bold mb-4">Enterprise Web App</h1>
<BookingSample />
</div>
<div className="p-8">
<h1 className="text-2xl font-bold mb-4">Enterprise Web App</h1>
<StorageSample />
</div>
<div className="p-8 bg-slate-900">
<I18nSample />
</div>
</div>;
} }
@@ -0,0 +1,22 @@
import BookingSample from './features/booking/presentation/BookingSample';
import StorageSample from './features/storage/presentation/StorageSample';
import I18nSample from './features/i18n/presentation/I18nSample';
export default function ExamplePage() {
return (
<div className="bg-amber-200">
example
<div className="p-8">
<h1 className="text-2xl font-bold mb-4">Enterprise Web App</h1>
<BookingSample />
</div>
<div className="p-8">
<h1 className="text-2xl font-bold mb-4">Enterprise Web App</h1>
<StorageSample />
</div>
<div className="p-8 bg-slate-900">
<I18nSample />
</div>
</div>
);
}
@@ -47,6 +47,8 @@ export default function I18nSample() {
try { try {
const data = await demoIndexedDB.getItem<any>(MOCK_DB_KEY); const data = await demoIndexedDB.getItem<any>(MOCK_DB_KEY);
setDbPayloadStr(data ? JSON.stringify(data, null, 2) : 'No data in DB'); setDbPayloadStr(data ? JSON.stringify(data, null, 2) : 'No data in DB');
setAdminHeaderTitle(data?.overrides?.header?.title || 'Daftar Pengeluaran');
setAdminModuleName(data?.overrides?.module_name || 'PENGELUARAN');
} catch (e) { } catch (e) {
setDbPayloadStr('Error reading DB'); setDbPayloadStr('Error reading DB');
} }
@@ -61,8 +63,8 @@ export default function I18nSample() {
namespace: 'booking', namespace: 'booking',
overrides: { overrides: {
module_name: adminModuleName, module_name: adminModuleName,
header: { title: adminHeaderTitle } header: { title: adminHeaderTitle },
} },
}; };
await demoIndexedDB.setItem(MOCK_DB_KEY, payload); await demoIndexedDB.setItem(MOCK_DB_KEY, payload);
setSyncStatus('✅ Saved tenant config to IndexedDB!'); setSyncStatus('✅ Saved tenant config to IndexedDB!');
@@ -83,8 +85,8 @@ export default function I18nSample() {
namespace: 'booking', namespace: 'booking',
overrides: { overrides: {
module_name: 'PROCUREMENT (B)', module_name: 'PROCUREMENT (B)',
header: { title: 'Procurement List (B)' } header: { title: 'Procurement List (B)' },
} },
}; };
} }
throw new Error('Unknown company'); throw new Error('Unknown company');
@@ -137,7 +139,6 @@ export default function I18nSample() {
// In a real app, you might apply this to the current active language or all languages. // In a real app, you might apply this to the current active language or all languages.
applyTenantOverrides(config.namespace, config.overrides, 'id'); applyTenantOverrides(config.namespace, config.overrides, 'id');
applyTenantOverrides(config.namespace, config.overrides, 'en'); applyTenantOverrides(config.namespace, config.overrides, 'en');
} catch (err) { } catch (err) {
console.error('Failed to fetch config', err); console.error('Failed to fetch config', err);
} finally { } finally {
@@ -153,7 +154,7 @@ export default function I18nSample() {
}; };
return ( return (
<div style={{ fontFamily: 'sans-serif', maxWidth: 800, color: '#f8fafc' }}> <div style={{ fontFamily: 'sans-serif', color: '#f8fafc' }}>
<h2 style={{ fontSize: 24, fontWeight: 'bold' }}>🌐 Enterprise i18n Demo</h2> <h2 style={{ fontSize: 24, fontWeight: 'bold' }}>🌐 Enterprise i18n Demo</h2>
<p style={{ color: '#94a3b8' }}> <p style={{ color: '#94a3b8' }}>
Current Active Language: <strong style={{ color: '#38bdf8' }}>{i18n.language}</strong> Current Active Language: <strong style={{ color: '#38bdf8' }}>{i18n.language}</strong>
@@ -173,7 +174,14 @@ export default function I18nSample() {
type="text" type="text"
value={adminModuleName} value={adminModuleName}
onChange={(e) => setAdminModuleName(e.target.value)} onChange={(e) => setAdminModuleName(e.target.value)}
style={{ padding: 6, borderRadius: 4, background: '#1e293b', border: '1px solid #475569', color: '#fff', width: 250 }} style={{
padding: 6,
borderRadius: 4,
background: '#1e293b',
border: '1px solid #475569',
color: '#fff',
width: 250,
}}
/> />
</label> </label>
<label style={{ fontSize: 14 }}> <label style={{ fontSize: 14 }}>
@@ -182,7 +190,14 @@ export default function I18nSample() {
type="text" type="text"
value={adminHeaderTitle} value={adminHeaderTitle}
onChange={(e) => setAdminHeaderTitle(e.target.value)} onChange={(e) => setAdminHeaderTitle(e.target.value)}
style={{ padding: 6, borderRadius: 4, background: '#1e293b', border: '1px solid #475569', color: '#fff', width: 250 }} style={{
padding: 6,
borderRadius: 4,
background: '#1e293b',
border: '1px solid #475569',
color: '#fff',
width: 250,
}}
/> />
</label> </label>
</div> </div>
@@ -237,14 +252,12 @@ export default function I18nSample() {
<div style={sectionStyle}> <div style={sectionStyle}>
<h3 style={{ fontSize: 18, marginBottom: 16 }}>B. Dynamic Tenant Overrides (End-to-End)</h3> <h3 style={{ fontSize: 18, marginBottom: 16 }}>B. Dynamic Tenant Overrides (End-to-End)</h3>
<p style={{ fontSize: 14, color: '#94a3b8', marginBottom: 16 }}> <p style={{ fontSize: 14, color: '#94a3b8', marginBottom: 16 }}>
Simulates a user logging in. It fetches the config directly from IndexedDB (mock database) and applies the deep-merge override. Simulates a user logging in. It fetches the config directly from IndexedDB (mock database) and applies the
deep-merge override.
</p> </p>
<div style={{ display: 'flex', gap: 8, marginBottom: 24 }}> <div style={{ display: 'flex', gap: 8, marginBottom: 24 }}>
<button <button onClick={resetTenant} style={btnStyle(activeTenant === 'default' ? '#16a34a' : '#475569')}>
onClick={resetTenant}
style={btnStyle(activeTenant === 'default' ? '#16a34a' : '#475569')}
>
Default Company Default Company
</button> </button>
<button <button
@@ -264,9 +277,7 @@ export default function I18nSample() {
</div> </div>
{isFetchingConfig && ( {isFetchingConfig && (
<div style={{ marginBottom: 16, color: '#fbbf24', fontSize: 14 }}> <div style={{ marginBottom: 16, color: '#fbbf24', fontSize: 14 }}> Fetching tenant config...</div>
Fetching tenant config...
</div>
)} )}
{/* Display the localized strings */} {/* Display the localized strings */}
@@ -280,23 +291,33 @@ export default function I18nSample() {
</tr> </tr>
{/* Type-safe keys from the common and booking namespaces */} {/* Type-safe keys from the common and booking namespaces */}
<tr style={{ borderBottom: '1px solid #334155' }}> <tr style={{ borderBottom: '1px solid #334155' }}>
<td style={{ padding: 8 }}><code>booking:module_name</code></td> <td style={{ padding: 8 }}>
<code>booking:module_name</code>
</td>
<td style={{ padding: 8, fontWeight: 'bold' }}>{t('booking:module_name')}</td> <td style={{ padding: 8, fontWeight: 'bold' }}>{t('booking:module_name')}</td>
</tr> </tr>
<tr style={{ borderBottom: '1px solid #334155' }}> <tr style={{ borderBottom: '1px solid #334155' }}>
<td style={{ padding: 8 }}><code>booking:header.title</code></td> <td style={{ padding: 8 }}>
<code>booking:header.title</code>
</td>
<td style={{ padding: 8, fontWeight: 'bold' }}>{t('booking:header.title')}</td> <td style={{ padding: 8, fontWeight: 'bold' }}>{t('booking:header.title')}</td>
</tr> </tr>
<tr style={{ borderBottom: '1px solid #334155' }}> <tr style={{ borderBottom: '1px solid #334155' }}>
<td style={{ padding: 8 }}><code>booking:header.subtitle</code></td> <td style={{ padding: 8 }}>
<code>booking:header.subtitle</code>
</td>
<td style={{ padding: 8, fontWeight: 'bold' }}>{t('booking:header.subtitle')}</td> <td style={{ padding: 8, fontWeight: 'bold' }}>{t('booking:header.subtitle')}</td>
</tr> </tr>
<tr style={{ borderBottom: '1px solid #334155' }}> <tr style={{ borderBottom: '1px solid #334155' }}>
<td style={{ padding: 8 }}><code>booking:select_date</code></td> <td style={{ padding: 8 }}>
<code>booking:select_date</code>
</td>
<td style={{ padding: 8, fontWeight: 'bold' }}>{t('booking:select_date')}</td> <td style={{ padding: 8, fontWeight: 'bold' }}>{t('booking:select_date')}</td>
</tr> </tr>
<tr> <tr>
<td style={{ padding: 8 }}><code>common:save</code></td> <td style={{ padding: 8 }}>
<code>common:save</code>
</td>
<td style={{ padding: 8, fontWeight: 'bold' }}>{t('common:save')}</td> <td style={{ padding: 8, fontWeight: 'bold' }}>{t('common:save')}</td>
</tr> </tr>
</tbody> </tbody>
@@ -19,7 +19,7 @@ const DEMO_USER: DemoUser = { id: 1, user: 'Firman', role: 'admin' };
const DEMO_DRAFT: DemoDraft = { id: 101, type: 'offline_draft', content: 'Draft data saved offline' }; const DEMO_DRAFT: DemoDraft = { id: 101, type: 'offline_draft', content: 'Draft data saved offline' };
const LS_KEY = StorageKey.USER_PROFILE; // Encrypted at rest (in ENCRYPTED_KEYS) const LS_KEY = StorageKey.USER_PROFILE; // Encrypted at rest (in ENCRYPTED_KEYS)
const IDB_KEY = 'offline_draft'; // Plain key for IndexedDB demo const IDB_KEY = 'offline_draft'; // Plain key for IndexedDB demo
// ─── Shared Styles ────────────────────────────────────────────── // ─── Shared Styles ──────────────────────────────────────────────
@@ -212,7 +212,7 @@ export default function StorageSample() {
// ═══════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════
return ( return (
<div style={{ padding: 24, fontFamily: 'monospace', maxWidth: 900 }}> <div style={{ padding: 24, fontFamily: 'monospace' }}>
<h2>🔐 @repo/core-storage Dual Backend CRUD Demo</h2> <h2>🔐 @repo/core-storage Dual Backend CRUD Demo</h2>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24, marginTop: 16 }}> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 24, marginTop: 16 }}>
+12 -1
View File
@@ -19,7 +19,8 @@ import {
Badge, Badge,
Divider, Divider,
} from '@repo/ui/components'; } from '@repo/ui/components';
import PrinterList from './printer-list' import PrinterList from './printer-list';
import ExamplePage from './example/example.page';
interface ShowcaseViewProps { interface ShowcaseViewProps {
colorScheme: ColorSchemeType; colorScheme: ColorSchemeType;
@@ -208,6 +209,16 @@ export default function ShowcaseView({ colorScheme, setColorScheme, density, set
<Card withBorder shadow="sm" radius="md" p="md"> <Card withBorder shadow="sm" radius="md" p="md">
<PrinterList /> <PrinterList />
</Card> </Card>
<Card withBorder shadow="sm" radius="md" p="md">
<Title order={4} mb="md">
Nested Showcase Example
</Title>
<Text>
This is an example of a nested showcase component. You can create multiple layers of showcases to organize
features by domain or complexity.
</Text>
<ExamplePage />
</Card>
</Stack> </Stack>
</Container> </Container>
); );