feat: add showcase example component and integrate booking and storage demos
This commit is contained in:
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
+42
-21
@@ -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>
|
||||||
+1
-1
@@ -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 }}>
|
||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user