Skip to content

Commit 71ec96c

Browse files
committed
feat: Refactor app handling to fetch apps dynamically from API and update metadata structure
1 parent 77926ec commit 71ec96c

3 files changed

Lines changed: 64 additions & 36 deletions

File tree

apps/console/src/App.tsx

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Toaster } from "@/components/ui/toaster"
99
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
1010
import { Database, Layers, Sparkles, Zap } from 'lucide-react';
1111
import { getApiBaseUrl, config } from './lib/config';
12-
import { appPackages, type AppPackage } from './mocks/browser';
12+
import type { AppPackage } from './mocks/browser';
1313

1414
function DashboardWelcome() {
1515
return (
@@ -119,13 +119,14 @@ function DashboardWelcome() {
119119

120120
export default function App() {
121121
const [client, setClient] = useState<ObjectStackClient | null>(null);
122+
const [apps, setApps] = useState<AppPackage[]>([]);
123+
const [selectedApp, setSelectedApp] = useState<AppPackage | null>(null);
122124
const [selectedObject, setSelectedObject] = useState<string | null>(null);
123125
const [editingRecord, setEditingRecord] = useState<any>(null);
124126
const [showForm, setShowForm] = useState(false);
125-
const [selectedApp, setSelectedApp] = useState<AppPackage>(appPackages[0]);
126127

128+
// 1. Create client
127129
useEffect(() => {
128-
// Use the configured API base URL based on runtime mode (MSW or Server)
129130
const baseUrl = getApiBaseUrl();
130131
console.log(`[App] Connecting to API: ${baseUrl} (mode: ${config.mode})`);
131132

@@ -135,6 +136,32 @@ export default function App() {
135136
setClient(newClient);
136137
}, []);
137138

139+
// 2. Fetch app list from the server API (not hardcoded)
140+
useEffect(() => {
141+
if (!client) return;
142+
let mounted = true;
143+
144+
async function loadApps() {
145+
try {
146+
// Spec: GET /api/v1/meta/apps → GetMetaItemsResponse = { type: 'apps', items: AppSchema[] }
147+
const result: any = await client!.meta.getItems('apps');
148+
const items: AppPackage[] = result?.items || result?.value || (Array.isArray(result) ? result : []);
149+
150+
console.log('[App] Fetched apps from API:', items.map((a: any) => a.label || a.name));
151+
152+
if (mounted && items.length > 0) {
153+
setApps(items);
154+
setSelectedApp(items[0]);
155+
}
156+
} catch (err) {
157+
console.error('[App] Failed to fetch apps from API:', err);
158+
}
159+
}
160+
161+
loadApps();
162+
return () => { mounted = false; };
163+
}, [client]);
164+
138165
function handleEdit(record: any) {
139166
setEditingRecord(record);
140167
setShowForm(true);
@@ -167,12 +194,12 @@ export default function App() {
167194
client={client}
168195
selectedObject={selectedObject}
169196
onSelectObject={(name) => setSelectedObject(name || null)}
170-
apps={appPackages}
197+
apps={apps}
171198
selectedApp={selectedApp}
172199
onSelectApp={handleSelectApp}
173200
/>
174201
<main className="flex min-w-0 flex-1 flex-col bg-background">
175-
<SiteHeader selectedObject={selectedObject} appLabel={selectedApp?.label} />
202+
<SiteHeader selectedObject={selectedObject} appLabel={selectedApp?.label || selectedApp?.name} />
176203
<div className="flex flex-1 flex-col overflow-hidden">
177204
{selectedObject ? (
178205
<div className="flex flex-1 flex-col gap-4 p-4">

apps/console/src/components/app-sidebar.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ function getTypeIcon(type: string): LucideIcon {
8787
}
8888

8989
/** Types that are internal / should be hidden from the sidebar */
90-
const HIDDEN_TYPES = new Set(['plugin', 'plugins', 'kind', 'app']);
90+
const HIDDEN_TYPES = new Set(['plugin', 'plugins', 'kind', 'app', 'apps']);
9191

9292
interface AppSidebarProps extends React.ComponentProps<typeof Sidebar> {
9393
client: ObjectStackClient | null;
@@ -177,10 +177,10 @@ export function AppSidebar({ client, selectedObject, onSelectObject, apps, selec
177177
</div>
178178
<div className="flex flex-1 flex-col gap-0.5 leading-none overflow-hidden">
179179
<span className="truncate font-semibold text-sm">
180-
{selectedApp ? selectedApp.label : 'ObjectStack'}
180+
{selectedApp ? (selectedApp.label || selectedApp.name) : 'ObjectStack'}
181181
</span>
182182
<span className="truncate text-xs text-muted-foreground">
183-
{selectedApp ? selectedApp.description : 'Select an app'}
183+
{selectedApp ? (selectedApp.description || 'No description') : 'Loading apps...'}
184184
</span>
185185
</div>
186186
<ChevronsUpDown className="ml-auto h-4 w-4 shrink-0 text-muted-foreground" />
@@ -193,20 +193,20 @@ export function AppSidebar({ client, selectedObject, onSelectObject, apps, selec
193193
const Icon = app.icon ? (APP_ICONS[app.icon] || Package) : Package;
194194
return (
195195
<DropdownMenuItem
196-
key={app.id}
196+
key={app.name}
197197
onClick={() => onSelectApp(app)}
198198
className="gap-2 py-2"
199199
>
200200
<div className="flex h-6 w-6 shrink-0 items-center justify-center rounded bg-primary/10 text-primary">
201201
<Icon className="h-3.5 w-3.5" />
202202
</div>
203203
<div className="flex flex-1 flex-col leading-tight">
204-
<span className="text-sm font-medium">{app.label}</span>
204+
<span className="text-sm font-medium">{app.label || app.name}</span>
205205
{app.description && (
206206
<span className="text-xs text-muted-foreground">{app.description}</span>
207207
)}
208208
</div>
209-
{selectedApp?.id === app.id && (
209+
{selectedApp?.name === app.name && (
210210
<Check className="h-4 w-4 text-primary" />
211211
)}
212212
</DropdownMenuItem>

apps/console/src/mocks/browser.ts

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
*
44
* This creates a complete ObjectStack environment in the browser using the In-Memory Driver
55
* and the MSW Plugin which automatically exposes the API.
6-
* Supports multiple app packages (app-todo, app-crm, etc.)
6+
*
7+
* NOTE: The console does NOT hardcode a list of app packages. The app list is
8+
* fetched dynamically from the server API via `GET /api/v1/meta/apps`.
9+
* The imports here are only used to boot the in-browser kernel (MSW mode).
710
*/
811
import { ObjectKernel } from '@objectstack/runtime';
912
import todoConfig from '@example/app-todo/objectstack.config';
@@ -12,50 +15,48 @@ import { createKernel } from './createKernel';
1215

1316
let kernel: ObjectKernel | null = null;
1417

15-
/** All available app packages */
18+
/**
19+
* AppPackage — derived from the `apps` metadata type (AppSchema in spec).
20+
* All fields come from the server API, NOT from frontend config.
21+
*/
1622
export interface AppPackage {
17-
id: string;
23+
/** Machine name (snake_case), e.g. 'crm_enterprise' */
1824
name: string;
25+
/** Display label, e.g. 'Enterprise CRM' */
1926
label: string;
27+
/** Description */
2028
description?: string;
29+
/** Icon name (Lucide) */
2130
icon?: string;
22-
config: any;
31+
/** Navigation tree */
32+
navigation?: any[];
33+
/** Branding */
34+
branding?: any;
35+
/** Whether app is active */
36+
active?: boolean;
2337
}
2438

2539
function resolveConfig(raw: any) {
2640
return (raw as any).default || raw;
2741
}
2842

29-
export const appPackages: AppPackage[] = [
30-
{
31-
id: 'com.example.todo',
32-
name: 'todo_app',
33-
label: 'Todo App',
34-
description: 'A simple Todo example',
35-
icon: 'check-square',
36-
config: resolveConfig(todoConfig),
37-
},
38-
{
39-
id: 'com.example.crm',
40-
name: 'crm_app',
41-
label: 'Enterprise CRM',
42-
description: 'Comprehensive enterprise CRM',
43-
icon: 'briefcase',
44-
config: resolveConfig(crmConfig),
45-
},
43+
/**
44+
* App configs used ONLY for kernel bootstrapping in MSW (browser) mode.
45+
* The UI never reads this directly — it discovers apps via the meta API.
46+
*/
47+
const bootConfigs = [
48+
resolveConfig(todoConfig),
49+
resolveConfig(crmConfig),
4650
];
4751

4852
export async function startMockServer() {
4953
if (kernel) return;
5054

5155
console.log('[MSW] Starting ObjectStack Runtime (Browser Mode)...');
52-
console.log('[MSW] Loading apps:', appPackages.map(a => a.label));
53-
54-
const appConfigs = appPackages.map(a => a.config);
5556

5657
// Use shared factory with multi-app support
5758
kernel = await createKernel({
58-
appConfigs,
59+
appConfigs: bootConfigs,
5960
enableBrowser: true
6061
});
6162

0 commit comments

Comments
 (0)