// @stago/sdk - Module Hooks // React hooks for module components import { useEffect, useCallback, useState } from 'react'; import { useModule, ModuleContext } from '../module-context'; import type { HttpOptions, HttpResponseKind } from '../http-client.js'; import { normalizeModuleApiPath, type ModuleApiResult } from '../module-api-path.js'; // Re-export useModule as main hook export { useModule } from '../module-context'; /** * Get user information only */ export function useUser() { const { user } = useModule(); return user; } /** * Get theme information and controls */ export function useTheme() { const { theme } = useModule(); return theme; } /** * Get router service */ export function useRouter() { const { router } = useModule(); return router; } /** * Get notification service */ export function useNotifications() { const { notifications } = useModule(); return notifications; } /** * Get global state for cross-module communication */ export function useGlobalState() { const { globalState } = useModule(); return globalState; } /** * Subscribe to a global state key * Returns current value and setter */ export function useGlobalStateValue( key: string, defaultValue?: T, ): [T | undefined, (value: T) => void] { const { globalState } = useModule(); const [value, setValue] = useState(() => globalState.get(key) ?? defaultValue); useEffect(() => { // Subscribe to changes const unsubscribe = globalState.subscribe(key, newValue => { setValue(newValue as T); }); return unsubscribe; }, [globalState, key]); const setGlobalValue = useCallback( (newValue: T) => { globalState.set(key, newValue); setValue(newValue); }, [globalState, key], ); return [value, setGlobalValue]; } /** * Get platform HTTP client (internal). * Module UI should call {@link useModuleRequest} instead of using this directly. */ function useHttp() { const { platform } = useModule(); return platform.http; } /** * Get module storage */ export function useStorage() { const { platform } = useModule(); return platform.storage; } /** * Get platform config */ export function useConfig() { const { platform } = useModule(); return platform.config; } export interface ModuleRequestInit { method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'; body?: unknown; query?: HttpOptions['query']; headers?: Record; timeout?: number; signal?: AbortSignal; skipAuth?: boolean; contentType?: string; /** * Default `json`: unwrap `ModuleApiResult{ success, data, error }`. * Non-json kinds return the raw parsed body (Blob / text / Response). */ responseKind?: HttpResponseKind; } function toHttpOptions(init?: ModuleRequestInit): HttpOptions | undefined { if (!init) return undefined; const { method: _method, body: _body, ...rest } = init; return rest; } /** * Module-facing API client — prefer this over `platform.http` / raw fetch. * * Pass business paths only (e.g. `/sprints`); the Host prepends `config.apiBaseUrl`. * JSON responses are unwrapped from `ModuleApiResult{ success, data, error }`. * Use `responseKind: 'blob' | 'response' | 'text'` for uploads/downloads (no unwrap). */ export function useModuleRequest() { const http = useHttp(); const request = useCallback( async (path: string, init?: ModuleRequestInit): Promise => { const url = normalizeModuleApiPath(path); const method = init?.method ?? 'GET'; const options = toHttpOptions(init); const kind = init?.responseKind ?? 'json'; let raw: unknown; switch (method) { case 'POST': raw = await http.post(url, init?.body, options); break; case 'PUT': raw = await http.put(url, init?.body, options); break; case 'PATCH': raw = await http.patch(url, init?.body, options); break; case 'DELETE': raw = await http.delete(url, options); break; case 'GET': default: raw = await http.get(url, options); break; } // Upload/download and other non-envelope payloads skip ModuleApiResult unwrap. if (kind !== 'json') { return raw as T; } const res = raw as ModuleApiResult; if (!res?.success) { throw new Error(res?.error?.message ?? '请求失败'); } return res.data as T; }, [http], ); return { request }; }