Overview
The shell owns the Settings hub. Plugins inject panels during register() with AppRegistry.setting.add, and use @quan-erp/base-frontend hooks to load and persist key/value settings (platform keys or plugin-owned keys).
- Register UI with AppRegistry.setting.add({ pluginName, element })
- Load with useSettingQuery({ mode: 'private' | 'public' })
- Persist with useUpdateSettingQuery()
- Prefer existing SettingKeys before inventing new ones
1. Register setting UI
In frontend/src/index.tsx, call AppRegistry.setting.add inside PluginModule.register. Pass pluginName (metadata.name) and a React element for your panel.
- SettingComponents shape: { pluginName: string, element: ReactElement }
- pluginName must equal module.metadata.json name
- Keep the panel self-contained — load/save inside the component, not in register()
1import type { AppRegistryState, PluginModule } from "@quan-erp/shared-types";
2import metadata from "../module.metadata.json" with { type: "json" };
3import { MyPluginSettings } from "./page/setting/my-plugin-settings";
4
5const Plugin: PluginModule = {
6 register(AppRegistry: AppRegistryState) {
7 AppRegistry.setting.add({
8 pluginName: metadata.name,
9 element: <MyPluginSettings />,
10 });
11 },
12};
13
14export default Plugin;2. Build the settings panel
Put panel UI under frontend/src/page/setting/ (or similar). Use base-frontend setting hooks and shared-ui form controls. Gate sensitive controls with the same APIs you use elsewhere (Protected / requiredApis).
1import { useSettingContext, useSettingQuery } from "@quan-erp/base-frontend";
2
3export function MyPluginSettings() {
4 const { data: settings, isLoading } = useSettingQuery({ mode: "private" });
5 const { onUpdateSetting, editedSetting } = useSettingContext();
6
7 if (isLoading) return null;
8
9 return (
10 <div className="flex flex-col gap-4 p-4">
11 <h3>My plugin</h3>
12 {/* Bind controls to settings / editedSetting;
13 call onUpdateSetting(key, value, datatype) on change */}
14 </div>
15 );
16}3. Settings hooks
Import hooks from @quan-erp/base-frontend.
- useSettingQuery({ mode: 'private' | 'public' }, option?) — load setting map
- useUpdateSettingQuery() — persist settings (wrapper uses { many: true })
- useSettingStore — client cache; note double-call pattern useSettingStore()()
- useSettingContext — onUpdateSetting(key, value, datatype, userId?, isPublic?), editedSetting
- useIsContainInBottomNavBar(route) — whether a route is in mobile bottom-nav config
1import {
2 useSettingQuery,
3 useUpdateSettingQuery,
4} from "@quan-erp/base-frontend";
5
6const { data = {} } = useSettingQuery({ mode: "private" });
7const updateSetting = useUpdateSettingQuery();
8
9// Prefer useSettingContext().onUpdateSetting inside the Settings hub.
10// For standalone flows:
11await updateSetting.mutateAsync({ /* SettingMapValue */ });4. Setting keys
Platform SettingKeys include LOCALE, THEME, DATE_FORMAT, TIMEZONE, BUSINESS_NAME, PRIMARY_COLOR, and more. Reuse them when your plugin should follow shell preferences. For plugin-specific config, use a clear namespaced key and a matching SettingDataType.
- Private vs public mode — pick the mode that matches who should read the value
- Do not invent duplicates of LOCALE / THEME / TIMEZONE
- Align datatype with what onUpdateSetting / backend SettingService expect
Critical rules
- Register only in register() via AppRegistry.setting.add
- pluginName must match metadata.name
- element is a ReactElement (JSX), not a component type
- Use base-frontend setting hooks — do not bypass with ad-hoc axios to settings unless following platform APIs
- Protect privileged controls with the same requiredApis pattern as pages
Checklist
- Create page/setting panel component
- AppRegistry.setting.add({ pluginName, element }) in register()
- Wire useSettingQuery / useSettingContext (or useUpdateSettingQuery)
- Reuse platform keys when applicable
- Verify panel appears on Settings after quan-erp watch and install
Common failures
- Panel missing — not registered, wrong pluginName, or plugin not installed
- element type error — passed a component function instead of <Component />
- Settings not loading — wrong mode (private vs public) or hooks used outside settings providers
- Values not saving — onUpdateSetting / mutate not called, or datatype mismatch
- Duplicate platform keys — overwriting LOCALE/THEME unexpectedly from a plugin panel