@xmachines/play-tanstack-react-router 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.oxfmtrc.json +3 -0
- package/.oxlintrc.json +3 -0
- package/README.md +177 -0
- package/dist/extract-params.d.ts +45 -0
- package/dist/extract-params.d.ts.map +1 -0
- package/dist/extract-params.js +70 -0
- package/dist/extract-params.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/play-router-provider.d.ts +33 -0
- package/dist/play-router-provider.d.ts.map +1 -0
- package/dist/play-router-provider.js +31 -0
- package/dist/play-router-provider.js.map +1 -0
- package/dist/route-map.d.ts +101 -0
- package/dist/route-map.d.ts.map +1 -0
- package/dist/route-map.js +139 -0
- package/dist/route-map.js.map +1 -0
- package/dist/tanstack-router-bridge.d.ts +115 -0
- package/dist/tanstack-router-bridge.d.ts.map +1 -0
- package/dist/tanstack-router-bridge.js +112 -0
- package/dist/tanstack-router-bridge.js.map +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +9 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +10 -0
- package/dist/utils.js.map +1 -0
- package/examples/demo/README.md +100 -0
- package/examples/demo/docs/ARCHITECTURE.md +643 -0
- package/examples/demo/docs/INVARIANTS.md +461 -0
- package/examples/demo/docs/SWAP-REACT.md +635 -0
- package/examples/demo/index.html +16 -0
- package/examples/demo/package.json +39 -0
- package/examples/demo/src/App.tsx +148 -0
- package/examples/demo/src/components/About.tsx +49 -0
- package/examples/demo/src/components/Contact.tsx +43 -0
- package/examples/demo/src/components/Dashboard.tsx +46 -0
- package/examples/demo/src/components/DebugPanel.tsx +68 -0
- package/examples/demo/src/components/HeaderNav.tsx +103 -0
- package/examples/demo/src/components/Home.tsx +41 -0
- package/examples/demo/src/components/Login.tsx +82 -0
- package/examples/demo/src/components/Navigation.tsx +262 -0
- package/examples/demo/src/components/Profile.tsx +46 -0
- package/examples/demo/src/components/Register.tsx +109 -0
- package/examples/demo/src/components/Settings.tsx +92 -0
- package/examples/demo/src/components/index.ts +16 -0
- package/examples/demo/src/main.tsx +20 -0
- package/examples/demo/test/actor-authority.test.ts +50 -0
- package/examples/demo/test/browser/__screenshots__/back-button-duplicate.browser.test.tsx/Browser-back-button-navigates-through-unique-history--no-duplicates--1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-button-duplicate.browser.test.tsx/GAP-12--navigation-via-goto---events-creates-single-history-entries-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-button-duplicate.browser.test.tsx/GAP-12--navigation-via-goto---events-creates-single-history-entries-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--After-authentication-flow-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Multiple-rapid-navigations-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Multiple-rapid-navigations-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--URL-stays-in-sync-with-actor-state-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--URL-stays-in-sync-with-actor-state-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Works-correctly-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/back-forward-sync.browser.test.tsx/Back-Forward--Works-correctly-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to--about-loads-about-page-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to--contact-loads-contact-page-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to--home-loads-home-page-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to-protected-route-while-authenticated-loads-dashboard-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/direct-navigation.browser.test.ts/Direct-navigation-to-protected-route-while-unauthenticated-redirects-to-login-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/Debug--Print-history-after-each-navigation-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/Debug--Print-history-after-each-navigation-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/EXACT-USER-SCENARIO--home---about---home---contact---home--then-back-3x-should-land-on-about-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/exact-user-scenario.browser.test.tsx/EXACT-USER-SCENARIO--home---about---home---contact---home--then-back-3x-should-land-on-about-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Actor-Authority---infrastructure-cannot-override-guards-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Actor-Authority---infrastructure-cannot-override-guards-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Guards-reject-invalid-navigation-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/guard-rejection.browser.test.tsx/E2E--Guards-reject-invalid-navigation-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-investigation.browser.test.tsx/baseHistory-back---navigation--avoiding-window-history--1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-investigation.browser.test.tsx/baseHistory-back---navigation--avoiding-window-history--2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---authenticated-user-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---authenticated-user-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---unauthenticated-user-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-forward-with-guard-transitions---unauthenticated-user-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---authenticated-user-navigates-back-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---authenticated-user-navigates-back-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---unauthenticated-user-stays-on-public-routes-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Back-with-guard---unauthenticated-user-stays-on-public-routes-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back---unique-history-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back---unique-history-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Forward-button-after-back-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Navigate-forward-then-back---unique-history-entries-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Navigate-forward-then-back---unique-history-entries-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Rapid-back-forward-navigation-doesn-t-cause-duplicate-entries-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Rapid-back-forward-navigation-doesn-t-cause-duplicate-entries-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---about-to-home-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---about-to-home-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---contact-to-about-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--Single-back-navigation---contact-to-about-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-forward-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-forward-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-navigation-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/history-navigation.browser.test.ts/GAP-12--View-syncs-with-URL-after-back-navigation-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/login-flow.browser.test.tsx/E2E--User-can-log-in-and-see-dashboard-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/login-flow.browser.test.tsx/E2E--User-can-log-in-and-see-dashboard-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/navigation.browser.test.tsx/E2E--Navigation-reflects-actor-state-transitions-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/navigation.browser.test.tsx/E2E--Navigation-reflects-actor-state-transitions-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-forward-through-multiple-protected-routes-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-forward-through-multiple-protected-routes-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-navigates-from-dashboard-to-settings--protected-route--1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/Browser-back-navigates-from-dashboard-to-settings--protected-route--2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/RED--Browser-back-forward-through-multiple-protected-routes-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/RED--Browser-back-from-dashboard-to-settings--protected-route--1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/protected-route-navigation.browser.test.tsx/RED--Browser-back-navigates-from-dashboard-to-settings--protected-route--1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--account--section-when-navigating-to--settings-account-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--account--section-when-navigating-to--settings-account-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--general--section-when-navigating-to--settings--no-parameter--1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--general--section-when-navigating-to--settings--no-parameter--2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--profile--section-when-navigating-to--settings-profile-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-display--profile--section-when-navigating-to--settings-profile-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-update-section-display-when-clicking-section-navigation-buttons-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-parameter.browser.test.tsx/Settings-Parameter-Display-should-update-section-display-when-clicking-section-navigation-buttons-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-query-freeze.browser.test.ts/Settings-with-query-parameters-works-correctly-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/settings-query-freeze.browser.test.ts/Settings-with-section-parameter-works-correctly-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04--State-Driven-Reset---Browser-back-sends-event-to-actor-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04--State-Driven-Reset---Browser-back-sends-event-to-actor-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04b--Browser-navigation-with-SignalSyncedHistory-integration-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/state-driven.browser.test.ts/DEMO-04b--Browser-navigation-with-SignalSyncedHistory-integration-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/tanstack-integration.browser.test.tsx/TanStack-Router-Integration-renders-with-RouterProvider-context-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/tanstack-integration.browser.test.tsx/TanStack-Router-Integration-renders-with-RouterProvider-context-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/test-multiple-back.browser.test.tsx/Multiple-back--Navigate-forward-3x-then-back-3x-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-1--Opening-with--someinvalidstate-stays-at-current-state-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-2--Opening-with--about-renders-About-component--not-Login-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-2b--Opening-with--home-renders-Home-component--not-Login-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-2c--Opening-with--contact-renders-Contact-component--not-Login-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-3--Back-forward-navigation---rendering-syncs-with-URL-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-3--Back-forward-navigation---rendering-syncs-with-URL-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-4--Auth-state-preserved-when-navigating-between-authenticated-anonymous-states-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-4--Auth-state-preserved-when-navigating-between-authenticated-anonymous-states-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-4b--Browser-back-forward-preserves-auth-state-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-4b--Browser-back-forward-preserves-auth-state-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-5--Protected-route-with-play-route-respects-authentication-guard-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/uat-xstate-route-regression.browser.test.ts/UAT-5--Protected-route-with-play-route-respects-authentication-guard-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/user-reported-scenario.browser.test.tsx/User-scenario--home---about---home---contact---home--then-back-3x-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/user-reported-scenario.browser.test.tsx/User-scenario--login---home---about---home---contact---home--then-back-3x-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Browser-back-button-sends-play-route-event-with-correct-state-ID-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Browser-back-button-sends-play-route-event-with-correct-state-ID-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Browser-back-button-sends-xstate-route-event-with-correct-state-ID-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Direct-URL-navigation-sends-play-route-event-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Direct-URL-navigation-sends-xstate-route-event-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Forward-button-sends-play-route-event-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Forward-button-sends-play-route-event-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Forward-button-sends-xstate-route-event-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/GAP-12-fix-preserved--No-duplicate-history-entries-with-play-route-1.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/GAP-12-fix-preserved--No-duplicate-history-entries-with-play-route-2.png +0 -0
- package/examples/demo/test/browser/__screenshots__/xstate-route-events.browser.test.ts/Protected-route-sends-xstate-route-with-authentication-guard-1.png +0 -0
- package/examples/demo/test/browser/back-button-duplicate.browser.test.tsx +148 -0
- package/examples/demo/test/browser/back-forward-sync.browser.test.tsx +149 -0
- package/examples/demo/test/browser/direct-navigation.browser.test.ts +146 -0
- package/examples/demo/test/browser/exact-user-scenario.browser.test.tsx +207 -0
- package/examples/demo/test/browser/guard-rejection.browser.test.tsx +52 -0
- package/examples/demo/test/browser/history-investigation.browser.test.tsx +82 -0
- package/examples/demo/test/browser/history-navigation.browser.test.ts +351 -0
- package/examples/demo/test/browser/login-flow.browser.test.tsx +34 -0
- package/examples/demo/test/browser/navigation.browser.test.tsx +34 -0
- package/examples/demo/test/browser/protected-route-navigation.browser.test.tsx +161 -0
- package/examples/demo/test/browser/redirect-url-update.browser.test.tsx +140 -0
- package/examples/demo/test/browser/settings-parameter.browser.test.tsx +164 -0
- package/examples/demo/test/browser/settings-query-freeze.browser.test.ts +141 -0
- package/examples/demo/test/browser/state-driven.browser.test.ts +112 -0
- package/examples/demo/test/browser/tanstack-integration.browser.test.tsx +61 -0
- package/examples/demo/test/browser/uat-xstate-route-regression.browser.test.ts +58 -0
- package/examples/demo/test/browser/xstate-route-events.browser.test.ts +293 -0
- package/examples/demo/test/browser-back-view-rendering.test.ts +104 -0
- package/examples/demo/test/browser-e2e/auth-flow.browser.test.tsx +49 -0
- package/examples/demo/test/invalid-route-redirect.test.ts +40 -0
- package/examples/demo/test/passive-infra.test.ts +35 -0
- package/examples/demo/test/route-parameters.test.ts +539 -0
- package/examples/demo/test/signal-only.test.ts +54 -0
- package/examples/demo/test/strict-separation.test.ts +37 -0
- package/examples/demo/test/test-utils.ts +49 -0
- package/examples/demo/tsconfig.json +21 -0
- package/examples/demo/tsconfig.tsbuildinfo +1 -0
- package/examples/demo/vite.config.ts +13 -0
- package/examples/demo/vitest.browser.config.ts +72 -0
- package/examples/demo/vitest.config.e2e.browser.ts +28 -0
- package/examples/demo/vitest.config.ts +35 -0
- package/package.json +51 -0
- package/src/extract-params.ts +75 -0
- package/src/index.ts +31 -0
- package/src/play-router-provider.tsx +46 -0
- package/src/route-map.ts +158 -0
- package/src/tanstack-router-bridge.ts +135 -0
- package/src/types.ts +26 -0
- package/src/utils.ts +12 -0
- package/test/browser/__screenshots__/signal-synced-history.browser.test.ts/Browser-back-button-sends-route-navigate-event-to-actor-1.png +0 -0
- package/test/browser/__screenshots__/signal-synced-history.browser.test.ts/SignalSyncedHistory-prevents-circular-updates-1.png +0 -0
- package/test/browser/__screenshots__/signal-synced-history.browser.test.ts/SignalSyncedHistory-syncs-actor-route-to-browser-URL-1.png +0 -0
- package/test/browser/signal-synced-history.browser.test.ts +95 -0
- package/test/route-map.test.ts +107 -0
- package/test/tanstack-router-bridge.test.ts +318 -0
- package/test/urlpattern-integration.test.ts +145 -0
- package/tsconfig.json +16 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +35 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main Application - Integration Layer
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates complete Universal Player Architecture integration:
|
|
5
|
+
* - Actor Authority: Auth machine guards control navigation
|
|
6
|
+
* - Passive Infrastructure: Router observes actor.currentRoute signal
|
|
7
|
+
* - Signal-Only Reactivity: PlayRenderer watches signals for UI updates
|
|
8
|
+
* - Strict Separation: Business logic (machines/) has zero React imports
|
|
9
|
+
* - State-Driven Reset: Browser navigation sends events to actor
|
|
10
|
+
*
|
|
11
|
+
* This layer wires together business logic (auth-machine), infrastructure
|
|
12
|
+
* (TanStack Router), and view (React components) while maintaining strict
|
|
13
|
+
* separation of concerns.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import "@xmachines/play-router-shared/index.css";
|
|
17
|
+
|
|
18
|
+
import { useMemo, useEffect } from "react";
|
|
19
|
+
import { createRouter, createRootRoute } from "@tanstack/react-router";
|
|
20
|
+
import type { AbstractActor, Routable, Viewable } from "@xmachines/play-actor";
|
|
21
|
+
import { PlayRouterProvider } from "@xmachines/play-tanstack-react-router";
|
|
22
|
+
import { PlayRenderer } from "@xmachines/play-react";
|
|
23
|
+
import { extractMachineRoutes } from "@xmachines/play-router";
|
|
24
|
+
import { createRouteMapFromTree } from "@xmachines/play-tanstack-react-router";
|
|
25
|
+
import { definePlayer } from "@xmachines/play-xstate";
|
|
26
|
+
import { defineComponents } from "@xmachines/play-catalog";
|
|
27
|
+
import type { AnyActorLogic } from "xstate";
|
|
28
|
+
import { authMachine, catalog } from "@xmachines/play-router-shared";
|
|
29
|
+
import { Login } from "./components/Login.js";
|
|
30
|
+
import { Dashboard } from "./components/Dashboard.js";
|
|
31
|
+
import { Home } from "./components/Home.js";
|
|
32
|
+
import { About } from "./components/About.js";
|
|
33
|
+
import { Contact } from "./components/Contact.js";
|
|
34
|
+
import { Settings } from "./components/Settings.js";
|
|
35
|
+
import { Profile } from "./components/Profile.js";
|
|
36
|
+
import { Register } from "./components/Register.js";
|
|
37
|
+
import { Navigation } from "./components/Navigation.js";
|
|
38
|
+
import { DebugPanel } from "./components/DebugPanel.js";
|
|
39
|
+
import { HeaderNav } from "./components/HeaderNav.js";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Define React components matching catalog schema
|
|
43
|
+
*
|
|
44
|
+
* Type safety: defineComponents() ensures component props match catalog schemas.
|
|
45
|
+
* This enforces the contract between business logic (catalog) and view layer (React).
|
|
46
|
+
*/
|
|
47
|
+
const components = defineComponents(catalog, {
|
|
48
|
+
Login,
|
|
49
|
+
Dashboard,
|
|
50
|
+
Home,
|
|
51
|
+
About,
|
|
52
|
+
Contact,
|
|
53
|
+
Settings,
|
|
54
|
+
Profile,
|
|
55
|
+
Register,
|
|
56
|
+
Navigation,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
function Shell({
|
|
60
|
+
actor,
|
|
61
|
+
router,
|
|
62
|
+
}: {
|
|
63
|
+
actor: AbstractActor<AnyActorLogic> & Routable & Viewable;
|
|
64
|
+
router: unknown;
|
|
65
|
+
}) {
|
|
66
|
+
void router;
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div className="demo-app">
|
|
70
|
+
<header className="demo-header">
|
|
71
|
+
<h1 className="demo-title">XMachines Play - React Demo</h1>
|
|
72
|
+
<HeaderNav actor={actor} />
|
|
73
|
+
</header>
|
|
74
|
+
|
|
75
|
+
<main className="demo-content">
|
|
76
|
+
<PlayRenderer actor={actor} components={components} />
|
|
77
|
+
</main>
|
|
78
|
+
|
|
79
|
+
<DebugPanel actor={actor} />
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Main App component
|
|
86
|
+
*
|
|
87
|
+
* Architecture:
|
|
88
|
+
* 1. Creates actor instance from player factory
|
|
89
|
+
* 2. Creates route map for path → state ID resolution
|
|
90
|
+
* 3. Creates TanStack Router
|
|
91
|
+
* 4. Uses PlayRouterProvider with renderer prop pattern
|
|
92
|
+
*
|
|
93
|
+
* Lifecycle:
|
|
94
|
+
* - Mount: actor.start() → begin state machine execution
|
|
95
|
+
* - Runtime: PlayRenderer observes actor.currentView signal
|
|
96
|
+
* - Runtime: Provider subscribes to router changes
|
|
97
|
+
* - Unmount: actor.stop() → halt machine
|
|
98
|
+
*
|
|
99
|
+
* Actor Authority: Auth machine guards decide navigation validity.
|
|
100
|
+
* Passive Infrastructure: Router reflects actor state, doesn't control it.
|
|
101
|
+
*/
|
|
102
|
+
export function App() {
|
|
103
|
+
// 1. Create actor
|
|
104
|
+
const actor = useMemo(() => {
|
|
105
|
+
const createPlayer = definePlayer({ machine: authMachine, catalog });
|
|
106
|
+
const actorInstance = createPlayer();
|
|
107
|
+
actorInstance.start();
|
|
108
|
+
return actorInstance;
|
|
109
|
+
}, []);
|
|
110
|
+
|
|
111
|
+
// 2. Create route map for path → state ID resolution
|
|
112
|
+
const routeMap = useMemo(() => {
|
|
113
|
+
const routeTree = extractMachineRoutes(authMachine);
|
|
114
|
+
return createRouteMapFromTree(routeTree);
|
|
115
|
+
}, []);
|
|
116
|
+
|
|
117
|
+
// 3. Create TanStack Router (using TanStack's API)
|
|
118
|
+
const router = useMemo(() => {
|
|
119
|
+
// TanStack Router manages its own route configuration
|
|
120
|
+
// Note: In real app, you'd configure TanStack routes separately
|
|
121
|
+
const rootRoute = createRootRoute();
|
|
122
|
+
return createRouter({
|
|
123
|
+
routeTree: rootRoute,
|
|
124
|
+
defaultPreload: "intent",
|
|
125
|
+
});
|
|
126
|
+
}, []);
|
|
127
|
+
|
|
128
|
+
// 4. Cleanup on unmount
|
|
129
|
+
useEffect(() => {
|
|
130
|
+
return () => {
|
|
131
|
+
actor.stop();
|
|
132
|
+
};
|
|
133
|
+
}, [actor]);
|
|
134
|
+
|
|
135
|
+
// 5. Use PlayRouterProvider with renderer prop and structured layout
|
|
136
|
+
return (
|
|
137
|
+
<PlayRouterProvider
|
|
138
|
+
actor={actor}
|
|
139
|
+
router={router}
|
|
140
|
+
routeMap={routeMap}
|
|
141
|
+
renderer={(currentActor, currentRouter) => (
|
|
142
|
+
<Shell actor={currentActor} router={currentRouter} />
|
|
143
|
+
)}
|
|
144
|
+
/>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export default App;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* About - Public information page
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates public route with content display.
|
|
5
|
+
*
|
|
6
|
+
* @invariant Passive Infrastructure - Forwards events to actor, doesn't control navigation
|
|
7
|
+
* @invariant Strict Separation - Zero router imports
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { InferComponentProps } from "@xmachines/play-catalog";
|
|
11
|
+
import type { catalog } from "@xmachines/play-router-shared";
|
|
12
|
+
|
|
13
|
+
type AboutProps = InferComponentProps<typeof catalog, "About"> & {
|
|
14
|
+
send: (event: any) => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function About({ title, send }: AboutProps) {
|
|
18
|
+
return (
|
|
19
|
+
<div className="about" style={{ maxWidth: "800px", margin: "50px auto", padding: "20px" }}>
|
|
20
|
+
<h1>{title}</h1>
|
|
21
|
+
<p style={{ fontSize: "16px", lineHeight: "1.6", marginBottom: "15px" }}>
|
|
22
|
+
This is a demonstration of the XMachines Play Universal Player Architecture.
|
|
23
|
+
</p>
|
|
24
|
+
<p style={{ fontSize: "16px", lineHeight: "1.6", marginBottom: "15px" }}>
|
|
25
|
+
Features demonstrated:
|
|
26
|
+
</p>
|
|
27
|
+
<ul style={{ fontSize: "16px", lineHeight: "1.6", marginBottom: "30px" }}>
|
|
28
|
+
<li>State machine-driven navigation</li>
|
|
29
|
+
<li>Protected routes with guards</li>
|
|
30
|
+
<li>Framework-agnostic business logic</li>
|
|
31
|
+
<li>TC39 Signals for reactivity</li>
|
|
32
|
+
</ul>
|
|
33
|
+
<button
|
|
34
|
+
onClick={() => send({ type: "play.route", to: "#home" })}
|
|
35
|
+
style={{
|
|
36
|
+
padding: "10px 20px",
|
|
37
|
+
fontSize: "16px",
|
|
38
|
+
backgroundColor: "#007bff",
|
|
39
|
+
color: "white",
|
|
40
|
+
border: "none",
|
|
41
|
+
borderRadius: "4px",
|
|
42
|
+
cursor: "pointer",
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
Back to Home
|
|
46
|
+
</button>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contact - Public contact form
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates public route with user interaction.
|
|
5
|
+
*
|
|
6
|
+
* @invariant Passive Infrastructure - Forwards events to actor, doesn't control navigation
|
|
7
|
+
* @invariant Strict Separation - Zero router imports
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { InferComponentProps } from "@xmachines/play-catalog";
|
|
11
|
+
import type { catalog } from "@xmachines/play-router-shared";
|
|
12
|
+
|
|
13
|
+
type ContactProps = InferComponentProps<typeof catalog, "Contact"> & {
|
|
14
|
+
send: (event: any) => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function Contact({ title, send }: ContactProps) {
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
className="contact"
|
|
21
|
+
style={{ maxWidth: "800px", margin: "50px auto", padding: "20px" }}
|
|
22
|
+
>
|
|
23
|
+
<h1>{title}</h1>
|
|
24
|
+
<p style={{ fontSize: "16px", lineHeight: "1.6", marginBottom: "15px" }}>
|
|
25
|
+
For support, please reach out.
|
|
26
|
+
</p>
|
|
27
|
+
<button
|
|
28
|
+
onClick={() => send({ type: "play.route", to: "#home" })}
|
|
29
|
+
style={{
|
|
30
|
+
padding: "10px 20px",
|
|
31
|
+
fontSize: "16px",
|
|
32
|
+
backgroundColor: "#007bff",
|
|
33
|
+
color: "white",
|
|
34
|
+
border: "none",
|
|
35
|
+
borderRadius: "4px",
|
|
36
|
+
cursor: "pointer",
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
Back to Home
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dashboard Component - Passive Infrastructure Layer
|
|
3
|
+
*
|
|
4
|
+
* Simplified dashboard for unified auth machine demo.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React from "react";
|
|
8
|
+
|
|
9
|
+
import type { InferComponentProps } from "@xmachines/play-catalog";
|
|
10
|
+
import type { catalog } from "@xmachines/play-router-shared";
|
|
11
|
+
|
|
12
|
+
type DashboardProps = InferComponentProps<typeof catalog, "Dashboard"> & {
|
|
13
|
+
send: (event: any) => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const Dashboard: React.FC<DashboardProps> = ({ send, welcome }) => {
|
|
17
|
+
return (
|
|
18
|
+
<div style={{ maxWidth: "800px", margin: "50px auto", padding: "20px" }}>
|
|
19
|
+
{welcome && <h1 style={{ marginBottom: "20px" }}>Welcome to the Dashboard!</h1>}
|
|
20
|
+
<div style={{ marginBottom: "20px" }}>
|
|
21
|
+
<p style={{ fontSize: "16px", lineHeight: "1.6" }}>
|
|
22
|
+
This is a protected route - you can only see this because you're logged in.
|
|
23
|
+
</p>
|
|
24
|
+
<p style={{ fontSize: "16px", lineHeight: "1.6", marginTop: "10px" }}>
|
|
25
|
+
Use the navigation in the header to explore other pages.
|
|
26
|
+
</p>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<button
|
|
30
|
+
onClick={() => send({ type: "auth.logout" })}
|
|
31
|
+
style={{
|
|
32
|
+
padding: "10px 20px",
|
|
33
|
+
fontSize: "16px",
|
|
34
|
+
backgroundColor: "#dc3545",
|
|
35
|
+
color: "white",
|
|
36
|
+
border: "none",
|
|
37
|
+
borderRadius: "4px",
|
|
38
|
+
cursor: "pointer",
|
|
39
|
+
marginTop: "20px",
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
Logout
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import type { AbstractActor } from "@xmachines/play-actor";
|
|
3
|
+
import { Signal } from "@xmachines/play-signals";
|
|
4
|
+
import type { AnyActorLogic } from "xstate";
|
|
5
|
+
|
|
6
|
+
type RoutableActor = AbstractActor<AnyActorLogic> & {
|
|
7
|
+
currentRoute: { get(): string | null };
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type Snapshot = {
|
|
11
|
+
value: unknown;
|
|
12
|
+
context: {
|
|
13
|
+
isAuthenticated?: boolean;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
interface DebugPanelProps {
|
|
18
|
+
actor: RoutableActor;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function DebugPanel({ actor }: DebugPanelProps) {
|
|
22
|
+
const [state, setState] = useState(String((actor.getSnapshot() as Snapshot).value));
|
|
23
|
+
const [auth, setAuth] = useState((actor.getSnapshot() as Snapshot).context.isAuthenticated);
|
|
24
|
+
const [route, setRoute] = useState(actor.currentRoute.get());
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const watcher = new Signal.subtle.Watcher(() => {
|
|
28
|
+
queueMicrotask(() => {
|
|
29
|
+
for (const signal of watcher.getPending()) {
|
|
30
|
+
signal.get();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const typedSnapshot = actor.getSnapshot() as Snapshot;
|
|
34
|
+
setState(String(typedSnapshot.value));
|
|
35
|
+
setAuth(typedSnapshot.context.isAuthenticated);
|
|
36
|
+
setRoute(actor.currentRoute.get());
|
|
37
|
+
|
|
38
|
+
watcher.watch(actor.state);
|
|
39
|
+
watcher.watch(actor.currentRoute as any);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
watcher.watch(actor.state);
|
|
44
|
+
watcher.watch(actor.currentRoute as any);
|
|
45
|
+
|
|
46
|
+
return () => {
|
|
47
|
+
watcher.unwatch(actor.state);
|
|
48
|
+
watcher.unwatch(actor.currentRoute as any);
|
|
49
|
+
};
|
|
50
|
+
}, [actor]);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<footer className="demo-debug">
|
|
54
|
+
<h3 className="debug-title">Debug Panel</h3>
|
|
55
|
+
<div className="debug-state">
|
|
56
|
+
<span className="debug-item">
|
|
57
|
+
State: <code>{state}</code>
|
|
58
|
+
</span>
|
|
59
|
+
<span className="debug-item">
|
|
60
|
+
Auth: <code>{String(auth)}</code>
|
|
61
|
+
</span>
|
|
62
|
+
<span className="debug-item">
|
|
63
|
+
Route: <code>{route}</code>
|
|
64
|
+
</span>
|
|
65
|
+
</div>
|
|
66
|
+
</footer>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Header Navigation Component
|
|
3
|
+
*
|
|
4
|
+
* Reactive navigation that shows/hides links based on authentication state
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState } from "react";
|
|
8
|
+
import { useSignalEffect } from "@xmachines/play-react";
|
|
9
|
+
import type { AbstractActor } from "@xmachines/play-actor";
|
|
10
|
+
import type { AnyActorLogic } from "xstate";
|
|
11
|
+
|
|
12
|
+
interface HeaderNavProps {
|
|
13
|
+
actor: AbstractActor<AnyActorLogic>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function HeaderNav({ actor }: HeaderNavProps) {
|
|
17
|
+
type Snapshot = {
|
|
18
|
+
context: {
|
|
19
|
+
isAuthenticated?: boolean;
|
|
20
|
+
username?: string | null;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const [isAuthenticated, setIsAuthenticated] = useState(() => {
|
|
25
|
+
const snapshot = actor.getSnapshot() as Snapshot;
|
|
26
|
+
return snapshot.context.isAuthenticated ?? false;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const [username, setUsername] = useState(() => {
|
|
30
|
+
const snapshot = actor.getSnapshot() as Snapshot;
|
|
31
|
+
return snapshot.context.username || "demo";
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Watch actor.state signal for changes
|
|
35
|
+
useSignalEffect(() => {
|
|
36
|
+
const snapshot = actor.state.get() as Snapshot;
|
|
37
|
+
setIsAuthenticated(snapshot.context.isAuthenticated ?? false);
|
|
38
|
+
setUsername(snapshot.context.username || "demo");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<nav className="demo-nav" aria-label="Main navigation">
|
|
43
|
+
<a
|
|
44
|
+
href="/"
|
|
45
|
+
onClick={(e) => {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
actor.send({ type: "play.route", to: "#home" });
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
Home
|
|
51
|
+
</a>
|
|
52
|
+
<a
|
|
53
|
+
href="/about"
|
|
54
|
+
onClick={(e) => {
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
actor.send({ type: "play.route", to: "#about" });
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
About
|
|
60
|
+
</a>
|
|
61
|
+
<a
|
|
62
|
+
href="/contact"
|
|
63
|
+
onClick={(e) => {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
actor.send({ type: "play.route", to: "#contact" });
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
Contact
|
|
69
|
+
</a>
|
|
70
|
+
{!isAuthenticated && (
|
|
71
|
+
<a
|
|
72
|
+
href="/login"
|
|
73
|
+
onClick={(e) => {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
actor.send({ type: "play.route", to: "#login" });
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
Login
|
|
79
|
+
</a>
|
|
80
|
+
)}
|
|
81
|
+
{isAuthenticated && (
|
|
82
|
+
<>
|
|
83
|
+
<a
|
|
84
|
+
href={`/profile/${username}`}
|
|
85
|
+
onClick={(e) => {
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
actor.send({
|
|
88
|
+
type: "play.route",
|
|
89
|
+
to: "#profile",
|
|
90
|
+
params: { userId: username },
|
|
91
|
+
});
|
|
92
|
+
}}
|
|
93
|
+
>
|
|
94
|
+
Profile
|
|
95
|
+
</a>
|
|
96
|
+
<button type="button" onClick={() => actor.send({ type: "auth.logout" })}>
|
|
97
|
+
Logout
|
|
98
|
+
</button>
|
|
99
|
+
</>
|
|
100
|
+
)}
|
|
101
|
+
</nav>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Home - Public landing page component
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { InferComponentProps } from "@xmachines/play-catalog";
|
|
6
|
+
import type { catalog } from "@xmachines/play-router-shared";
|
|
7
|
+
|
|
8
|
+
type HomeProps = InferComponentProps<typeof catalog, "Home"> & {
|
|
9
|
+
send: (event: any) => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function Home({ title, send }: HomeProps) {
|
|
13
|
+
return (
|
|
14
|
+
<div className="home" style={{ maxWidth: "800px", margin: "50px auto", padding: "20px" }}>
|
|
15
|
+
<h1>{title}</h1>
|
|
16
|
+
<p style={{ fontSize: "16px", lineHeight: "1.6", marginBottom: "15px" }}>
|
|
17
|
+
Welcome to the XMachines Play demo application.
|
|
18
|
+
</p>
|
|
19
|
+
<p style={{ fontSize: "16px", lineHeight: "1.6", marginBottom: "30px" }}>
|
|
20
|
+
This demonstrates the Universal Player Architecture with React and TanStack Router.
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<div style={{ marginBottom: "20px" }}>
|
|
24
|
+
<button
|
|
25
|
+
onClick={() => send({ type: "play.route", to: "#login" })}
|
|
26
|
+
style={{
|
|
27
|
+
padding: "10px 20px",
|
|
28
|
+
fontSize: "16px",
|
|
29
|
+
backgroundColor: "#007bff",
|
|
30
|
+
color: "white",
|
|
31
|
+
border: "none",
|
|
32
|
+
borderRadius: "4px",
|
|
33
|
+
cursor: "pointer",
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
Go to Login
|
|
37
|
+
</button>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Login Component - Passive Infrastructure Layer
|
|
3
|
+
*
|
|
4
|
+
* Simplified login form (username only, no password).
|
|
5
|
+
*
|
|
6
|
+
* Demonstrates:
|
|
7
|
+
* - Passive Infrastructure: Forwards events to actor, doesn't control navigation
|
|
8
|
+
* - Signal-Only Reactivity: No business logic in component (only UI state)
|
|
9
|
+
* - Strict Separation: Zero router imports - actor decides navigation
|
|
10
|
+
*
|
|
11
|
+
* @invariant Passive Infrastructure - Component never imports @tanstack/react-router
|
|
12
|
+
* @invariant Actor Authority - Login validation happens in actor guards, not here
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import React, { useState, type FormEvent } from "react";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Login props (inferred from catalog)
|
|
19
|
+
*/
|
|
20
|
+
import type { InferComponentProps } from "@xmachines/play-catalog";
|
|
21
|
+
import type { catalog } from "@xmachines/play-router-shared";
|
|
22
|
+
|
|
23
|
+
type LoginProps = InferComponentProps<typeof catalog, "Login"> & {
|
|
24
|
+
send: (event: any) => void;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Simplified login form - username only (no password for demo)
|
|
29
|
+
*/
|
|
30
|
+
export const Login: React.FC<LoginProps> = ({ send, title = "Login" }) => {
|
|
31
|
+
const [username, setUsername] = useState("");
|
|
32
|
+
|
|
33
|
+
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
|
|
34
|
+
e.preventDefault();
|
|
35
|
+
|
|
36
|
+
// Forward to actor - let business logic decide what happens
|
|
37
|
+
send({
|
|
38
|
+
type: "auth.login",
|
|
39
|
+
username,
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div style={{ maxWidth: "400px", margin: "50px auto", padding: "20px" }}>
|
|
45
|
+
<h1>{title}</h1>
|
|
46
|
+
<form onSubmit={handleSubmit}>
|
|
47
|
+
<div style={{ marginBottom: "15px" }}>
|
|
48
|
+
<label htmlFor="username" style={{ display: "block", marginBottom: "5px" }}>
|
|
49
|
+
Username
|
|
50
|
+
</label>
|
|
51
|
+
<input
|
|
52
|
+
type="text"
|
|
53
|
+
id="username"
|
|
54
|
+
value={username}
|
|
55
|
+
onChange={(e) => setUsername((e.target as HTMLInputElement).value)}
|
|
56
|
+
style={{ width: "100%", padding: "8px", fontSize: "16px" }}
|
|
57
|
+
placeholder="Enter any username"
|
|
58
|
+
required
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
<button
|
|
62
|
+
type="submit"
|
|
63
|
+
style={{
|
|
64
|
+
width: "100%",
|
|
65
|
+
padding: "10px",
|
|
66
|
+
fontSize: "16px",
|
|
67
|
+
backgroundColor: "#007bff",
|
|
68
|
+
color: "white",
|
|
69
|
+
border: "none",
|
|
70
|
+
borderRadius: "4px",
|
|
71
|
+
cursor: "pointer",
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
74
|
+
Log In
|
|
75
|
+
</button>
|
|
76
|
+
</form>
|
|
77
|
+
<p style={{ marginTop: "15px", fontSize: "14px", color: "#666" }}>
|
|
78
|
+
Enter any username - no password required for demo
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
};
|