@mmapp/react-compiler 0.1.0-alpha.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.
Files changed (314) hide show
  1. package/README.md +107 -0
  2. package/compile-blueprint-chat.mjs +99 -0
  3. package/compile-blueprint-glass-console.mjs +98 -0
  4. package/compile-chat-defs.mjs +92 -0
  5. package/dist/babel/index.d.mts +3 -0
  6. package/dist/babel/index.d.ts +3 -0
  7. package/dist/babel/index.js +4851 -0
  8. package/dist/babel/index.mjs +7 -0
  9. package/dist/chunk-26U577GB.mjs +3465 -0
  10. package/dist/chunk-2FBDFAX6.mjs +2362 -0
  11. package/dist/chunk-2L4QSMXG.mjs +175 -0
  12. package/dist/chunk-2REDFOER.mjs +931 -0
  13. package/dist/chunk-46YKSHQR.mjs +175 -0
  14. package/dist/chunk-4XHK6FWL.mjs +2058 -0
  15. package/dist/chunk-5M7DKKBC.mjs +215 -0
  16. package/dist/chunk-5VNJ7C6N.mjs +154 -0
  17. package/dist/chunk-6CQOAAMV.mjs +1803 -0
  18. package/dist/chunk-6SEVAAVT.mjs +3516 -0
  19. package/dist/chunk-6YLR5ZDA.mjs +2829 -0
  20. package/dist/chunk-AOGY2GK6.mjs +3292 -0
  21. package/dist/chunk-AXXUXRNA.mjs +1434 -0
  22. package/dist/chunk-CHLVKMQW.mjs +175 -0
  23. package/dist/chunk-CKGOZAB7.mjs +939 -0
  24. package/dist/chunk-D34RAZUX.mjs +2223 -0
  25. package/dist/chunk-EQGA6A6D.mjs +121 -0
  26. package/dist/chunk-EY2CSXYA.mjs +822 -0
  27. package/dist/chunk-FIQ65CDR.mjs +925 -0
  28. package/dist/chunk-FOZXJFAR.mjs +186 -0
  29. package/dist/chunk-FX6URXWN.mjs +186 -0
  30. package/dist/chunk-G7SMOWOL.mjs +828 -0
  31. package/dist/chunk-GGB4G5YY.mjs +175 -0
  32. package/dist/chunk-HLRGCCIL.mjs +4839 -0
  33. package/dist/chunk-HOIUP6IF.mjs +690 -0
  34. package/dist/chunk-I3AU7GRD.mjs +120 -0
  35. package/dist/chunk-ILFGMUVD.mjs +1933 -0
  36. package/dist/chunk-IPTX5MJU.mjs +3223 -0
  37. package/dist/chunk-ITGUSH2Z.mjs +2783 -0
  38. package/dist/chunk-IXHBCAMF.mjs +3306 -0
  39. package/dist/chunk-J7TWJ3TM.mjs +2784 -0
  40. package/dist/chunk-JDPLDGVF.mjs +4810 -0
  41. package/dist/chunk-K53XP2DL.mjs +148 -0
  42. package/dist/chunk-K5HX2SVL.mjs +1902 -0
  43. package/dist/chunk-KFGYOOVS.mjs +214 -0
  44. package/dist/chunk-KFVVOS5N.mjs +925 -0
  45. package/dist/chunk-L2OZ4CDV.mjs +113 -0
  46. package/dist/chunk-MIZV3TAN.mjs +3293 -0
  47. package/dist/chunk-NKKLQE5V.mjs +148 -0
  48. package/dist/chunk-NOW23XFZ.mjs +186 -0
  49. package/dist/chunk-NRXQKQ74.mjs +148 -0
  50. package/dist/chunk-OWI6XWCD.mjs +3375 -0
  51. package/dist/chunk-PRUMNNDI.mjs +3192 -0
  52. package/dist/chunk-QTBD5B3F.mjs +148 -0
  53. package/dist/chunk-SKSDPPNT.mjs +3788 -0
  54. package/dist/chunk-SP2YUS33.mjs +186 -0
  55. package/dist/chunk-SU4E6E7B.mjs +3153 -0
  56. package/dist/chunk-SYUUKW5A.mjs +3379 -0
  57. package/dist/chunk-UL2XZEMA.mjs +3128 -0
  58. package/dist/chunk-XMWUHQVV.mjs +939 -0
  59. package/dist/chunk-XZNEDRGN.mjs +3876 -0
  60. package/dist/chunk-Y6FXYEAI.mjs +10 -0
  61. package/dist/chunk-YFS6JMYO.mjs +3342 -0
  62. package/dist/chunk-Z6AIQ4KL.mjs +113 -0
  63. package/dist/cli/index.d.mts +1 -0
  64. package/dist/cli/index.d.ts +1 -0
  65. package/dist/cli/index.js +11585 -0
  66. package/dist/cli/index.mjs +701 -0
  67. package/dist/codemod/cli.d.mts +1 -0
  68. package/dist/codemod/cli.d.ts +1 -0
  69. package/dist/codemod/cli.js +1104 -0
  70. package/dist/codemod/cli.mjs +157 -0
  71. package/dist/codemod/index.d.mts +148 -0
  72. package/dist/codemod/index.d.ts +148 -0
  73. package/dist/codemod/index.js +981 -0
  74. package/dist/codemod/index.mjs +25 -0
  75. package/dist/dev-server-Bs_sz2DG.d.mts +111 -0
  76. package/dist/dev-server-Bs_sz2DG.d.ts +111 -0
  77. package/dist/dev-server-CjoufJ-u.d.mts +109 -0
  78. package/dist/dev-server-CjoufJ-u.d.ts +109 -0
  79. package/dist/dev-server.d.mts +3 -0
  80. package/dist/dev-server.d.ts +3 -0
  81. package/dist/dev-server.js +7603 -0
  82. package/dist/dev-server.mjs +11 -0
  83. package/dist/envelope-DD7v0v6E.d.mts +265 -0
  84. package/dist/envelope-DD7v0v6E.d.ts +265 -0
  85. package/dist/envelope-vCVjrHlo.d.mts +265 -0
  86. package/dist/envelope-vCVjrHlo.d.ts +265 -0
  87. package/dist/envelope.d.mts +2 -0
  88. package/dist/envelope.d.ts +2 -0
  89. package/dist/envelope.js +5184 -0
  90. package/dist/envelope.mjs +9 -0
  91. package/dist/index-B5gSgvnd.d.mts +44 -0
  92. package/dist/index-B5gSgvnd.d.ts +44 -0
  93. package/dist/index-Bs0MnR54.d.mts +103 -0
  94. package/dist/index-Bs0MnR54.d.ts +103 -0
  95. package/dist/index-DR0nNc_f.d.mts +101 -0
  96. package/dist/index-DR0nNc_f.d.ts +101 -0
  97. package/dist/index-revho_gS.d.mts +104 -0
  98. package/dist/index-revho_gS.d.ts +104 -0
  99. package/dist/index.d.mts +1099 -0
  100. package/dist/index.d.ts +1099 -0
  101. package/dist/index.js +10162 -0
  102. package/dist/index.mjs +372 -0
  103. package/dist/init-IXEE2RCF.mjs +340 -0
  104. package/dist/project-compiler-EGJUTAJU.mjs +10 -0
  105. package/dist/project-compiler-VFR6CSDX.mjs +10 -0
  106. package/dist/project-decompiler-5GY2KSG4.mjs +7 -0
  107. package/dist/pull-A2QUHW4K.mjs +109 -0
  108. package/dist/pull-JBEQWVPE.mjs +109 -0
  109. package/dist/testing/index.d.mts +211 -0
  110. package/dist/testing/index.d.ts +211 -0
  111. package/dist/testing/index.js +5106 -0
  112. package/dist/testing/index.mjs +247 -0
  113. package/dist/vite/index.d.mts +59 -0
  114. package/dist/vite/index.d.ts +59 -0
  115. package/dist/vite/index.js +5023 -0
  116. package/dist/vite/index.mjs +8 -0
  117. package/examples/README.md +72 -0
  118. package/examples/authentication/main.workflow.tsx +139 -0
  119. package/examples/authentication/mm.config.ts +22 -0
  120. package/examples/authentication/models/auth.ts +45 -0
  121. package/examples/authentication/pages/LoginPage.tsx +79 -0
  122. package/examples/authentication/pages/SignupPage.tsx +87 -0
  123. package/examples/counter.workflow.tsx +65 -0
  124. package/examples/dashboard.workflow.tsx +419 -0
  125. package/examples/invoice-approval/actions/invoice.server.ts +72 -0
  126. package/examples/invoice-approval/main.workflow.tsx +168 -0
  127. package/examples/invoice-approval/mm.config.ts +18 -0
  128. package/examples/invoice-approval/models/invoice.ts +46 -0
  129. package/examples/invoice-approval/pages/InvoiceDetailPage.tsx +175 -0
  130. package/examples/invoice-approval/pages/InvoiceFormPage.tsx +198 -0
  131. package/examples/invoice-approval/pages/InvoiceListPage.tsx +141 -0
  132. package/examples/todo-app.workflow.tsx +131 -0
  133. package/examples/uber-app/actions/matching.server.ts +177 -0
  134. package/examples/uber-app/actions/notifications.server.ts +176 -0
  135. package/examples/uber-app/actions/payments.server.ts +184 -0
  136. package/examples/uber-app/actions/pricing.server.ts +176 -0
  137. package/examples/uber-app/app/admin/analytics.tsx +102 -0
  138. package/examples/uber-app/app/admin/fleet.tsx +102 -0
  139. package/examples/uber-app/app/admin/surge-pricing.tsx +95 -0
  140. package/examples/uber-app/app/driver/dashboard.tsx +87 -0
  141. package/examples/uber-app/app/driver/earnings.tsx +101 -0
  142. package/examples/uber-app/app/driver/navigation.tsx +94 -0
  143. package/examples/uber-app/app/driver/ride-acceptance.tsx +103 -0
  144. package/examples/uber-app/app/rider/home.tsx +109 -0
  145. package/examples/uber-app/app/rider/payment-methods.tsx +134 -0
  146. package/examples/uber-app/app/rider/ride-history.tsx +90 -0
  147. package/examples/uber-app/app/rider/ride-tracking.tsx +108 -0
  148. package/examples/uber-app/components/DriverCard.tsx +176 -0
  149. package/examples/uber-app/components/MapView.tsx +216 -0
  150. package/examples/uber-app/components/RatingStars.tsx +227 -0
  151. package/examples/uber-app/components/RideCard.tsx +167 -0
  152. package/examples/uber-app/mm.config.ts +30 -0
  153. package/examples/uber-app/models/location.model.ts +70 -0
  154. package/examples/uber-app/models/payment.model.ts +87 -0
  155. package/examples/uber-app/models/rating.model.ts +54 -0
  156. package/examples/uber-app/models/ride.model.ts +118 -0
  157. package/examples/uber-app/models/user.model.ts +66 -0
  158. package/examples/uber-app/models/vehicle.model.ts +63 -0
  159. package/examples/uber-app/tests/payment.test.tsx +129 -0
  160. package/examples/uber-app/tests/ride-flow.test.tsx +123 -0
  161. package/examples/uber-app/workflows/dispute-resolution.workflow.tsx +205 -0
  162. package/examples/uber-app/workflows/driver-onboarding.workflow.tsx +227 -0
  163. package/examples/uber-app/workflows/payment-processing.workflow.tsx +223 -0
  164. package/examples/uber-app/workflows/ride-request.workflow.tsx +194 -0
  165. package/package.json +77 -0
  166. package/package.json.backup +86 -0
  167. package/scripts/decompile.ts +226 -0
  168. package/scripts/seed-auth.ts +267 -0
  169. package/scripts/seed-uber.ts +248 -0
  170. package/scripts/validate-uber.ts +119 -0
  171. package/seed-blueprint-chat.mjs +444 -0
  172. package/seed-blueprint-glass-console.mjs +445 -0
  173. package/seed-compiled.mjs +318 -0
  174. package/src/RoundTripValidator.ts +400 -0
  175. package/src/__tests__/atom-rendering-coverage.test.ts +680 -0
  176. package/src/__tests__/auth-module-compilation.test.ts +247 -0
  177. package/src/__tests__/auth-template-compilation.test.ts +589 -0
  178. package/src/__tests__/change-extractor.test.ts +142 -0
  179. package/src/__tests__/cli-pull.test.ts +73 -0
  180. package/src/__tests__/cli-test.test.ts +72 -0
  181. package/src/__tests__/component-extractor.test.ts +331 -0
  182. package/src/__tests__/context-extractor.test.ts +145 -0
  183. package/src/__tests__/decompiler.test.ts +718 -0
  184. package/src/__tests__/define-blueprint.test.ts +133 -0
  185. package/src/__tests__/definition-validator.test.ts +519 -0
  186. package/src/__tests__/during-extractor.test.ts +152 -0
  187. package/src/__tests__/effect-extractor.test.ts +107 -0
  188. package/src/__tests__/event-emission.test.ts +127 -0
  189. package/src/__tests__/examples.test.ts +236 -0
  190. package/src/__tests__/full-blueprint-coverage.test.ts +1221 -0
  191. package/src/__tests__/golden-suite.test.ts +403 -0
  192. package/src/__tests__/grammar-island-extractor.test.ts +289 -0
  193. package/src/__tests__/instance-key.test.ts +82 -0
  194. package/src/__tests__/ir-migration.test.ts +255 -0
  195. package/src/__tests__/lock-file.test.ts +117 -0
  196. package/src/__tests__/model-extractor.test.ts +195 -0
  197. package/src/__tests__/model-field-acl.test.ts +237 -0
  198. package/src/__tests__/model-hooks.test.ts +130 -0
  199. package/src/__tests__/model-ref-resolution.test.ts +268 -0
  200. package/src/__tests__/model-roundtrip.test.ts +502 -0
  201. package/src/__tests__/model-runtime.test.ts +112 -0
  202. package/src/__tests__/model-transitions.test.ts +183 -0
  203. package/src/__tests__/nrt-action-trace.test.ts +391 -0
  204. package/src/__tests__/pipeline-hardening.test.ts +413 -0
  205. package/src/__tests__/project-compiler.test.ts +546 -0
  206. package/src/__tests__/project-decompiler.test.ts +343 -0
  207. package/src/__tests__/query-compilation.test.ts +145 -0
  208. package/src/__tests__/round-trip/PLAN.md +158 -0
  209. package/src/__tests__/round-trip/README.md +52 -0
  210. package/src/__tests__/round-trip/RESULTS.md +86 -0
  211. package/src/__tests__/round-trip/fixtures/data-heavy/main.workflow.tsx +55 -0
  212. package/src/__tests__/round-trip/fixtures/data-heavy/mm.config.ts +11 -0
  213. package/src/__tests__/round-trip/fixtures/data-heavy/models/contact.ts +54 -0
  214. package/src/__tests__/round-trip/fixtures/full-workflow/main.workflow.tsx +79 -0
  215. package/src/__tests__/round-trip/fixtures/full-workflow/mm.config.ts +12 -0
  216. package/src/__tests__/round-trip/fixtures/full-workflow/models/order.ts +50 -0
  217. package/src/__tests__/round-trip/fixtures/simple-crud/main.workflow.tsx +25 -0
  218. package/src/__tests__/round-trip/fixtures/simple-crud/mm.config.ts +11 -0
  219. package/src/__tests__/round-trip/fixtures/simple-crud/models/task.ts +32 -0
  220. package/src/__tests__/round-trip/fixtures/view-heavy/main.workflow.tsx +79 -0
  221. package/src/__tests__/round-trip/fixtures/view-heavy/mm.config.ts +10 -0
  222. package/src/__tests__/round-trip/round-trip.test.ts +2598 -0
  223. package/src/__tests__/round-trip-ir.test.ts +300 -0
  224. package/src/__tests__/round-trip.test.ts +1212 -0
  225. package/src/__tests__/route-merging.test.ts +372 -0
  226. package/src/__tests__/router-composition.test.ts +489 -0
  227. package/src/__tests__/router-extractor.test.ts +176 -0
  228. package/src/__tests__/server-action-extractor.test.ts +128 -0
  229. package/src/__tests__/smart-type-inference.test.ts +365 -0
  230. package/src/__tests__/source-envelope.test.ts +284 -0
  231. package/src/__tests__/source-fidelity.test.ts +516 -0
  232. package/src/__tests__/state-extractor.test.ts +115 -0
  233. package/src/__tests__/strict-mode.test.ts +227 -0
  234. package/src/__tests__/transition-effect-extractor.test.ts +119 -0
  235. package/src/__tests__/transition-extractor.test.ts +68 -0
  236. package/src/__tests__/ts-to-expression.test.ts +462 -0
  237. package/src/__tests__/type-generator.test.ts +201 -0
  238. package/src/__tests__/uber-validation.test.ts +502 -0
  239. package/src/action-compiler.ts +361 -0
  240. package/src/babel/emitters/experience-transform.ts +199 -0
  241. package/src/babel/emitters/ir-to-tsx-emitter.ts +110 -0
  242. package/src/babel/emitters/pure-form-emitter.ts +1023 -0
  243. package/src/babel/emitters/runtime-glue-emitter.ts +39 -0
  244. package/src/babel/extractors/change-extractor.ts +199 -0
  245. package/src/babel/extractors/component-extractor.ts +907 -0
  246. package/src/babel/extractors/computed-extractor.ts +262 -0
  247. package/src/babel/extractors/context-extractor.ts +277 -0
  248. package/src/babel/extractors/during-extractor.ts +295 -0
  249. package/src/babel/extractors/effect-extractor.ts +340 -0
  250. package/src/babel/extractors/event-extractor.ts +235 -0
  251. package/src/babel/extractors/grammar-island-extractor.ts +302 -0
  252. package/src/babel/extractors/model-extractor.ts +1018 -0
  253. package/src/babel/extractors/router-extractor.ts +303 -0
  254. package/src/babel/extractors/server-action-extractor.ts +173 -0
  255. package/src/babel/extractors/server-action-hook-extractor.ts +72 -0
  256. package/src/babel/extractors/server-state-extractor.ts +88 -0
  257. package/src/babel/extractors/state-extractor.ts +214 -0
  258. package/src/babel/extractors/transition-effect-extractor.ts +176 -0
  259. package/src/babel/extractors/transition-extractor.ts +143 -0
  260. package/src/babel/index.ts +24 -0
  261. package/src/babel/transpilers/ts-to-expression.ts +674 -0
  262. package/src/babel/visitor.ts +807 -0
  263. package/src/cli/auth.ts +255 -0
  264. package/src/cli/build.ts +288 -0
  265. package/src/cli/deploy.ts +206 -0
  266. package/src/cli/index.ts +328 -0
  267. package/src/cli/init.ts +388 -0
  268. package/src/cli/installer.ts +261 -0
  269. package/src/cli/lock-file.ts +94 -0
  270. package/src/cli/mmrc.ts +22 -0
  271. package/src/cli/pull.ts +172 -0
  272. package/src/cli/registry-client.ts +175 -0
  273. package/src/cli/test.ts +397 -0
  274. package/src/cli/type-generator.ts +243 -0
  275. package/src/codemod/__tests__/forward.test.ts +239 -0
  276. package/src/codemod/__tests__/reverse.test.ts +145 -0
  277. package/src/codemod/__tests__/round-trip.test.ts +137 -0
  278. package/src/codemod/annotation.ts +97 -0
  279. package/src/codemod/classify.ts +197 -0
  280. package/src/codemod/cli.ts +207 -0
  281. package/src/codemod/control-flow.ts +409 -0
  282. package/src/codemod/forward.ts +244 -0
  283. package/src/codemod/import-manager.ts +171 -0
  284. package/src/codemod/index.ts +120 -0
  285. package/src/codemod/reverse.ts +197 -0
  286. package/src/codemod/rules.ts +174 -0
  287. package/src/codemod/state-transform.ts +126 -0
  288. package/src/decompiler/ast-builder.ts +538 -0
  289. package/src/decompiler/config-generator.ts +151 -0
  290. package/src/decompiler/index.ts +315 -0
  291. package/src/decompiler/project-decompiler.ts +1776 -0
  292. package/src/decompiler/project.ts +862 -0
  293. package/src/decompiler/split-strategy.ts +140 -0
  294. package/src/decompiler/state-emitter.ts +1053 -0
  295. package/src/decompiler/sx-emitter.ts +318 -0
  296. package/src/decompiler/workspace-hydrator.ts +189 -0
  297. package/src/dev-server.ts +238 -0
  298. package/src/envelope/fs-tree.ts +217 -0
  299. package/src/envelope/source-envelope.ts +264 -0
  300. package/src/envelope.ts +315 -0
  301. package/src/incremental-compiler.ts +401 -0
  302. package/src/index.ts +99 -0
  303. package/src/model-compiler.ts +277 -0
  304. package/src/project-compiler.ts +1629 -0
  305. package/src/route-extractor.ts +333 -0
  306. package/src/testing/index.ts +32 -0
  307. package/src/testing/snapshot.ts +252 -0
  308. package/src/testing/test-utils.ts +226 -0
  309. package/src/types.ts +68 -0
  310. package/src/vite/index.ts +288 -0
  311. package/test-compile.mjs +142 -0
  312. package/tsconfig.json +25 -0
  313. package/tsup.config.ts +23 -0
  314. package/vitest.config.ts +9 -0
