@hot-updater/console 1.0.0-rc.4 → 1.0.0-rc.41

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 (330) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/ConsoleFeatureUnavailable-DRzeMGRM.js +1 -0
  3. package/.output/public/assets/EventDetails-SB5ygJzS.js +1 -0
  4. package/.output/public/assets/HashValueDisplay-BFX_kY3-.js +1 -0
  5. package/.output/public/assets/InsightsInfo-btYppp0B.js +1 -0
  6. package/.output/public/assets/InsightsPageHeader-Ba8psD8t.js +1 -0
  7. package/.output/public/assets/NotFoundPage-CyRG2eca.js +1 -0
  8. package/.output/public/assets/alert-dialog-Bbk_4-WJ.js +1 -0
  9. package/.output/public/assets/api-DUgI6yOI.js +1 -0
  10. package/.output/public/assets/api-keys-CIO9L_wu.js +1 -0
  11. package/.output/public/assets/api-keys-CmkNKOhS.js +1 -0
  12. package/.output/public/assets/arrow-left-CnMYpqB7.js +1 -0
  13. package/.output/public/assets/arrow-up-right-BtffZA6e.js +1 -0
  14. package/.output/public/assets/chevron-right-BqD5wtFb.js +1 -0
  15. package/.output/public/assets/clipboard-DS9ucUQL.js +1 -0
  16. package/.output/public/assets/console-features-api-BMsI3Bj8.js +1 -0
  17. package/.output/public/assets/dialog-Bqbb7B0N.js +1 -0
  18. package/.output/public/assets/dist-LxKJB-Vu.js +1 -0
  19. package/.output/public/assets/empty-CuT2UhFp.js +1 -0
  20. package/.output/public/assets/field-BBDpjIqu.js +1 -0
  21. package/.output/public/assets/index-BEfQ2_7f.js +11 -0
  22. package/.output/public/assets/input-group-ja_30qaF.js +1 -0
  23. package/.output/public/assets/insights-B0ib9qOE.js +2 -0
  24. package/.output/public/assets/insights-BIMJx-0p.js +1 -0
  25. package/.output/public/assets/insights-CndqcWEj.js +42 -0
  26. package/.output/public/assets/insights-api-B6Ewc-OG.js +1 -0
  27. package/.output/public/assets/insights-usage-rpc-DYEhsCKT.js +1 -0
  28. package/.output/public/assets/insights_.distribution-B26PMy4k.js +2 -0
  29. package/.output/public/assets/insights_.distribution-BBDtqqYS.js +1 -0
  30. package/.output/public/assets/insights_.distribution-DhHmYS8k.js +1 -0
  31. package/.output/public/assets/installations-C3cMuwRx.js +1 -0
  32. package/.output/public/assets/installations-C3yrjwDh.js +2 -0
  33. package/.output/public/assets/installations-D3edlN75.js +1 -0
  34. package/.output/public/assets/key-round-wS_SIZ6b.js +1 -0
  35. package/.output/public/assets/list-CgrYiadM.js +1 -0
  36. package/.output/public/assets/refresh-cw-BYgoNCco.js +1 -0
  37. package/.output/public/assets/remote-config-0i-gDkeU.js +1 -0
  38. package/.output/public/assets/remote-config-An1ROIOQ.js +6 -0
  39. package/.output/public/assets/remote-config-CoeWgA9i.js +1 -0
  40. package/.output/public/assets/routes-DNoaJIQV.js +2 -0
  41. package/.output/public/assets/routes-cWBDtghZ.js +1 -0
  42. package/.output/public/assets/search-DaTDPW25.js +1 -0
  43. package/.output/public/assets/select-CZMQKT7F.js +1 -0
  44. package/.output/public/assets/sidebar-DNVyJPfX.js +3 -0
  45. package/.output/public/assets/smartphone-DpTFgtKf.js +1 -0
  46. package/.output/public/assets/styles-D8mxMvCB.css +2 -0
  47. package/.output/public/assets/switch-CU4-fupc.js +1 -0
  48. package/.output/public/assets/table-Gm4qRWwE.js +1 -0
  49. package/.output/public/assets/tabs-R1I8CRjR.js +1 -0
  50. package/.output/public/assets/undo-2-CmrHjwFc.js +1 -0
  51. package/.output/public/assets/useCompositeListItem-BaPeC9ZO.js +1 -0
  52. package/.output/public/assets/valueToPercent-CH8GCHaL.js +1 -0
  53. package/.output/server/__23tanstack-start-server-fn-resolver-DTAqlLRd.mjs +178 -0
  54. package/.output/server/_libs/@base-ui/react+[...].mjs +6450 -2741
  55. package/.output/server/_libs/@tanstack/react-router+[...].mjs +3802 -4022
  56. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +140 -97
  57. package/.output/server/_libs/lucide-react.mjs +235 -31
  58. package/.output/server/_libs/next-themes.mjs +1 -1
  59. package/.output/server/_libs/react-redux+[...].mjs +1 -1
  60. package/.output/server/_libs/recharts+[...].mjs +483 -901
  61. package/.output/server/_libs/sonner.mjs +1 -1
  62. package/.output/server/_libs/tanstack__history.mjs +68 -41
  63. package/.output/server/_libs/tanstack__query-core.mjs +2010 -309
  64. package/.output/server/_libs/tanstack__react-query.mjs +448 -21
  65. package/.output/server/_libs/verkit.mjs +102 -1
  66. package/.output/server/_ssr/BundleIdDisplay-BNoVW3Dv.mjs +13 -0
  67. package/.output/server/_ssr/ConsoleFeatureUnavailable-BPFa-cmJ.mjs +58 -0
  68. package/.output/server/_ssr/EventDetails-9CeshfdR.mjs +344 -0
  69. package/.output/server/_ssr/HashValueDisplay-DXZlkcps.mjs +55 -0
  70. package/.output/server/_ssr/InsightsInfo-DLNdJQ0m.mjs +75 -0
  71. package/.output/server/_ssr/InsightsPageHeader-DZGVDgvv.mjs +145 -0
  72. package/.output/server/_ssr/NotFoundPage-mYHHg-2s.mjs +37 -0
  73. package/.output/server/_ssr/{dialog-DUaIgpgd.mjs → alert-dialog-Chkim7vZ.mjs} +6 -83
  74. package/.output/server/_ssr/api-C-Il5-sa.mjs +203 -0
  75. package/.output/server/_ssr/api-keys-01FGqj4d.mjs +5 -0
  76. package/.output/server/_ssr/{api-keys-DJml_XoK.mjs → api-keys-B_f9h7ln.mjs} +48 -10
  77. package/.output/server/_ssr/api-keys-rpc-Cf4YCyFc.mjs +52 -0
  78. package/.output/server/_ssr/api-rpc-DRqwxxT_.mjs +296 -0
  79. package/.output/server/_ssr/{auth-BF8mbAgF.mjs → auth-UP0wXs4i.mjs} +1 -1
  80. package/.output/server/_ssr/{auth-rpc-D_hv52-J.mjs → auth-rpc-D8nFpU9C.mjs} +6 -6
  81. package/.output/server/_ssr/auth.server-COZoXu4i.mjs +5425 -0
  82. package/.output/server/_ssr/bundleActivity-M5gzBFNg.mjs +18 -0
  83. package/.output/server/_ssr/config-WVX4IxSR.mjs +15 -0
  84. package/.output/server/_ssr/config.server-Dm2l7w0O.mjs +36 -0
  85. package/.output/server/_ssr/console-access--o9FXikU.mjs +17 -0
  86. package/.output/server/_ssr/console-features-BftSkf3f.mjs +60 -0
  87. package/.output/server/_ssr/console-features-api-CYclUQ4h.mjs +43 -0
  88. package/.output/server/_ssr/console-features-rpc-1WaAkPM_.mjs +19 -0
  89. package/.output/server/_ssr/{createServerFn-B2hcXoPM.mjs → createMiddleware-Cm6Tn0_Y.mjs} +67 -56
  90. package/.output/server/_ssr/{createServerRpc-DB29C_5p.mjs → createServerRpc-RLR4H-Fn.mjs} +2 -2
  91. package/.output/server/_ssr/databaseConfig-C5SkAyUy.mjs +5 -0
  92. package/.output/server/_ssr/{databaseJsonValue-d6I3nAS4.mjs → databaseJsonValue-Csbuzc9Q.mjs} +28 -5
  93. package/.output/server/_ssr/databaseRows-BUuNU-qt.mjs +74 -0
  94. package/.output/server/_ssr/dialog-mfSANqjC.mjs +84 -0
  95. package/.output/server/_ssr/{dist-CyzPaI5v.mjs → dist-wmnTk7w6.mjs} +140 -6
  96. package/.output/server/_ssr/{downloadBundle-B3ert-pY.mjs → downloadBundle-DViuIa9U.mjs} +10 -10
  97. package/.output/server/_ssr/empty-BPayFad3.mjs +60 -0
  98. package/.output/server/_ssr/{empty-plugin-adapters-B5QvGpEl.mjs → empty-plugin-adapters-CPtwX8a7.mjs} +1 -1
  99. package/.output/server/_ssr/failureReports-DKDhVsXA.mjs +33 -0
  100. package/.output/server/_ssr/field-DjZ5t7rH.mjs +189 -0
  101. package/.output/server/_ssr/getBundleChildren-CmjDg6Re.mjs +22 -0
  102. package/.output/server/_ssr/{input-group-Bb8ueylQ.mjs → input-group-Dx-MEkNM.mjs} +3 -52
  103. package/.output/server/_ssr/insights-BopxyJjk.mjs +14 -0
  104. package/.output/server/_ssr/insights-Bvm03d61.mjs +5 -0
  105. package/.output/server/_ssr/insights-CrLAHSDy.mjs +2315 -0
  106. package/.output/server/_ssr/insights-api-Bg8ZV_h_.mjs +122 -0
  107. package/.output/server/_ssr/insights-errors-BjnFUyZj.mjs +34 -0
  108. package/.output/server/_ssr/insights-failures-rpc-tyy5m9Cc.mjs +36 -0
  109. package/.output/server/_ssr/insights-failures-xaa41Buf.mjs +46 -0
  110. package/.output/server/_ssr/{insights-recovery-DSFM-YTL.mjs → insights-recovery-BWQhiZaF.mjs} +9 -2
  111. package/.output/server/_ssr/insights-recovery-rpc-CPJOiJAV.mjs +32 -0
  112. package/.output/server/_ssr/{insights-rpc-DrIQCjpD.mjs → insights-rpc-DoC3hla9.mjs} +22 -14
  113. package/.output/server/_ssr/insights-usage-BpBM0U0c.mjs +29 -0
  114. package/.output/server/_ssr/insights-usage-rpc-CFuTYp3T.mjs +29 -0
  115. package/.output/server/_ssr/insights-usage-rpc-O2ruP4iE.mjs +41 -0
  116. package/.output/server/_ssr/insightsUsage-CzhVjsc8.mjs +56 -0
  117. package/.output/server/_ssr/insights_.distribution-B3BVPD00.mjs +16 -0
  118. package/.output/server/_ssr/insights_.distribution-BMoisxRG.mjs +347 -0
  119. package/.output/server/_ssr/insights_.distribution-BiCcFyrG.mjs +7 -0
  120. package/.output/server/_ssr/{installations-YumqpNpa.mjs → installations-Cx3jIrwl.mjs} +280 -364
  121. package/.output/server/_ssr/installations-DTGPaKXx.mjs +69 -0
  122. package/.output/server/_ssr/installations-osiAuNKr.mjs +5 -0
  123. package/.output/server/_ssr/listReleases-BfUBlCHo.mjs +79 -0
  124. package/.output/server/_ssr/release-adoption-005tQRpo.mjs +55 -0
  125. package/.output/server/_ssr/release-adoption-rpc-BVySDR0L.mjs +41 -0
  126. package/.output/server/_ssr/releaseAdoption-2PJPA09N.mjs +114 -0
  127. package/.output/server/_ssr/releaseManagement-BTCWEY8-.mjs +11 -0
  128. package/.output/server/_ssr/remote-config-CUoAxUMy.mjs +430 -0
  129. package/.output/server/_ssr/remote-config-Cc5P4J-r.mjs +17 -0
  130. package/.output/server/_ssr/remote-config-CjdxcZ9B.mjs +5 -0
  131. package/.output/server/_ssr/remote-config-D2GmbGwI.mjs +2594 -0
  132. package/.output/server/_ssr/remote-config-rpc-kjoZ9PRS.mjs +115 -0
  133. package/.output/server/_ssr/rolldown-runtime-rSIU-vHC.mjs +13 -0
  134. package/.output/server/_ssr/{router-iP95Vqi2.mjs → router-DwGVewL-.mjs} +141 -139
  135. package/.output/server/_ssr/{routes-ivsV2TDu.mjs → routes-C4yxv3JU.mjs} +192 -685
  136. package/.output/server/_ssr/routes-D3_aegLY.mjs +409 -0
  137. package/.output/server/_ssr/runtime.server-BoWf4QhU.mjs +1716 -0
  138. package/.output/server/_ssr/schema-DrOtkRVD.mjs +361 -0
  139. package/.output/server/_ssr/select-DkzM2miy.mjs +107 -0
  140. package/.output/server/_ssr/server-BW0gzB0p.mjs +8325 -0
  141. package/.output/server/_ssr/{createSsrRpc-NHF2PxuK.mjs → sidebar-DPcOijvL.mjs} +107 -47
  142. package/.output/server/_ssr/ssr.mjs +1 -1
  143. package/.output/server/_ssr/start-BfFbHKMK.mjs +4 -0
  144. package/.output/server/_ssr/switch-BqJ2mAwC.mjs +17 -0
  145. package/.output/server/_ssr/{table-D1mL8fJu.mjs → table-R7RY92q9.mjs} +2 -2
  146. package/.output/server/_ssr/tabs-trDcbRkT.mjs +45 -0
  147. package/.output/server/_ssr/updateFailures-H6pnnYK4.mjs +40 -0
  148. package/.output/server/_ssr/{uuidv7-D0QFkPL_.mjs → uuidv7-ctA_XVZA.mjs} +2 -2
  149. package/.output/server/_tanstack-start-manifest_v-BwInaUTw.mjs +139 -0
  150. package/.output/server/index.mjs +315 -154
  151. package/LICENSE +13 -14
  152. package/README.md +82 -72
  153. package/lib/index.d.mts +23 -11
  154. package/lib/vite-internal.mjs +13 -3
  155. package/lib/vite.d.mts +2 -0
  156. package/package.json +14 -14
  157. package/src/components/AppSidebar.tsx +100 -71
  158. package/src/components/ConsoleAccessPage.tsx +5 -1
  159. package/src/components/ConsoleFeatureUnavailable.tsx +80 -0
  160. package/src/components/NotFoundPage.tsx +7 -7
  161. package/src/components/features/FeatureSlots.tsx +103 -0
  162. package/src/components/features/api-keys/apiKeysFeatureUi.tsx +15 -0
  163. package/src/components/features/bundles/BundleMetadata.tsx +8 -5
  164. package/src/components/features/bundles/RolloutCohortsDialog.tsx +1 -1
  165. package/src/components/features/channels/ChannelManagementDialog.tsx +2 -5
  166. package/src/components/features/insights/AppUsage.tsx +366 -0
  167. package/src/components/features/insights/BuiltinBundle.tsx +36 -0
  168. package/src/components/features/insights/ComparedBundlesChart.tsx +310 -0
  169. package/src/components/features/insights/EstimatedCount.tsx +18 -0
  170. package/src/components/features/insights/EventDetails.tsx +400 -39
  171. package/src/components/features/insights/EventHistoryCard.tsx +112 -26
  172. package/src/components/features/insights/EventHistoryList.tsx +2 -2
  173. package/src/components/features/insights/FailureReports.tsx +407 -0
  174. package/src/components/features/insights/InsightsControls.tsx +265 -88
  175. package/src/components/features/insights/InsightsErrorAlert.tsx +14 -1
  176. package/src/components/features/insights/InsightsInfo.tsx +48 -0
  177. package/src/components/features/insights/InsightsOverview.tsx +604 -355
  178. package/src/components/features/insights/InsightsPageHeader.tsx +35 -20
  179. package/src/components/features/insights/InsightsPeriodSelector.tsx +89 -0
  180. package/src/components/features/insights/InstallationHistoryCard.tsx +66 -11
  181. package/src/components/features/insights/InstallationMatchesCard.tsx +1 -1
  182. package/src/components/features/insights/ReleaseActivity.tsx +308 -0
  183. package/src/components/features/insights/ReportingDevicesSummary.tsx +48 -0
  184. package/src/components/features/insights/RollbackButton.tsx +118 -0
  185. package/src/components/features/insights/UpdateFailures.tsx +382 -0
  186. package/src/components/features/insights/insightsFeatureUi.tsx +35 -0
  187. package/src/components/features/releases/ReleaseEditorSheet.tsx +26 -12
  188. package/src/components/features/remote-config/ConditionDialog.tsx +581 -0
  189. package/src/components/features/remote-config/ConditionsCard.tsx +244 -0
  190. package/src/components/features/remote-config/ParameterDialog.tsx +407 -0
  191. package/src/components/features/remote-config/ParametersCard.tsx +244 -0
  192. package/src/components/features/remote-config/PreviewCard.tsx +284 -0
  193. package/src/components/features/remote-config/PublishDialog.tsx +319 -0
  194. package/src/components/features/remote-config/RemoteConfigPage.tsx +353 -0
  195. package/src/components/features/remote-config/RuleSummary.tsx +21 -0
  196. package/src/components/features/remote-config/ValueInput.tsx +148 -0
  197. package/src/components/features/remote-config/VersionsCard.tsx +361 -0
  198. package/src/components/features/remote-config/remoteConfigFeatureUi.tsx +15 -0
  199. package/src/components/features/remote-config/useDateTimeText.ts +16 -0
  200. package/src/components/ui/card.tsx +1 -1
  201. package/src/components/ui/scroll-area.tsx +52 -0
  202. package/src/components/ui/sidebar.tsx +22 -29
  203. package/src/components/ui/tabs.tsx +81 -0
  204. package/src/index.ts +26 -19
  205. package/src/lib/api-keys-api.ts +0 -28
  206. package/src/lib/api-keys-rpc.ts +27 -38
  207. package/src/lib/api-rpc.ts +189 -266
  208. package/src/lib/api.ts +19 -102
  209. package/src/lib/bundle-activity.ts +24 -4
  210. package/src/lib/console-access.ts +20 -0
  211. package/src/lib/console-features-api.ts +68 -0
  212. package/src/lib/console-features-rpc.ts +13 -0
  213. package/src/lib/console-features.ts +97 -0
  214. package/src/lib/insights-api.ts +56 -2
  215. package/src/lib/insights-errors.ts +64 -0
  216. package/src/lib/insights-failures-rpc.ts +36 -0
  217. package/src/lib/insights-failures.ts +130 -0
  218. package/src/lib/insights-recovery-rpc.ts +13 -17
  219. package/src/lib/insights-recovery.ts +10 -27
  220. package/src/lib/insights-retention.ts +21 -0
  221. package/src/lib/insights-rpc.ts +30 -12
  222. package/src/lib/insights-search.ts +93 -0
  223. package/src/lib/insights-usage-rpc.ts +29 -0
  224. package/src/lib/insights-usage.ts +76 -0
  225. package/src/lib/insights-view.ts +34 -5
  226. package/src/lib/release-adoption-api.ts +214 -0
  227. package/src/lib/release-adoption-rpc.ts +49 -0
  228. package/src/lib/release-adoption.ts +164 -0
  229. package/src/lib/remote-config-api.ts +119 -0
  230. package/src/lib/remote-config-draft.ts +652 -0
  231. package/src/lib/remote-config-rpc.ts +196 -0
  232. package/src/lib/scroll-restoration.ts +21 -0
  233. package/src/lib/server/adminInsights.ts +124 -0
  234. package/src/lib/server/auth.server.ts +13 -5
  235. package/src/lib/server/bundleActivity.ts +27 -28
  236. package/src/lib/server/config.server.ts +21 -74
  237. package/src/lib/server/console-feature-apis.ts +70 -0
  238. package/src/lib/server/console-runtime.server.ts +82 -38
  239. package/src/lib/server/downloadBundle.ts +16 -15
  240. package/src/lib/server/failureReports.ts +49 -0
  241. package/src/lib/server/getBundleChildren.ts +31 -122
  242. package/src/lib/server/insightsUsage.ts +63 -0
  243. package/src/lib/server/listReleases.ts +109 -44
  244. package/src/lib/server/releaseAdoption.ts +154 -0
  245. package/src/lib/server/releaseReachability.ts +8 -7
  246. package/src/lib/server/remoteConfig.ts +191 -0
  247. package/src/lib/server/runtime.server.ts +120 -31
  248. package/src/lib/server/updateFailures.ts +65 -0
  249. package/src/routeTree.gen.ts +65 -44
  250. package/src/router.tsx +3 -2
  251. package/src/routes/-installations-search.ts +32 -8
  252. package/src/routes/-releases-search.ts +76 -29
  253. package/src/routes/__root.tsx +10 -4
  254. package/src/routes/api/bundles/$bundleId/download.ts +2 -5
  255. package/src/routes/api-keys.tsx +5 -2
  256. package/src/routes/index.tsx +210 -152
  257. package/src/routes/insights.tsx +133 -18
  258. package/src/routes/insights_.distribution.tsx +422 -0
  259. package/src/routes/installations.tsx +98 -41
  260. package/src/routes/remote-config.tsx +42 -0
  261. package/src/styles.css +9 -0
  262. package/src/vite-internal.ts +15 -3
  263. package/src/vite.ts +2 -0
  264. package/.output/public/assets/InsightsPageHeader-C-Xc2MB9.js +0 -1
  265. package/.output/public/assets/api-keys-C1sgC6ko.js +0 -1
  266. package/.output/public/assets/api-keys-api-LchP65vQ.js +0 -1
  267. package/.output/public/assets/badge-BuMDU0Rf.js +0 -1
  268. package/.output/public/assets/card-BEvx_QKh.js +0 -1
  269. package/.output/public/assets/chart-no-axes-combined-C0G0fQ3J.js +0 -1
  270. package/.output/public/assets/composite-CHTH1Xhh.js +0 -1
  271. package/.output/public/assets/createServerFn-DHc2erub.js +0 -3
  272. package/.output/public/assets/dialog-dyZ1SphZ.js +0 -1
  273. package/.output/public/assets/dist-Bh1gaDfB.js +0 -1
  274. package/.output/public/assets/field-79SVH26s.js +0 -1
  275. package/.output/public/assets/index-BNkxg4jz.js +0 -10
  276. package/.output/public/assets/input-group-BbHfaBzQ.js +0 -1
  277. package/.output/public/assets/insights-D_7uSKfc.js +0 -1
  278. package/.output/public/assets/installations-BzoHWuXw.js +0 -1
  279. package/.output/public/assets/installations-DvjbUqd1.js +0 -2
  280. package/.output/public/assets/lazyRouteComponent-BN0MS05o.js +0 -1
  281. package/.output/public/assets/link-BG06whwB.js +0 -1
  282. package/.output/public/assets/loader-circle-Bbp8LGVC.js +0 -1
  283. package/.output/public/assets/preload-helper-uTix4PVD.js +0 -1
  284. package/.output/public/assets/refresh-cw-DZgiAOMk.js +0 -1
  285. package/.output/public/assets/routes-BFE-oRIT.js +0 -1
  286. package/.output/public/assets/routes-DaPMY31P.js +0 -2
  287. package/.output/public/assets/shield-check-DoIiaaXU.js +0 -1
  288. package/.output/public/assets/signing-BTRhaF5D.js +0 -1
  289. package/.output/public/assets/styles-C7U0oVke.css +0 -2
  290. package/.output/public/assets/table-BBEzJ7Xl.js +0 -1
  291. package/.output/public/assets/useCompositeListItem-BkPDaqyj.js +0 -42
  292. package/.output/server/__23tanstack-start-server-fn-resolver-Dr2ixDgQ.mjs +0 -146
  293. package/.output/server/_ssr/InsightsPageHeader-C5Has8Ry.mjs +0 -60
  294. package/.output/server/_ssr/api-keys-api-CrCZyvnl.mjs +0 -60
  295. package/.output/server/_ssr/api-keys-rpc-CFaNf-q0.mjs +0 -136
  296. package/.output/server/_ssr/api-rpc-B61KL3Eo.mjs +0 -1399
  297. package/.output/server/_ssr/auth.server-DF_shCeq.mjs +0 -47
  298. package/.output/server/_ssr/badge-BgsCP9Dp.mjs +0 -28
  299. package/.output/server/_ssr/bundle-signing-rpc-BBipZnFs.mjs +0 -16
  300. package/.output/server/_ssr/bundle-signing.server-Co_zHdJx.mjs +0 -12
  301. package/.output/server/_ssr/bundleActivity-D3_fIda2.mjs +0 -27
  302. package/.output/server/_ssr/card-DrbxCPtG.mjs +0 -86
  303. package/.output/server/_ssr/chart-qxa-_30a.mjs +0 -162
  304. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +0 -13
  305. package/.output/server/_ssr/config.server-CBr6PDj5.mjs +0 -368
  306. package/.output/server/_ssr/databaseRows-5bCRTxm-.mjs +0 -126
  307. package/.output/server/_ssr/deleteBundle-BAq3JuGr.mjs +0 -66
  308. package/.output/server/_ssr/field-CqNUlqUq.mjs +0 -74
  309. package/.output/server/_ssr/getBundleChildren-COMt16m_.mjs +0 -50
  310. package/.output/server/_ssr/insights-B-DUbksX.mjs +0 -502
  311. package/.output/server/_ssr/insights-recovery-rpc-P71FH66n.mjs +0 -37
  312. package/.output/server/_ssr/insightsContract-fjJDBeEE.mjs +0 -303
  313. package/.output/server/_ssr/insightsRecovery-CqcosG7B.mjs +0 -149
  314. package/.output/server/_ssr/installations-BQrppti7.mjs +0 -29
  315. package/.output/server/_ssr/routes-BDabsmc7.mjs +0 -37
  316. package/.output/server/_ssr/runtime.server-jmZOKIQM.mjs +0 -438
  317. package/.output/server/_ssr/server-p7yrVBfD.mjs +0 -7468
  318. package/.output/server/_ssr/signing-CxCbnFCt.mjs +0 -134
  319. package/.output/server/_ssr/start-DpViQGnt.mjs +0 -4
  320. package/.output/server/_tanstack-start-manifest_v-Cwpu0C9Q.mjs +0 -100
  321. package/src/components/features/bundles/BundleActivityChart.tsx +0 -95
  322. package/src/components/features/bundles/BundleInsightsSummary.tsx +0 -121
  323. package/src/components/features/signing/BundleSigningPage.tsx +0 -139
  324. package/src/lib/bundle-signing-api.ts +0 -14
  325. package/src/lib/bundle-signing-rpc.ts +0 -16
  326. package/src/lib/server/bundle-signing.server.ts +0 -23
  327. package/src/lib/server/deleteBundle.ts +0 -145
  328. package/src/lib/server/insightsHistory.ts +0 -52
  329. package/src/lib/server/insightsRecovery.ts +0 -186
  330. package/src/routes/signing.tsx +0 -37
