Files
registry/ts_web/router.ts

156 lines
4.0 KiB
TypeScript
Raw Normal View History

import * as plugins from './plugins.js';
import * as appstate from './appstate.js';
const SmartRouter = plugins.domtools.plugins.smartrouter.SmartRouter;
export const validViews = [
'dashboard',
'organizations',
'packages',
'tokens',
'settings',
'admin',
] as const;
export type TValidView = typeof validViews[number];
class AppRouter {
private router: InstanceType<typeof SmartRouter>;
private initialized = false;
private suppressStateUpdate = false;
constructor() {
this.router = new SmartRouter({ debug: false });
}
public init(): void {
if (this.initialized) return;
this.setupRoutes();
this.setupStateSync();
this.handleInitialRoute();
this.initialized = true;
}
private setupRoutes(): void {
for (const view of validViews) {
this.router.on(`/${view}`, async () => {
this.updateViewState(view);
});
}
// Root redirect
this.router.on('/', async () => {
this.navigateTo('/dashboard');
});
}
private setupStateSync(): void {
appstate.uiStatePart.select((s) => s.activeView).subscribe((activeView) => {
if (this.suppressStateUpdate) return;
const currentPath = window.location.pathname;
const expectedPath = `/${activeView}`;
if (currentPath !== expectedPath) {
this.suppressStateUpdate = true;
this.router.pushUrl(expectedPath);
this.suppressStateUpdate = false;
}
});
}
private handleInitialRoute(): void {
const path = window.location.pathname;
// Handle OAuth callback
if (path === '/oauth-callback') {
this.handleOAuthCallback();
return;
}
if (!path || path === '/') {
this.router.pushUrl('/dashboard');
} else {
const segments = path.split('/').filter(Boolean);
const view = segments[0];
if (validViews.includes(view as TValidView)) {
this.updateViewState(view as TValidView);
// If there's a sub-path, store the entity ID
if (segments[1]) {
const currentState = appstate.uiStatePart.getState();
appstate.uiStatePart.setState({
...currentState,
activeEntityId: segments[1],
});
}
} else {
this.router.pushUrl('/dashboard');
}
}
}
private handleOAuthCallback(): void {
const params = new URLSearchParams(window.location.search);
const accessToken = params.get('accessToken');
const refreshToken = params.get('refreshToken');
const sessionId = params.get('sessionId');
if (accessToken && refreshToken && sessionId) {
// Store tokens and redirect to dashboard
// The app shell will pick up the identity from loginStatePart
appstate.handleOAuthCallback(accessToken, refreshToken, sessionId);
}
// Redirect to dashboard
this.navigateTo('/dashboard');
}
private updateViewState(view: string): void {
this.suppressStateUpdate = true;
const currentState = appstate.uiStatePart.getState();
if (currentState.activeView !== view) {
appstate.uiStatePart.setState({
...currentState,
activeView: view,
activeEntityId: undefined,
});
}
this.suppressStateUpdate = false;
}
public navigateTo(path: string): void {
this.router.pushUrl(path);
}
public navigateToView(view: string): void {
const normalized = view.toLowerCase().replace(/\s+/g, '-');
if (validViews.includes(normalized as TValidView)) {
this.navigateTo(`/${normalized}`);
} else {
this.navigateTo('/dashboard');
}
}
public navigateToEntity(view: string, entityId: string): void {
const currentState = appstate.uiStatePart.getState();
appstate.uiStatePart.setState({
...currentState,
activeView: view,
activeEntityId: entityId,
});
this.router.pushUrl(`/${view}/${entityId}`);
}
public getCurrentView(): string {
return appstate.uiStatePart.getState().activeView;
}
public destroy(): void {
this.router.destroy();
this.initialized = false;
}
}
export const appRouter = new AppRouter();