@@ -0,0 +1,176 @@
1
+ /**
2
+ * @workflow slug="uber-rideshare"
3
+ * @description Fare calculation engine — base fare, distance, time, surge, promos, and vehicle multipliers
4
+ */
5
+
6
+ import type { TransitionContext, ActionResult } from '@mindmatrix/react';
7
+
8
+ /** Per-vehicle-type rate configuration */
9
+ const VEHICLE_RATES = {
10
+ economy: { baseFare: 2.50, perKm: 1.20, perMinute: 0.25, bookingFee: 2.00, minFare: 7.00 },
11
+ premium: { baseFare: 5.00, perKm: 2.00, perMinute: 0.40, bookingFee: 2.50, minFare: 12.00 },
12
+ xl: { baseFare: 4.00, perKm: 1.80, perMinute: 0.35, bookingFee: 2.50, minFare: 10.00 },
13
+ luxury: { baseFare: 8.00, perKm: 3.50, perMinute: 0.60, bookingFee: 3.00, minFare: 20.00 },
14
+ } as const;
15
+
16
+ /** Cancellation fee tiers based on ride state */
17
+ const CANCELLATION_FEES = {
18
+ searching: 0,
19
+ matched: 2.00,
20
+ driver_enroute: 5.00,
21
+ driver_arrived: 10.00,
22
+ } as const;
23
+
24
+ /** Time-of-day multiplier (peak hours) */
25
+ function getTimeMultiplier(): number {
26
+ const hour = new Date().getHours();
27
+ if ((hour >= 7 && hour <= 9) || (hour >= 17 && hour <= 19)) return 1.2; // Rush hour
28
+ if (hour >= 22 || hour <= 5) return 1.1; // Late night
29
+ return 1.0;
30
+ }
31
+
32
+ /** Calculate estimated fare before ride starts */
33
+ export async function calculateEstimatedFare(ctx: TransitionContext): Promise<ActionResult> {
34
+ const { instance } = ctx;
35
+ const fields = instance.fields as Record<string, unknown>;
36
+ const vehicleType = (fields.vehicleType as string) || 'economy';
37
+ const distanceKm = fields.estimatedDistanceKm as number;
38
+ const durationMin = fields.estimatedDurationMinutes as number;
39
+ const surgeMultiplier = (fields.surgeMultiplier as number) || 1.0;
40
+ const promoCode = fields.promoCode as string;
41
+
42
+ const rates = VEHICLE_RATES[vehicleType as keyof typeof VEHICLE_RATES] || VEHICLE_RATES.economy;
43
+ const timeMultiplier = getTimeMultiplier();
44
+
45
+ const baseFare = rates.baseFare;
46
+ const distanceFare = distanceKm * rates.perKm;
47
+ const timeFare = durationMin * rates.perMinute;
48
+ const subtotal = (baseFare + distanceFare + timeFare) * timeMultiplier;
49
+ const surgeFee = subtotal * (surgeMultiplier - 1);
50
+ const bookingFee = rates.bookingFee;
51
+
52
+ // Promo discount (simplified: flat $5 off for valid codes)
53
+ let promoDiscount = 0;
54
+ if (promoCode && promoCode.length >= 4) {
55
+ promoDiscount = Math.min(5.00, subtotal * 0.2); // 20% off, max $5
56
+ }
57
+
58
+ const totalBeforeTip = Math.max(
59
+ rates.minFare,
60
+ subtotal + surgeFee + bookingFee - promoDiscount
61
+ );
62
+
63
+ return {
64
+ success: true,
65
+ data: {
66
+ baseFare: Math.round(baseFare * 100) / 100,
67
+ distanceFare: Math.round(distanceFare * 100) / 100,
68
+ timeFare: Math.round(timeFare * 100) / 100,
69
+ surgeFee: Math.round(surgeFee * 100) / 100,
70
+ bookingFee,
71
+ promoDiscount: Math.round(promoDiscount * 100) / 100,
72
+ estimatedFare: Math.round(totalBeforeTip * 100) / 100,
73
+ timeMultiplier,
74
+ surgeMultiplier,
75
+ currency: 'USD',
76
+ },
77
+ };
78
+ }
79
+
80
+ /** Calculate actual fare after ride completes using real distance and duration */
81
+ export async function calculateActualFare(ctx: TransitionContext): Promise<ActionResult> {
82
+ const { instance } = ctx;
83
+ const fields = instance.fields as Record<string, unknown>;
84
+ const vehicleType = (fields.vehicleType as string) || 'economy';
85
+ const actualDistanceKm = fields.actualDistanceKm as number;
86
+ const actualDurationMin = fields.actualDurationMinutes as number;
87
+ const surgeMultiplier = (fields.surgeMultiplier as number) || 1.0;
88
+ const promoDiscount = (fields.promoDiscount as number) || 0;
89
+ const tollsFee = (fields.tollsFee as number) || 0;
90
+ const tipAmount = (fields.tipAmount as number) || 0;
91
+
92
+ const rates = VEHICLE_RATES[vehicleType as keyof typeof VEHICLE_RATES] || VEHICLE_RATES.economy;
93
+
94
+ const baseFare = rates.baseFare;
95
+ const distanceFare = actualDistanceKm * rates.perKm;
96
+ const timeFare = actualDurationMin * rates.perMinute;
97
+ const subtotal = baseFare + distanceFare + timeFare;
98
+ const surgeFee = subtotal * (surgeMultiplier - 1);
99
+ const bookingFee = rates.bookingFee;
100
+
101
+ const totalBeforeTip = Math.max(
102
+ rates.minFare,
103
+ subtotal + surgeFee + bookingFee + tollsFee - promoDiscount
104
+ );
105
+ const totalWithTip = totalBeforeTip + tipAmount;
106
+
107
+ // Platform takes 25%, driver gets 75% of fare + 100% of tips
108
+ const platformFeePercent = 25;
109
+ const platformFee = Math.round(totalBeforeTip * (platformFeePercent / 100) * 100) / 100;
110
+ const driverPayout = Math.round((totalBeforeTip - platformFee + tipAmount) * 100) / 100;
111
+
112
+ return {
113
+ success: true,
114
+ data: {
115
+ baseFare: Math.round(baseFare * 100) / 100,
116
+ distanceFare: Math.round(distanceFare * 100) / 100,
117
+ timeFare: Math.round(timeFare * 100) / 100,
118
+ surgeFee: Math.round(surgeFee * 100) / 100,
119
+ bookingFee,
120
+ tollsFee,
121
+ promoDiscount,
122
+ tipAmount,
123
+ actualFare: Math.round(totalWithTip * 100) / 100,
124
+ platformFee,
125
+ platformFeePercent,
126
+ driverPayout,
127
+ },
128
+ };
129
+ }
130
+
131
+ /** Calculate cancellation fee based on ride state at time of cancellation */
132
+ export async function calculateCancellationFee(ctx: TransitionContext): Promise<ActionResult> {
133
+ const { instance } = ctx;
134
+ const currentState = instance.currentState as string;
135
+ const fee = CANCELLATION_FEES[currentState as keyof typeof CANCELLATION_FEES] || 0;
136
+
137
+ return {
138
+ success: true,
139
+ data: {
140
+ cancellationFee: fee,
141
+ cancelledAt: new Date().toISOString(),
142
+ },
143
+ };
144
+ }
145
+
146
+ /** Calculate dynamic surge multiplier based on demand/supply ratio */
147
+ export async function calculateSurge(ctx: TransitionContext): Promise<ActionResult> {
148
+ const { env } = ctx;
149
+
150
+ const activeRides = await env.query('uber-ride', {
151
+ filter: { status: 'searching' },
152
+ });
153
+ const onlineDrivers = await env.query('uber-user', {
154
+ filter: { role: 'driver', isOnline: true },
155
+ });
156
+
157
+ const demand = activeRides.total || 0;
158
+ const supply = onlineDrivers.total || 1;
159
+ const ratio = demand / supply;
160
+
161
+ let multiplier = 1.0;
162
+ if (ratio > 3.0) multiplier = 3.0;
163
+ else if (ratio > 2.0) multiplier = 2.0 + (ratio - 2.0);
164
+ else if (ratio > 1.5) multiplier = 1.5 + (ratio - 1.5);
165
+ else if (ratio > 1.0) multiplier = 1.0 + (ratio - 1.0) * 0.5;
166
+
167
+ return {
168
+ success: true,
169
+ data: {
170
+ surgeMultiplier: Math.round(multiplier * 10) / 10,
171
+ demand,
172
+ supply,
173
+ ratio: Math.round(ratio * 100) / 100,
174
+ },
175
+ };
176
+ }
@@ -0,0 +1,102 @@
1
+ import { useState, useWorkflow, useTransition, useField, useComputed, useRole, useQuery, useOnEnter, useOnEvent } from '@mindmatrix/react';
2
+ import { Stack, Row, Text, Button, Show, Each, Card, Badge, Heading, Input } from '@mindmatrix/react';
3
+ import { RideStats, RevenueBreakdown } from '../../models/analytics';
4
+
5
+ type TimePeriod = 'today' | 'week' | 'month';
6
+
7
+ export default function AnalyticsDashboard() {
8
+ const isAdmin = useRole('admin');
9
+ const [period, setPeriod] = useState<TimePeriod>('today');
10
+
11
+ const rideStats = useQuery<RideStats>('ride_stats', { period });
12
+ const revenue = useQuery<RevenueBreakdown>('revenue_breakdown', { period });
13
+ const driverUtil = useQuery<{ percentage: number; activeDrivers: number; totalDrivers: number }>('driver_utilization', { period });
14
+ const waitTime = useQuery<{ averageMinutes: number; p95Minutes: number }>('average_wait_time', { period });
15
+ const surgeZones = useQuery<{ count: number; zones: string[] }>('active_surge_zones');
16
+
17
+ const revenueBarData = useComputed(() => {
18
+ if (!revenue.data) return [];
19
+ const max = Math.max(revenue.data.rides, revenue.data.tips, revenue.data.surgeExtra, revenue.data.cancellationFees);
20
+ return [
21
+ { label: 'Rides', value: revenue.data.rides, pct: max > 0 ? (revenue.data.rides / max) * 100 : 0 },
22
+ { label: 'Tips', value: revenue.data.tips, pct: max > 0 ? (revenue.data.tips / max) * 100 : 0 },
23
+ { label: 'Surge', value: revenue.data.surgeExtra, pct: max > 0 ? (revenue.data.surgeExtra / max) * 100 : 0 },
24
+ { label: 'Cancel Fees', value: revenue.data.cancellationFees, pct: max > 0 ? (revenue.data.cancellationFees / max) * 100 : 0 },
25
+ ];
26
+ }, [revenue.data]);
27
+
28
+ const totalRevenue = useComputed(() => {
29
+ if (!revenue.data) return 0;
30
+ return revenue.data.rides + revenue.data.tips + revenue.data.surgeExtra + revenue.data.cancellationFees;
31
+ }, [revenue.data]);
32
+
33
+ return (
34
+ <Show when={isAdmin} fallback={<Text>Access denied.</Text>}>
35
+ <Stack gap="lg">
36
+ <Row justify="between" align="center">
37
+ <Heading level={2}>Platform Analytics</Heading>
38
+ <Row gap="sm">
39
+ {(['today', 'week', 'month'] as const).map((p) => (
40
+ <Button key={p} variant={period === p ? 'primary' : 'ghost'} onClick={() => setPeriod(p)}>
41
+ {p.charAt(0).toUpperCase() + p.slice(1)}
42
+ </Button>
43
+ ))}
44
+ </Row>
45
+ </Row>
46
+
47
+ <Row gap="md" wrap>
48
+ <Card>
49
+ <Stack gap="xs" align="center">
50
+ <Text size="sm" color="muted">Total Rides</Text>
51
+ <Text size="xl" weight="bold">{rideStats.data?.totalRides ?? '—'}</Text>
52
+ <Text size="xs" color="muted">Completed: {rideStats.data?.completedRides ?? 0}</Text>
53
+ </Stack>
54
+ </Card>
55
+ <Card>
56
+ <Stack gap="xs" align="center">
57
+ <Text size="sm" color="muted">Revenue</Text>
58
+ <Text size="xl" weight="bold">${totalRevenue.toLocaleString()}</Text>
59
+ </Stack>
60
+ </Card>
61
+ <Card>
62
+ <Stack gap="xs" align="center">
63
+ <Text size="sm" color="muted">Driver Utilization</Text>
64
+ <Text size="xl" weight="bold">{driverUtil.data?.percentage ?? 0}%</Text>
65
+ <Text size="xs" color="muted">{driverUtil.data?.activeDrivers ?? 0} / {driverUtil.data?.totalDrivers ?? 0} active</Text>
66
+ </Stack>
67
+ </Card>
68
+ <Card>
69
+ <Stack gap="xs" align="center">
70
+ <Text size="sm" color="muted">Avg Wait Time</Text>
71
+ <Text size="xl" weight="bold">{waitTime.data?.averageMinutes ?? 0} min</Text>
72
+ <Text size="xs" color="muted">p95: {waitTime.data?.p95Minutes ?? 0} min</Text>
73
+ </Stack>
74
+ </Card>
75
+ <Card>
76
+ <Stack gap="xs" align="center">
77
+ <Text size="sm" color="muted">Surge Zones</Text>
78
+ <Text size="xl" weight="bold">{surgeZones.data?.count ?? 0}</Text>
79
+ </Stack>
80
+ </Card>
81
+ </Row>
82
+
83
+ <Card>
84
+ <Heading level={4}>Revenue Breakdown</Heading>
85
+ <Stack gap="sm">
86
+ <Each items={revenueBarData}>
87
+ {(bar: { label: string; value: number; pct: number }) => (
88
+ <Row key={bar.label} gap="sm" align="center">
89
+ <Text style={{ width: 100 }}>{bar.label}</Text>
90
+ <div style={{ flex: 1, background: '#e5e7eb', borderRadius: 4, height: 20 }}>
91
+ <div style={{ width: `${bar.pct}%`, background: '#3b82f6', borderRadius: 4, height: 20 }} />
92
+ </div>
93
+ <Text weight="bold">${bar.value.toLocaleString()}</Text>
94
+ </Row>
95
+ )}
96
+ </Each>
97
+ </Stack>
98
+ </Card>
99
+ </Stack>
100
+ </Show>
101
+ );
102
+ }
@@ -0,0 +1,102 @@
1
+ import { useState, useWorkflow, useTransition, useField, useComputed, useRole, useQuery, useOnEnter, useOnEvent } from '@mindmatrix/react';
2
+ import { Stack, Row, Text, Button, Show, Each, Card, Badge, Heading, Input } from '@mindmatrix/react';
3
+ import { Vehicle, Driver } from '../../models/fleet';
4
+
5
+ type VehicleType = 'sedan' | 'suv' | 'van' | 'luxury';
6
+ type VehicleStatus = 'active' | 'maintenance' | 'retired';
7
+
8
+ export default function FleetManagement() {
9
+ const isAdmin = useRole('admin');
10
+ const [vehicles] = useField<Vehicle[]>('vehicles');
11
+ const [drivers] = useField<Driver[]>('drivers');
12
+ const [filterType, setFilterType] = useState<VehicleType | 'all'>('all');
13
+ const [showAddForm, setShowAddForm] = useState(false);
14
+ const [newPlate, setNewPlate] = useState('');
15
+ const [newType, setNewType] = useState<VehicleType>('sedan');
16
+ const addVehicle = useTransition('add_vehicle');
17
+ const assignDriver = useTransition('assign_driver');
18
+ const updateStatus = useTransition('update_vehicle_status');
19
+
20
+ const filteredVehicles = useComputed(() => {
21
+ if (filterType === 'all') return vehicles;
22
+ return vehicles.filter((v: Vehicle) => v.type === filterType);
23
+ }, [vehicles, filterType]);
24
+
25
+ const statusCounts = useComputed(() => ({
26
+ active: vehicles.filter((v: Vehicle) => v.status === 'active').length,
27
+ maintenance: vehicles.filter((v: Vehicle) => v.status === 'maintenance').length,
28
+ retired: vehicles.filter((v: Vehicle) => v.status === 'retired').length,
29
+ }), [vehicles]);
30
+
31
+ const handleAddVehicle = () => {
32
+ if (!newPlate.trim()) return;
33
+ addVehicle({ plate: newPlate, type: newType, status: 'active' });
34
+ setNewPlate('');
35
+ setShowAddForm(false);
36
+ };
37
+
38
+ const statusColor = (s: VehicleStatus) =>
39
+ s === 'active' ? 'green' : s === 'maintenance' ? 'yellow' : 'gray';
40
+
41
+ return (
42
+ <Show when={isAdmin} fallback={<Text>Access denied. Admin role required.</Text>}>
43
+ <Stack gap=lg>
44
+ <Row justify=between align=center>
45
+ <Heading level={2}>Fleet Management</Heading>
46
+ <Button onClick={() => setShowAddForm(true)}>Add Vehicle</Button>
47
+ </Row>
48
+
49
+ <Row gap=md>
50
+ <Badge color=green>Active: {statusCounts.active}</Badge>
51
+ <Badge color=yellow>Maintenance: {statusCounts.maintenance}</Badge>
52
+ <Badge color=gray>Retired: {statusCounts.retired}</Badge>
53
+ </Row>
54
+
55
+ <Row gap=sm>
56
+ {(['all', 'sedan', 'suv', 'van', 'luxury'] as const).map((t) => (
57
+ <Button key={t} variant={filterType === t ? 'primary' : 'ghost'} onClick={() => setFilterType(t)}>
58
+ {t.charAt(0).toUpperCase() + t.slice(1)}
59
+ </Button>
60
+ ))}
61
+ </Row>
62
+
63
+ <Show when={showAddForm}>
64
+ <Card>
65
+ <Stack gap=sm>
66
+ <Input label=License Plate value={newPlate} onChange={setNewPlate} />
67
+ <Row gap=sm>
68
+ {(['sedan', 'suv', 'van', 'luxury'] as const).map((t) => (
69
+ <Button key={t} variant={newType === t ? 'primary' : 'ghost'} onClick={() => setNewType(t)}>{t}</Button>
70
+ ))}
71
+ </Row>
72
+ <Row gap=sm>
73
+ <Button onClick={handleAddVehicle}>Save</Button>
74
+ <Button variant=ghost onClick={() => setShowAddForm(false)}>Cancel</Button>
75
+ </Row>
76
+ </Stack>
77
+ </Card>
78
+ </Show>
79
+
80
+ <Each items={filteredVehicles}>
81
+ {(vehicle: Vehicle) => (
82
+ <Card key={vehicle.id}>
83
+ <Row justify=between align=center>
84
+ <Stack gap=xs>
85
+ <Text weight=bold>{vehicle.plate} — {vehicle.type}</Text>
86
+ <Text size=sm color=muted>{vehicle.assignedDriver?.name ?? 'Unassigned'}</Text>
87
+ </Stack>
88
+ <Row gap=sm align=center>
89
+ <Badge color={statusColor(vehicle.status)}>{vehicle.status}</Badge>
90
+ <Button size=sm onClick={() => assignDriver({ vehicleId: vehicle.id })}>Assign</Button>
91
+ <Button size=sm variant=ghost onClick={() => updateStatus({ vehicleId: vehicle.id, status: 'maintenance' })}>
92
+ Service
93
+ </Button>
94
+ </Row>
95
+ </Row>
96
+ </Card>
97
+ )}
98
+ </Each>
99
+ </Stack>
100
+ </Show>
101
+ );
102
+ }
@@ -0,0 +1,95 @@
1
+ import { useState, useWorkflow, useTransition, useField, useComputed, useRole, useQuery, useOnEnter, useOnEvent } from '@mindmatrix/react';
2
+ import { Stack, Row, Text, Button, Show, Each, Card, Badge, Heading, Input } from '@mindmatrix/react';
3
+ import { SurgeZone, SurgeConfig } from '../../models/surge';
4
+
5
+ export default function SurgePricing() {
6
+ const isAdmin = useRole('admin');
7
+ const [zones, setZones] = useField<SurgeZone[]>('surge_zones');
8
+ const [config] = useField<SurgeConfig>('surge_config');
9
+ const [editingZoneId, setEditingZoneId] = useState<string | null>(null);
10
+ const [editMultiplier, setEditMultiplier] = useState('1.0');
11
+ const [threshold, setThreshold] = useState(String(config?.demandRatioThreshold ?? 1.5));
12
+
13
+ const updateMultiplier = useTransition('update_surge_multiplier');
14
+ const toggleSurge = useTransition('toggle_surge');
15
+ const updateThreshold = useTransition('update_surge_threshold');
16
+
17
+ const activeZoneCount = useComputed(
18
+ () => zones.filter((z: SurgeZone) => z.surgeEnabled && z.multiplier > 1.0).length,
19
+ [zones]
20
+ );
21
+
22
+ const startEdit = (zone: SurgeZone) => {
23
+ setEditingZoneId(zone.id);
24
+ setEditMultiplier(String(zone.multiplier));
25
+ };
26
+
27
+ const saveMultiplier = (zoneId: string) => {
28
+ const value = parseFloat(editMultiplier);
29
+ if (isNaN(value) || value < 1.0 || value > 5.0) return;
30
+ updateMultiplier({ zoneId, multiplier: value });
31
+ setEditingZoneId(null);
32
+ };
33
+
34
+ const handleToggle = (zoneId: string, enabled: boolean) => {
35
+ toggleSurge({ zoneId, enabled: !enabled });
36
+ };
37
+
38
+ const saveThreshold = () => {
39
+ const value = parseFloat(threshold);
40
+ if (isNaN(value) || value < 1.0) return;
41
+ updateThreshold({ demandRatioThreshold: value });
42
+ };
43
+
44
+ return (
45
+ <Show when={isAdmin} fallback={<Text>Access denied.</Text>}>
46
+ <Stack gap="lg">
47
+ <Row justify="between" align="center">
48
+ <Heading level={2}>Surge Pricing</Heading>
49
+ <Badge color={activeZoneCount > 0 ? 'red' : 'green'}>
50
+ {activeZoneCount} active surge zone{activeZoneCount !== 1 ? 's' : ''}
51
+ </Badge>
52
+ </Row>
53
+
54
+ <Card>
55
+ <Stack gap="sm">
56
+ <Heading level={4}>Global Threshold</Heading>
57
+ <Text size="sm" color="muted">Demand-to-supply ratio that triggers surge pricing</Text>
58
+ <Row gap="sm" align="center">
59
+ <Input type="number" value={threshold} onChange={setThreshold} min="1.0" step="0.1" style={{ width: 100 }} />
60
+ <Button size="sm" onClick={saveThreshold}>Update</Button>
61
+ </Row>
62
+ </Stack>
63
+ </Card>
64
+
65
+ <Each items={zones}>
66
+ {(zone: SurgeZone) => (
67
+ <Card key={zone.id}>
68
+ <Row justify="between" align="center">
69
+ <Stack gap="xs">
70
+ <Text weight="bold">{zone.name}</Text>
71
+ <Text size="sm" color="muted">Current demand ratio: {zone.currentDemandRatio.toFixed(1)}</Text>
72
+ </Stack>
73
+ <Row gap="sm" align="center">
74
+ <Show when={editingZoneId === zone.id} fallback={
75
+ <Badge color={zone.multiplier > 1.0 ? 'red' : 'gray'}>{zone.multiplier.toFixed(1)}x</Badge>
76
+ }>
77
+ <Input type="number" value={editMultiplier} onChange={setEditMultiplier} min="1.0" max="5.0" step="0.1" style={{ width: 80 }} />
78
+ <Button size="sm" onClick={() => saveMultiplier(zone.id)}>Save</Button>
79
+ <Button size="sm" variant="ghost" onClick={() => setEditingZoneId(null)}>Cancel</Button>
80
+ </Show>
81
+ <Show when={editingZoneId !== zone.id}>
82
+ <Button size="sm" variant="ghost" onClick={() => startEdit(zone)}>Edit</Button>
83
+ </Show>
84
+ <Button size="sm" variant={zone.surgeEnabled ? 'danger' : 'primary'} onClick={() => handleToggle(zone.id, zone.surgeEnabled)}>
85
+ {zone.surgeEnabled ? 'Disable' : 'Enable'}
86
+ </Button>
87
+ </Row>
88
+ </Row>
89
+ </Card>
90
+ )}
91
+ </Each>
92
+ </Stack>
93
+ </Show>
94
+ );
95
+ }
@@ -0,0 +1,87 @@
1
+ import { useState, useWorkflow, useField, useComputed, useRole, useOnEnter } from '@mindmatrix/react';
2
+ import { Stack, Row, Text, Button, Show, Card, Badge, Heading } from '@mindmatrix/react';
3
+ import { RideRequest } from '../models/ride';
4
+ import { DriverStats } from '../models/driver';
5
+ import { ActiveRideCard } from '../components/ActiveRideCard';
6
+
7
+ export default function DriverDashboard() {
8
+ const workflow = useWorkflow('driver-session');
9
+ const [isOnline, setIsOnline] = useState(false);
10
+
11
+ const status = useField<'online' | 'offline' | 'on-trip'>('driver.status');
12
+ const activeRide = useField<RideRequest | null>('driver.activeRide');
13
+ const stats = useField<DriverStats>('driver.todayStats');
14
+
15
+ const todayEarnings = useComputed('driver.todayEarnings', () => {
16
+ return stats.value.completedTrips.reduce((sum, trip) => sum + trip.fare, 0);
17
+ });
18
+
19
+ const acceptanceRate = useComputed('driver.acceptanceRate', () => {
20
+ const { accepted, total } = stats.value.requestStats;
21
+ return total > 0 ? Math.round((accepted / total) * 100) : 100;
22
+ });
23
+
24
+ const role = useRole('driver');
25
+
26
+ useOnEnter(() => {
27
+ workflow.initialize({ driverId: role.userId });
28
+ });
29
+
30
+ const handleToggleOnline = () => {
31
+ const nextState = isOnline ? 'go-offline' : 'go-online';
32
+ workflow.transition(nextState);
33
+ setIsOnline(!isOnline);
34
+ };
35
+
36
+ return (
37
+ <Stack gap="lg" padding="md">
38
+ <Row justify="between" align="center">
39
+ <Heading level={2}>Driver Dashboard</Heading>
40
+ <Badge variant={isOnline ? 'success' : 'neutral'}>
41
+ {isOnline ? 'Online' : 'Offline'}
42
+ </Badge>
43
+ </Row>
44
+
45
+ <Button
46
+ variant={isOnline ? 'outline' : 'primary'}
47
+ size="lg"
48
+ onPress={handleToggleOnline}
49
+ >
50
+ {isOnline ? 'Go Offline' : 'Go Online'}
51
+ </Button>
52
+
53
+ <Row gap="md">
54
+ <Card flex={1}>
55
+ <Text size="sm" color="muted">Today's Earnings</Text>
56
+ <Text size="xl" weight="bold">${todayEarnings.value.toFixed(2)}</Text>
57
+ </Card>
58
+ <Card flex={1}>
59
+ <Text size="sm" color="muted">Trips</Text>
60
+ <Text size="xl" weight="bold">{stats.value.completedTrips.length}</Text>
61
+ </Card>
62
+ <Card flex={1}>
63
+ <Text size="sm" color="muted">Acceptance</Text>
64
+ <Badge variant={acceptanceRate.value >= 85 ? 'success' : 'warning'}>
65
+ {acceptanceRate.value}%
66
+ </Badge>
67
+ </Card>
68
+ </Row>
69
+
70
+ <Show when={activeRide.value !== null}>
71
+ <Heading level={3}>Current Ride</Heading>
72
+ <ActiveRideCard
73
+ ride={activeRide.value!}
74
+ onNavigate={() => workflow.transition('open-navigation')}
75
+ />
76
+ </Show>
77
+
78
+ <Show when={isOnline && !activeRide.value}>
79
+ <Card padding="lg">
80
+ <Text align="center" color="muted">
81
+ Waiting for ride requests...
82
+ </Text>
83
+ </Card>
84
+ </Show>
85
+ </Stack>
86
+ );
87
+ }
@@ -0,0 +1,101 @@
1
+ import { useState, useWorkflow, useField, useComputed, useQuery } from '@mindmatrix/react';
2
+ import { Stack, Row, Text, Button, Show, Each, Card, Badge, Heading } from '@mindmatrix/react';
3
+ import { EarningsRecord, PayoutEntry } from '../models/earnings';
4
+
5
+ type Period = 'daily' | 'weekly' | 'monthly';
6
+
7
+ export default function Earnings() {
8
+ const workflow = useWorkflow('driver-earnings');
9
+ const [period, setPeriod] = useState<Period>('daily');
10
+
11
+ const earningsHistory = useQuery<EarningsRecord[]>('driver.earningsHistory', {
12
+ period,
13
+ limit: 30,
14
+ });
15
+
16
+ const payouts = useQuery<PayoutEntry[]>('driver.payoutHistory', {
17
+ limit: 20,
18
+ });
19
+
20
+ const totalEarnings = useComputed('earnings.total', () => {
21
+ if (!earningsHistory.data) return 0;
22
+ return earningsHistory.data.reduce((sum, record) => sum + record.amount, 0);
23
+ });
24
+
25
+ const tripsCompleted = useComputed('earnings.trips', () => {
26
+ if (!earningsHistory.data) return 0;
27
+ return earningsHistory.data.reduce((sum, record) => sum + record.tripCount, 0);
28
+ });
29
+
30
+ const avgRating = useField<number>('driver.averageRating');
31
+
32
+ return (
33
+ <Stack gap="lg" padding="md">
34
+ <Heading level={2}>Earnings</Heading>
35
+
36
+ <Row gap="sm">
37
+ {(['daily', 'weekly', 'monthly'] as Period[]).map((p) => (
38
+ <Button
39
+ key={p}
40
+ variant={period === p ? 'primary' : 'outline'}
41
+ size="sm"
42
+ onPress={() => setPeriod(p)}
43
+ >
44
+ {p.charAt(0).toUpperCase() + p.slice(1)}
45
+ </Button>
46
+ ))}
47
+ </Row>
48
+
49
+ <Row gap="md">
50
+ <Card flex={1}>
51
+ <Text size="sm" color="muted">Total</Text>
52
+ <Text size="xl" weight="bold">${totalEarnings.value.toFixed(2)}</Text>
53
+ </Card>
54
+ <Card flex={1}>
55
+ <Text size="sm" color="muted">Trips</Text>
56
+ <Text size="xl" weight="bold">{tripsCompleted.value}</Text>
57
+ </Card>
58
+ <Card flex={1}>
59
+ <Text size="sm" color="muted">Rating</Text>
60
+ <Text size="xl" weight="bold">{avgRating.value.toFixed(1)}</Text>
61
+ </Card>
62
+ </Row>
63
+
64
+ <Heading level={3}>Trip History</Heading>
65
+ <Show when={!earningsHistory.isLoading}>
66
+ <Each items={earningsHistory.data ?? []}>
67
+ {(record) => (
68
+ <Card key={record.id} padding="sm">
69
+ <Row justify="between" align="center">
70
+ <Stack gap="xs">
71
+ <Text weight="medium">{record.date}</Text>
72
+ <Text size="sm" color="muted">{record.tripCount} trips</Text>
73
+ </Stack>
74
+ <Text weight="bold">${record.amount.toFixed(2)}</Text>
75
+ </Row>
76
+ </Card>
77
+ )}
78
+ </Each>
79
+ </Show>
80
+
81
+ <Heading level={3}>Payout History</Heading>
82
+ <Show when={!payouts.isLoading}>
83
+ <Each items={payouts.data ?? []}>
84
+ {(payout) => (
85
+ <Card key={payout.id} padding="sm">
86
+ <Row justify="between" align="center">
87
+ <Stack gap="xs">
88
+ <Text weight="medium">{payout.date}</Text>
89
+ <Badge variant={payout.status === 'completed' ? 'success' : 'warning'}>
90
+ {payout.status}
91
+ </Badge>
92
+ </Stack>
93
+ <Text weight="bold">${payout.amount.toFixed(2)}</Text>
94
+ </Row>
95
+ </Card>
96
+ )}
97
+ </Each>
98
+ </Show>
99
+ </Stack>
100
+ );
101
+ }