@@ -0,0 +1,244 @@
1
+ import {
2
+ ArrowDown,
3
+ ArrowUp,
4
+ GitBranch,
5
+ Pencil,
6
+ Plus,
7
+ Trash2,
8
+ } from "lucide-react";
9
+ import { type MouseEvent, useState } from "react";
10
+
11
+ import {
12
+ AlertDialog,
13
+ AlertDialogAction,
14
+ AlertDialogCancel,
15
+ AlertDialogContent,
16
+ AlertDialogDescription,
17
+ AlertDialogFooter,
18
+ AlertDialogHeader,
19
+ AlertDialogTitle,
20
+ } from "@/components/ui/alert-dialog";
21
+ import { Button } from "@/components/ui/button";
22
+ import {
23
+ Card,
24
+ CardContent,
25
+ CardDescription,
26
+ CardHeader,
27
+ CardTitle,
28
+ } from "@/components/ui/card";
29
+ import {
30
+ Empty,
31
+ EmptyContent,
32
+ EmptyDescription,
33
+ EmptyHeader,
34
+ EmptyMedia,
35
+ EmptyTitle,
36
+ } from "@/components/ui/empty";
37
+ import {
38
+ parametersUsing,
39
+ type RemoteConfigCondition,
40
+ type RemoteConfigTemplate,
41
+ } from "@/lib/remote-config-draft";
42
+
43
+ import { ConditionDialog } from "./ConditionDialog";
44
+ import { RuleSummary } from "./RuleSummary";
45
+ import { useDateTimeText } from "./useDateTimeText";
46
+
47
+ /** The draft's conditions in priority order: the first match decides a value. */
48
+ export function ConditionsCard({
49
+ template,
50
+ onSaveCondition,
51
+ onRemoveCondition,
52
+ onMoveCondition,
53
+ }: {
54
+ readonly template: RemoteConfigTemplate;
55
+ readonly onSaveCondition: (
56
+ previousName: string | null,
57
+ condition: RemoteConfigCondition,
58
+ ) => void;
59
+ readonly onRemoveCondition: (name: string) => void;
60
+ readonly onMoveCondition: (index: number, offset: -1 | 1) => void;
61
+ }) {
62
+ const [editing, setEditing] = useState<{
63
+ readonly name: string | null;
64
+ } | null>(null);
65
+ const [removing, setRemoving] = useState<string | null>(null);
66
+ const { dateTimeText } = useDateTimeText();
67
+ const { conditions } = template;
68
+ const editingName = editing?.name ?? null;
69
+ const removingUsers =
70
+ removing === null ? [] : parametersUsing(template, removing);
71
+
72
+ const remove = (name: string) => {
73
+ if (parametersUsing(template, name).length === 0) onRemoveCondition(name);
74
+ else setRemoving(name);
75
+ };
76
+
77
+ return (
78
+ <Card className="overflow-hidden shadow-sm">
79
+ <CardHeader className="flex-row flex-wrap items-center justify-between gap-3 space-y-0 border-b px-4 py-3 sm:px-6">
80
+ <div className="min-w-0">
81
+ <CardTitle className="text-sm">
82
+ <h2>Conditions</h2>
83
+ </CardTitle>
84
+ <CardDescription className="text-xs">
85
+ Listed by priority: when several match a device, the first wins.
86
+ </CardDescription>
87
+ </div>
88
+ <Button
89
+ className="min-h-11 sm:min-h-7"
90
+ onClick={() => setEditing({ name: null })}
91
+ >
92
+ <Plus data-icon="inline-start" />
93
+ Add condition
94
+ </Button>
95
+ </CardHeader>
96
+ <CardContent className="p-0">
97
+ {conditions.length === 0 ? (
98
+ <Empty className="py-10">
99
+ <EmptyHeader>
100
+ <EmptyMedia variant="icon">
101
+ <GitBranch />
102
+ </EmptyMedia>
103
+ <EmptyTitle>No conditions</EmptyTitle>
104
+ <EmptyDescription>
105
+ Add a condition to give some devices other values: a platform,
106
+ channel, app version range, cohort, or a share of installs.
107
+ </EmptyDescription>
108
+ </EmptyHeader>
109
+ <EmptyContent>
110
+ <Button
111
+ className="min-h-11 sm:min-h-7"
112
+ onClick={() => setEditing({ name: null })}
113
+ >
114
+ <Plus data-icon="inline-start" />
115
+ Add condition
116
+ </Button>
117
+ </EmptyContent>
118
+ </Empty>
119
+ ) : (
120
+ <ol aria-label="Conditions" className="divide-y">
121
+ {conditions.map((condition, index) => {
122
+ const users = parametersUsing(template, condition.name);
123
+ return (
124
+ <li
125
+ className="grid gap-3 px-4 py-3 sm:px-6 md:grid-cols-[auto_minmax(0,1fr)_auto] md:items-center"
126
+ key={condition.name}
127
+ >
128
+ <span
129
+ aria-label={`Priority ${index + 1}`}
130
+ className="flex size-6 items-center justify-center rounded-md bg-muted text-xs font-medium tabular-nums"
131
+ >
132
+ {index + 1}
133
+ </span>
134
+ <div className="min-w-0">
135
+ <p className="truncate text-sm font-medium">
136
+ {condition.name}
137
+ </p>
138
+ <RuleSummary
139
+ condition={condition}
140
+ dateTimeText={dateTimeText}
141
+ />
142
+ <p className="mt-1 text-xs text-muted-foreground">
143
+ {users.length === 0
144
+ ? "No parameter uses it yet"
145
+ : `Sets ${users.length === 1 ? "1 parameter" : `${users.length} parameters`}: ${users.slice(0, 3).join(", ")}${users.length > 3 ? "…" : ""}`}
146
+ </p>
147
+ </div>
148
+ <div className="flex flex-wrap gap-1 md:justify-end">
149
+ <Button
150
+ aria-label={`Move ${condition.name} up`}
151
+ className="size-11 md:size-7"
152
+ disabled={index === 0}
153
+ onClick={() => onMoveCondition(index, -1)}
154
+ size="icon"
155
+ variant="ghost"
156
+ >
157
+ <ArrowUp />
158
+ </Button>
159
+ <Button
160
+ aria-label={`Move ${condition.name} down`}
161
+ className="size-11 md:size-7"
162
+ disabled={index === conditions.length - 1}
163
+ onClick={() => onMoveCondition(index, 1)}
164
+ size="icon"
165
+ variant="ghost"
166
+ >
167
+ <ArrowDown />
168
+ </Button>
169
+ <Button
170
+ aria-label={`Edit ${condition.name}`}
171
+ className="min-h-11 md:min-h-7"
172
+ onClick={() => setEditing({ name: condition.name })}
173
+ size="sm"
174
+ variant="outline"
175
+ >
176
+ <Pencil data-icon="inline-start" />
177
+ Edit
178
+ </Button>
179
+ <Button
180
+ aria-label={`Delete ${condition.name}`}
181
+ className="size-11 md:size-7"
182
+ onClick={() => remove(condition.name)}
183
+ size="icon"
184
+ variant="ghost"
185
+ >
186
+ <Trash2 />
187
+ </Button>
188
+ </div>
189
+ </li>
190
+ );
191
+ })}
192
+ </ol>
193
+ )}
194
+ </CardContent>
195
+ <ConditionDialog
196
+ condition={
197
+ editingName === null
198
+ ? null
199
+ : (conditions.find(({ name }) => name === editingName) ?? null)
200
+ }
201
+ onOpenChange={(open) => {
202
+ if (!open) setEditing(null);
203
+ }}
204
+ onSave={(condition) => onSaveCondition(editingName, condition)}
205
+ open={editing !== null}
206
+ takenNames={conditions
207
+ .map(({ name }) => name)
208
+ .filter((name) => name !== editingName)}
209
+ />
210
+ <AlertDialog
211
+ onOpenChange={(open) => {
212
+ if (!open) setRemoving(null);
213
+ }}
214
+ open={removing !== null}
215
+ >
216
+ <AlertDialogContent>
217
+ <AlertDialogHeader>
218
+ <AlertDialogTitle>Delete {removing}?</AlertDialogTitle>
219
+ <AlertDialogDescription>
220
+ {removingUsers.length === 1
221
+ ? "1 parameter has a value for it"
222
+ : `${removingUsers.length} parameters have a value for it`}{" "}
223
+ ({removingUsers.join(", ")}). Those values are removed too, so
224
+ matching devices get the default value after you publish.
225
+ </AlertDialogDescription>
226
+ </AlertDialogHeader>
227
+ <AlertDialogFooter>
228
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
229
+ <AlertDialogAction
230
+ onClick={(event: MouseEvent<HTMLButtonElement>) => {
231
+ event.preventDefault();
232
+ if (removing !== null) onRemoveCondition(removing);
233
+ setRemoving(null);
234
+ }}
235
+ variant="destructive"
236
+ >
237
+ Delete condition
238
+ </AlertDialogAction>
239
+ </AlertDialogFooter>
240
+ </AlertDialogContent>
241
+ </AlertDialog>
242
+ </Card>
243
+ );
244
+ }
@@ -0,0 +1,407 @@
1
+ import { Plus, Trash2 } from "lucide-react";
2
+ import { type FormEvent, useId, useState } from "react";
3
+
4
+ import { Button } from "@/components/ui/button";
5
+ import {
6
+ Dialog,
7
+ DialogContent,
8
+ DialogDescription,
9
+ DialogFooter,
10
+ DialogHeader,
11
+ DialogTitle,
12
+ } from "@/components/ui/dialog";
13
+ import {
14
+ Field,
15
+ FieldDescription,
16
+ FieldError,
17
+ FieldGroup,
18
+ FieldLabel,
19
+ FieldLegend,
20
+ FieldSet,
21
+ } from "@/components/ui/field";
22
+ import { Input } from "@/components/ui/input";
23
+ import {
24
+ Select,
25
+ SelectContent,
26
+ SelectGroup,
27
+ SelectItem,
28
+ SelectTrigger,
29
+ SelectValue,
30
+ } from "@/components/ui/select";
31
+ import {
32
+ conditionalValueFor,
33
+ parameterKeyError,
34
+ type RemoteConfigCondition,
35
+ type RemoteConfigParameter,
36
+ type RemoteConfigParameterValue,
37
+ type RemoteConfigValueType,
38
+ VALUE_TYPE_LABELS,
39
+ } from "@/lib/remote-config-draft";
40
+
41
+ import { ConditionDialog } from "./ConditionDialog";
42
+ import { RuleSummary } from "./RuleSummary";
43
+ import { useDateTimeText } from "./useDateTimeText";
44
+ import {
45
+ initialValueText,
46
+ parameterValueError,
47
+ ValueInput,
48
+ } from "./ValueInput";
49
+
50
+ const VALUE_TYPES = Object.keys(VALUE_TYPE_LABELS) as RemoteConfigValueType[];
51
+
52
+ /** How the app reads a value of each type; JSON arrives as text to parse. */
53
+ const READ_METHODS: Readonly<Record<RemoteConfigValueType, string>> = {
54
+ STRING: "getString",
55
+ NUMBER: "getNumber",
56
+ BOOLEAN: "getBoolean",
57
+ JSON: "getString",
58
+ };
59
+
60
+ /** A value converted to another type: kept when it still fits, reset when not. */
61
+ const retype = (
62
+ value: RemoteConfigParameterValue,
63
+ valueType: RemoteConfigValueType,
64
+ ): RemoteConfigParameterValue =>
65
+ "value" in value && parameterValueError(valueType, value) !== null
66
+ ? { value: initialValueText(valueType) }
67
+ : value;
68
+
69
+ /**
70
+ * Adds or edits a parameter: its key, type, default value, and a value for
71
+ * any of the template's conditions, which it can also add on the spot.
72
+ */
73
+ export function ParameterDialog({
74
+ open,
75
+ onOpenChange,
76
+ parameterKey,
77
+ parameter,
78
+ takenKeys,
79
+ conditions,
80
+ onSave,
81
+ onAddCondition,
82
+ }: {
83
+ readonly open: boolean;
84
+ readonly onOpenChange: (open: boolean) => void;
85
+ /** The key of the parameter to edit, or null for a new one. */
86
+ readonly parameterKey: string | null;
87
+ readonly parameter: RemoteConfigParameter | null;
88
+ readonly takenKeys: readonly string[];
89
+ readonly conditions: readonly RemoteConfigCondition[];
90
+ readonly onSave: (key: string, parameter: RemoteConfigParameter) => void;
91
+ readonly onAddCondition: (condition: RemoteConfigCondition) => void;
92
+ }) {
93
+ return (
94
+ <Dialog onOpenChange={onOpenChange} open={open}>
95
+ <DialogContent className="max-h-[calc(100svh-2rem)] overflow-y-auto data-nested-dialog-open:after:absolute data-nested-dialog-open:after:inset-0 data-nested-dialog-open:after:rounded-xl data-nested-dialog-open:after:bg-background/70 sm:max-w-2xl">
96
+ {open ? (
97
+ <ParameterForm
98
+ conditions={conditions}
99
+ onAddCondition={onAddCondition}
100
+ onCancel={() => onOpenChange(false)}
101
+ onSave={(key, next) => {
102
+ onSave(key, next);
103
+ onOpenChange(false);
104
+ }}
105
+ parameter={parameter}
106
+ parameterKey={parameterKey}
107
+ takenKeys={takenKeys}
108
+ />
109
+ ) : null}
110
+ </DialogContent>
111
+ </Dialog>
112
+ );
113
+ }
114
+
115
+ function ParameterForm({
116
+ parameterKey,
117
+ parameter,
118
+ takenKeys,
119
+ conditions,
120
+ onSave,
121
+ onCancel,
122
+ onAddCondition,
123
+ }: {
124
+ readonly parameterKey: string | null;
125
+ readonly parameter: RemoteConfigParameter | null;
126
+ readonly takenKeys: readonly string[];
127
+ readonly conditions: readonly RemoteConfigCondition[];
128
+ readonly onSave: (key: string, parameter: RemoteConfigParameter) => void;
129
+ readonly onCancel: () => void;
130
+ readonly onAddCondition: (condition: RemoteConfigCondition) => void;
131
+ }) {
132
+ const id = useId();
133
+ const { dateTimeText } = useDateTimeText();
134
+ const [key, setKey] = useState(parameterKey ?? "");
135
+ const [valueType, setValueType] = useState<RemoteConfigValueType>(
136
+ parameter?.valueType ?? "STRING",
137
+ );
138
+ const [description, setDescription] = useState(parameter?.description ?? "");
139
+ // Saved trimmed, as the server stores it.
140
+ const descriptionTooLong = description.trim().length > 256;
141
+ const [defaultValue, setDefaultValue] = useState<RemoteConfigParameterValue>(
142
+ parameter?.defaultValue ?? { value: "" },
143
+ );
144
+ const [conditionalValues, setConditionalValues] = useState<
145
+ Readonly<Record<string, RemoteConfigParameterValue>>
146
+ >(parameter?.conditionalValues ?? {});
147
+ const [submitted, setSubmitted] = useState(false);
148
+ const [addingCondition, setAddingCondition] = useState(false);
149
+
150
+ const keyError = parameterKeyError(key.trim(), takenKeys);
151
+ const valued = conditions.filter(
152
+ ({ name }) => conditionalValueFor(conditionalValues, name) !== undefined,
153
+ );
154
+ const unvalued = conditions.filter(
155
+ ({ name }) => conditionalValueFor(conditionalValues, name) === undefined,
156
+ );
157
+ const invalid =
158
+ keyError !== null ||
159
+ descriptionTooLong ||
160
+ [defaultValue, ...Object.values(conditionalValues)].some(
161
+ (value) => parameterValueError(valueType, value) !== null,
162
+ );
163
+
164
+ const changeType = (next: RemoteConfigValueType) => {
165
+ setValueType(next);
166
+ setDefaultValue(retype(defaultValue, next));
167
+ setConditionalValues(
168
+ Object.fromEntries(
169
+ Object.entries(conditionalValues).map(([name, value]) => [
170
+ name,
171
+ retype(value, next),
172
+ ]),
173
+ ),
174
+ );
175
+ };
176
+
177
+ const addValueFor = (name: string) =>
178
+ setConditionalValues({
179
+ ...conditionalValues,
180
+ [name]: { value: initialValueText(valueType) },
181
+ });
182
+
183
+ const submit = (event: FormEvent<HTMLFormElement>) => {
184
+ event.preventDefault();
185
+ setSubmitted(true);
186
+ if (invalid) return;
187
+ const kept = Object.fromEntries(
188
+ valued.map(({ name }) => [
189
+ name,
190
+ conditionalValueFor(conditionalValues, name)!,
191
+ ]),
192
+ );
193
+ onSave(key.trim(), {
194
+ valueType,
195
+ defaultValue,
196
+ ...(Object.keys(kept).length === 0 ? {} : { conditionalValues: kept }),
197
+ ...(description.trim().length === 0
198
+ ? {}
199
+ : { description: description.trim() }),
200
+ });
201
+ };
202
+
203
+ return (
204
+ <>
205
+ <form className="flex flex-col gap-4" noValidate onSubmit={submit}>
206
+ <DialogHeader>
207
+ <DialogTitle>
208
+ {parameterKey === null ? "Add parameter" : "Edit parameter"}
209
+ </DialogTitle>
210
+ <DialogDescription>
211
+ The app reads it with{" "}
212
+ <code className="font-mono">
213
+ hotUpdater.remoteConfig.{READ_METHODS[valueType]}("
214
+ {key.trim() || "key"}")
215
+ </code>
216
+ , which is null when neither the server nor the app's defaults have
217
+ a value.
218
+ </DialogDescription>
219
+ </DialogHeader>
220
+ <FieldGroup>
221
+ <div className="grid gap-4 sm:grid-cols-[minmax(0,1fr)_11rem]">
222
+ <Field data-invalid={(submitted && keyError !== null) || undefined}>
223
+ <FieldLabel htmlFor={`${id}-key`}>Key</FieldLabel>
224
+ <Input
225
+ autoComplete="off"
226
+ className="min-h-11 font-mono sm:min-h-9"
227
+ id={`${id}-key`}
228
+ maxLength={257}
229
+ onChange={(event) => setKey(event.target.value)}
230
+ placeholder="welcome_message"
231
+ spellCheck={false}
232
+ value={key}
233
+ />
234
+ <FieldError>{submitted ? keyError : null}</FieldError>
235
+ </Field>
236
+ <Field>
237
+ <FieldLabel htmlFor={`${id}-type`}>Type</FieldLabel>
238
+ <Select
239
+ items={VALUE_TYPE_LABELS}
240
+ onValueChange={(value) => {
241
+ if (value !== null)
242
+ changeType(value as RemoteConfigValueType);
243
+ }}
244
+ value={valueType}
245
+ >
246
+ <SelectTrigger
247
+ className="min-h-11 w-full sm:min-h-9"
248
+ id={`${id}-type`}
249
+ >
250
+ <SelectValue />
251
+ </SelectTrigger>
252
+ <SelectContent>
253
+ <SelectGroup>
254
+ {VALUE_TYPES.map((type) => (
255
+ <SelectItem key={type} value={type}>
256
+ {VALUE_TYPE_LABELS[type]}
257
+ </SelectItem>
258
+ ))}
259
+ </SelectGroup>
260
+ </SelectContent>
261
+ </Select>
262
+ </Field>
263
+ </div>
264
+ <Field data-invalid={descriptionTooLong || undefined}>
265
+ <FieldLabel htmlFor={`${id}-description`}>Description</FieldLabel>
266
+ <Input
267
+ autoComplete="off"
268
+ className="min-h-11 sm:min-h-9"
269
+ id={`${id}-description`}
270
+ onChange={(event) => setDescription(event.target.value)}
271
+ placeholder="Optional: what the app does with it"
272
+ value={description}
273
+ />
274
+ <FieldError>
275
+ {descriptionTooLong ? "Use 256 characters or fewer." : null}
276
+ </FieldError>
277
+ </Field>
278
+ <ValueInput
279
+ id={`${id}-default`}
280
+ label="Default value"
281
+ onChange={setDefaultValue}
282
+ showErrors={submitted}
283
+ value={defaultValue}
284
+ valueType={valueType}
285
+ />
286
+ <FieldSet>
287
+ <FieldLegend variant="label">Conditional values</FieldLegend>
288
+ <FieldDescription>
289
+ Devices that match a condition get its value. When several match,
290
+ the first in the Conditions list wins.
291
+ </FieldDescription>
292
+ {valued.length > 0 ? (
293
+ <ul className="flex flex-col gap-3">
294
+ {valued.map((condition) => (
295
+ <li className="rounded-lg border p-3" key={condition.name}>
296
+ <div className="mb-2 flex items-start justify-between gap-2">
297
+ <div className="min-w-0">
298
+ <p className="truncate text-sm font-medium">
299
+ {condition.name}
300
+ </p>
301
+ <RuleSummary
302
+ condition={condition}
303
+ dateTimeText={dateTimeText}
304
+ />
305
+ </div>
306
+ <Button
307
+ aria-label={`Remove the value for ${condition.name}`}
308
+ className="size-11 sm:size-7"
309
+ onClick={() => {
310
+ const { [condition.name]: _removed, ...rest } =
311
+ conditionalValues;
312
+ setConditionalValues(rest);
313
+ }}
314
+ size="icon-sm"
315
+ type="button"
316
+ variant="ghost"
317
+ >
318
+ <Trash2 />
319
+ </Button>
320
+ </div>
321
+ <ValueInput
322
+ id={`${id}-${condition.name}`}
323
+ label={
324
+ <>
325
+ Value
326
+ <span className="sr-only"> for {condition.name}</span>
327
+ </>
328
+ }
329
+ onChange={(value) =>
330
+ setConditionalValues({
331
+ ...conditionalValues,
332
+ [condition.name]: value,
333
+ })
334
+ }
335
+ showErrors={submitted}
336
+ value={
337
+ conditionalValueFor(conditionalValues, condition.name)!
338
+ }
339
+ valueType={valueType}
340
+ />
341
+ </li>
342
+ ))}
343
+ </ul>
344
+ ) : null}
345
+ <div className="flex flex-wrap gap-2">
346
+ {unvalued.length > 0 ? (
347
+ <Select
348
+ items={Object.fromEntries(
349
+ unvalued.map(({ name }) => [name, name]),
350
+ )}
351
+ onValueChange={(value) => {
352
+ if (value !== null) addValueFor(value);
353
+ }}
354
+ value={null}
355
+ >
356
+ <SelectTrigger
357
+ aria-label="Add a value for a condition"
358
+ className="min-h-11 w-full sm:min-h-9 sm:w-64"
359
+ >
360
+ <Plus aria-hidden="true" className="size-3.5" />
361
+ <SelectValue placeholder="Add a value for a condition" />
362
+ </SelectTrigger>
363
+ <SelectContent>
364
+ <SelectGroup>
365
+ {unvalued.map(({ name }) => (
366
+ <SelectItem key={name} value={name}>
367
+ {name}
368
+ </SelectItem>
369
+ ))}
370
+ </SelectGroup>
371
+ </SelectContent>
372
+ </Select>
373
+ ) : null}
374
+ <Button
375
+ className="min-h-11 sm:min-h-9"
376
+ onClick={() => setAddingCondition(true)}
377
+ type="button"
378
+ variant="outline"
379
+ >
380
+ <Plus data-icon="inline-start" />
381
+ New condition
382
+ </Button>
383
+ </div>
384
+ </FieldSet>
385
+ </FieldGroup>
386
+ <DialogFooter>
387
+ <Button onClick={onCancel} type="button" variant="outline">
388
+ Cancel
389
+ </Button>
390
+ <Button type="submit">
391
+ {parameterKey === null ? "Add parameter" : "Save parameter"}
392
+ </Button>
393
+ </DialogFooter>
394
+ </form>
395
+ <ConditionDialog
396
+ condition={null}
397
+ onOpenChange={setAddingCondition}
398
+ onSave={(condition) => {
399
+ onAddCondition(condition);
400
+ addValueFor(condition.name);
401
+ }}
402
+ open={addingCondition}
403
+ takenNames={conditions.map(({ name }) => name)}
404
+ />
405
+ </>
406
+ );
407
+ }