velxio/frontend/src/lib/proRoutes.ts

53 lines
1.7 KiB
TypeScript
Raw Normal View History

refactor(oss-split): remove auth/admin/profile frontend from OSS Phase 3 of the OSS / pro split — frontend side. Phase 2 already moved the auth/DB stack out of the OSS backend; this commit does the same for the React app. After this, the OSS image is editor + simulator + landing + docs only. What moved to the private overlay (pro/frontend/src/pro/): pages/{Login,Register,ForgotPassword,ResetPassword}Page.tsx pages/{Admin,UserProfile,Project,ProjectById}Page.tsx components/admin/{AdminBoardsTab,AdminDashboardTab,UserActivityModal}.tsx components/layout/{SaveProjectModal,LoginPromptModal}.tsx services/{authService,adminService}.ts store/useAuthStore.ts hooks/autoSaveImpl.ts New seams added so OSS components stay decoupled: * lib/proRoutes.ts — registerProRoutes()/useProRoutes() via useSyncExternalStore. mountPro() injects the moved pages at runtime; App.tsx subscribes to the registry, so registration after the initial render re-renders without a Not-Found flash. * lib/proSession.ts — registerSessionCheck()/triggerSessionCheck(). App.tsx fires this on mount instead of useAuthStore.checkSession(); pure OSS no-ops. * lib/proSaveAction.ts — installSaveActionImpl()/triggerSaveAction(). EditorPage's Save button dispatches through this; the overlay decides whether to show SaveProjectModal or LoginPromptModal based on auth state. In OSS without an overlay it's a no-op today; in Phase 4 of the split it becomes the .vlx Export entry point. OSS-side rewrites: * App.tsx drops the 8 page imports + 8 route entries; uses triggerSessionCheck() instead of useAuthStore directly. * AppHeader.tsx drops the user/login/register block entirely. The header-auth slot (introduced in Phase 1) now stays empty in OSS and gets filled by the overlay's portal mount. * EditorPage.tsx drops useAuthStore + SaveProjectModal + LoginPromptModal imports. The Save handler is now triggerSaveAction(). * LandingPage.tsx drops the dead UserMenu component (defined but never rendered) + its useAuthStore imports. * main.tsx drops the side-effect import of hooks/autoSaveImpl — the impl lives in pro now and self-registers via mountPro(). Build config: * vite.config.ts adds @velxio alias → src/. Lets the overlay import upstream modules (lib/proRoutes etc.) by stable name regardless of whether it's symlinked (local dev) or COPYed (Docker). * preserveSymlinks now gated on VITE_PRO_BUILD only (not on serve mode). Needed so Rollup keeps the overlay logically inside src/pro/ during local junction-based builds. Build verification: * OSS-only: 20-ish routes, no /login, /admin, /:username — 285 SEO pages prerendered. Bundle drops ~80-120 KB. * OSS + overlay: full 38 routes (30 upstream + 8 from registerProRoutes), HeaderAuth dropdown injected via slot, save action wired to the overlay's modal flow. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 01:31:12 +07:00
/**
* Pro-route registry.
*
* App.tsx defines the OSS route table at module load. Routes that only
* make sense in a private deployment (login, register, admin, user
* profile, project-by-slug, etc.) are registered separately by the pro
* overlay's mountPro() and merged into the route tree via this module.
*
* Subscription is `useSyncExternalStore`-based so any registration that
* happens AFTER the initial App render still produces a synchronous
* re-render the user never sees a "Not Found" flash for routes the
* overlay was about to add. In practice the dynamic import of
* `@pro/index` resolves before the user can navigate, but the contract
* guarantees correctness even if it didn't.
*
* Calling registerProRoutes() more than once REPLACES the previous set
* (idempotent pro can re-register on hot reload without leaking
* duplicate entries). Pass [] to clear.
*/
import { useSyncExternalStore, type ReactElement } from 'react';
export interface ProRoute {
/** Route path WITHOUT leading slash, matching App.tsx's ROUTES convention. */
path: string;
element: ReactElement;
/** True if this is the locale root (path === '' for /<locale>/). */
index?: boolean;
}
let _routes: ProRoute[] = [];
const _listeners = new Set<() => void>();
export function registerProRoutes(routes: ProRoute[]): void {
_routes = routes;
for (const listener of _listeners) listener();
}
function subscribe(listener: () => void): () => void {
_listeners.add(listener);
return () => {
_listeners.delete(listener);
};
}
function getSnapshot(): ProRoute[] {
return _routes;
}
export function useProRoutes(): ProRoute[] {
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
}