@hot-updater/console 0.36.7 → 1.0.0-rc.0

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 (266) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/AnalyticsCapabilityContext-BCke57-7.js +1 -0
  3. package/.output/public/assets/BundleActivityChart-CnsBe_1J.js +42 -0
  4. package/.output/public/assets/BundleIdDisplay-Dky75zLU.js +1 -0
  5. package/.output/public/assets/alert-C-1KvDW8.js +1 -0
  6. package/.output/public/assets/analytics-Bqz0c-BT.js +1 -0
  7. package/.output/public/assets/analytics-api-CJdrTLLO.js +1 -0
  8. package/.output/public/assets/api-B_Rh-uzC.js +1 -0
  9. package/.output/public/assets/api-keys-BurCFhzH.js +1 -0
  10. package/.output/public/assets/api-keys-api-BEDJdQg3.js +1 -0
  11. package/.output/public/assets/badge-Bf0fZ-Gx.js +1 -0
  12. package/.output/public/assets/chart-no-axes-combined-FaYMGmQS.js +1 -0
  13. package/.output/public/assets/createServerFn-gM2-LFBy.js +3 -0
  14. package/.output/public/assets/dialog-BS60BRr2.js +1 -0
  15. package/.output/public/assets/dist-DspNKfMj.js +1 -0
  16. package/.output/public/assets/download-O-yNBc9H.js +1 -0
  17. package/.output/public/assets/field-Buq148-A.js +1 -0
  18. package/.output/public/assets/index-kFhd2S78.js +10 -0
  19. package/.output/public/assets/installations-BrQ-od41.js +2 -0
  20. package/.output/public/assets/installations-CGlZ3ROz.js +1 -0
  21. package/.output/public/assets/lazyRouteComponent-rPzgA7S-.js +1 -0
  22. package/.output/public/assets/loader-circle-DCYF-wJl.js +1 -0
  23. package/.output/public/assets/preload-helper-TKOBYGYD.js +1 -0
  24. package/.output/public/assets/refresh-cw-n8XYbPPj.js +1 -0
  25. package/.output/public/assets/routes-CrdokJ80.js +1 -0
  26. package/.output/public/assets/routes-DcQnVajO.js +2 -0
  27. package/.output/public/assets/search-DLhbWw3W.js +1 -0
  28. package/.output/public/assets/shield-check-DE_hVuHu.js +1 -0
  29. package/.output/public/assets/signing-HRexfjsH.js +1 -0
  30. package/.output/public/assets/styles-eB4EtvFS.css +2 -0
  31. package/.output/public/assets/table-D8CLieHX.js +1 -0
  32. package/.output/public/assets/textarea-YJLTVAoq.js +1 -0
  33. package/.output/public/assets/useMutation-JsGVjQMY.js +1 -0
  34. package/.output/public/assets/useOpenInteractionType-4cRD5xO0.js +1 -0
  35. package/.output/server/__23tanstack-start-server-fn-resolver-DBP9TQ7K.mjs +146 -0
  36. package/.output/server/_libs/@base-ui/react+[...].mjs +16124 -0
  37. package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +1554 -0
  38. package/.output/server/_libs/@tanstack/react-router+[...].mjs +41 -10
  39. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +132 -0
  40. package/.output/server/_libs/d3-array.mjs +231 -0
  41. package/.output/server/_libs/d3-color.mjs +330 -0
  42. package/.output/server/_libs/d3-format.mjs +252 -0
  43. package/.output/server/_libs/d3-interpolate.mjs +207 -0
  44. package/.output/server/_libs/d3-path.mjs +77 -0
  45. package/.output/server/_libs/d3-scale+[...].mjs +1907 -0
  46. package/.output/server/_libs/d3-shape.mjs +818 -0
  47. package/.output/server/_libs/decimal.js-light.mjs +884 -0
  48. package/.output/server/_libs/es-toolkit.mjs +1134 -0
  49. package/.output/server/_libs/eventemitter3.mjs +236 -0
  50. package/.output/server/_libs/immer.mjs +532 -0
  51. package/.output/server/_libs/lucide-react.mjs +141 -13
  52. package/.output/server/_libs/next-themes.mjs +2 -2
  53. package/.output/server/_libs/react-redux+[...].mjs +205 -0
  54. package/.output/server/_libs/recharts+[...].mjs +14117 -0
  55. package/.output/server/_libs/sonner.mjs +2 -2
  56. package/.output/server/_libs/tanstack__query-core.mjs +114 -1
  57. package/.output/server/_libs/tanstack__react-query.mjs +3 -4
  58. package/.output/server/_runtime.mjs +10 -1
  59. package/.output/server/_ssr/AnalyticsCapabilityContext-ui4DX8GO.mjs +15 -0
  60. package/.output/server/_ssr/BundleActivityChart-BiB9ZORH.mjs +288 -0
  61. package/.output/server/_ssr/BundleIdDisplay-Czk6iR6p.mjs +13 -0
  62. package/.output/server/_ssr/alert-Dim3MGY5.mjs +86 -0
  63. package/.output/server/_ssr/analytics-DtMTn8fn.mjs +858 -0
  64. package/.output/server/_ssr/analytics-api-Cg1M5v_H.mjs +71 -0
  65. package/.output/server/_ssr/analytics-input-fRRRn0uA.mjs +96 -0
  66. package/.output/server/_ssr/analytics-rpc-DY5uXvHT.mjs +155 -0
  67. package/.output/server/_ssr/api-BTSv1tZb.mjs +282 -0
  68. package/.output/server/_ssr/api-keys-BnM_o2nS.mjs +300 -0
  69. package/.output/server/_ssr/api-keys-api-CIywsSF5.mjs +60 -0
  70. package/.output/server/_ssr/api-keys-rpc-BSp1wkIV.mjs +136 -0
  71. package/.output/server/_ssr/api-rpc-cG5wd_tO.mjs +1468 -0
  72. package/.output/server/_ssr/auth-BF8mbAgF.mjs +21 -0
  73. package/.output/server/_ssr/auth-rpc-BrJ6obHJ.mjs +24 -0
  74. package/.output/server/_ssr/auth.server-DwKoZoQM.mjs +50 -0
  75. package/.output/server/_ssr/badge-DCxbEeof.mjs +26 -0
  76. package/.output/server/_ssr/bundle-signing-rpc-COoQhW8E.mjs +16 -0
  77. package/.output/server/_ssr/bundle-signing.server-D5u8KkMp.mjs +51 -0
  78. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +13 -0
  79. package/.output/server/_ssr/config.server-DEiLhKmy.mjs +361 -0
  80. package/.output/server/_ssr/{createServerFn-DzU0k62V.mjs → createServerFn-B2hcXoPM.mjs} +1 -1
  81. package/.output/server/_ssr/createServerRpc-DB29C_5p.mjs +12 -0
  82. package/.output/server/_ssr/{sidebar-Cv_H0_Jv.mjs → createSsrRpc-9P0S0lJ3.mjs} +68 -50
  83. package/.output/server/_ssr/databaseRows-CwbNwYSl.mjs +166 -0
  84. package/.output/server/_ssr/deleteBundle-Cu_XOMt0.mjs +66 -0
  85. package/.output/server/_ssr/dialog-CQuRMNZ4.mjs +170 -0
  86. package/.output/server/_ssr/dist-BVSLTpoP.mjs +250 -0
  87. package/.output/server/_ssr/downloadBundle-CNnYxYO2.mjs +24 -0
  88. package/.output/server/_ssr/{empty-plugin-adapters-B4HCzgmM.mjs → empty-plugin-adapters-B5QvGpEl.mjs} +1 -1
  89. package/.output/server/_ssr/field-DRIXKjZG.mjs +74 -0
  90. package/.output/server/_ssr/{getBundleChildren-CVtSiH5t.mjs → getBundleChildren-BcRMFoDF.mjs} +10 -18
  91. package/.output/server/_ssr/installations-B-azgAvX.mjs +19 -0
  92. package/.output/server/_ssr/installations-Ctvzp2JH.mjs +535 -0
  93. package/.output/server/_ssr/router-C-KUBfOi.mjs +513 -0
  94. package/.output/server/_ssr/routes-BM4ht9jb.mjs +2286 -0
  95. package/.output/server/_ssr/routes-Z7TwC6nC.mjs +37 -0
  96. package/.output/server/_ssr/runtime.server-awTLiiXW.mjs +527 -0
  97. package/.output/server/_ssr/server-BjzmvXnO.mjs +7468 -0
  98. package/.output/server/_ssr/signing-k8GxyBDF.mjs +202 -0
  99. package/.output/server/_ssr/ssr.mjs +4 -5740
  100. package/.output/server/_ssr/start-DpViQGnt.mjs +4 -0
  101. package/.output/server/_ssr/table-v1yCvYo7.mjs +53 -0
  102. package/.output/server/_ssr/textarea-DYHnskhb.mjs +14 -0
  103. package/.output/server/_ssr/uuidv7-BbJ-9D_I.mjs +31 -0
  104. package/.output/server/_tanstack-start-manifest_v-CH4StzJu.mjs +108 -0
  105. package/.output/server/index.mjs +256 -53
  106. package/README.md +29 -8
  107. package/lib/index.d.mts +36 -0
  108. package/lib/index.mjs +4 -0
  109. package/lib/vite-internal.mjs +127 -0
  110. package/lib/vite.d.mts +10 -0
  111. package/lib/vite.mjs +5 -0
  112. package/package.json +51 -29
  113. package/public/apple-touch-icon.png +0 -0
  114. package/public/favicon-16x16.png +0 -0
  115. package/public/favicon-32x32.png +0 -0
  116. package/public/favicon.ico +0 -0
  117. package/public/logo.svg +1 -0
  118. package/public/manifest.json +30 -0
  119. package/public/robots.txt +3 -0
  120. package/src/components/AppSidebar.tsx +159 -0
  121. package/src/components/BundleIdDisplay.tsx +18 -0
  122. package/src/components/ChannelBadge.tsx +31 -0
  123. package/src/components/ConsoleAccessPage.tsx +122 -0
  124. package/src/components/EnabledStatusIcon.tsx +54 -0
  125. package/src/components/HashValueDisplay.tsx +85 -0
  126. package/src/components/HotUpdaterLogo.tsx +30 -0
  127. package/src/components/NotFoundPage.tsx +33 -0
  128. package/src/components/PlatformIcon.tsx +39 -0
  129. package/src/components/RolloutPercentageBadge.tsx +28 -0
  130. package/src/components/ThemeProvider.tsx +116 -0
  131. package/src/components/TimestampDisplay.tsx +41 -0
  132. package/src/components/features/analytics/ActivityChart.tsx +150 -0
  133. package/src/components/features/analytics/AnalyticsCapabilityContext.tsx +24 -0
  134. package/src/components/features/analytics/AnalyticsControls.tsx +115 -0
  135. package/src/components/features/analytics/AnalyticsErrorAlert.tsx +21 -0
  136. package/src/components/features/analytics/AnalyticsOverview.tsx +208 -0
  137. package/src/components/features/analytics/BundleDistribution.tsx +133 -0
  138. package/src/components/features/analytics/BundleSelector.tsx +114 -0
  139. package/src/components/features/analytics/InstallationHistoryCard.tsx +248 -0
  140. package/src/components/features/analytics/InstallationMatchesCard.tsx +132 -0
  141. package/src/components/features/analytics/InstallationPageHeader.tsx +138 -0
  142. package/src/components/features/analytics/InstallationPagination.tsx +48 -0
  143. package/src/components/features/analytics/InstallationSearch.tsx +230 -0
  144. package/src/components/features/analytics/UpdateOutcomes.tsx +147 -0
  145. package/src/components/features/api-keys/ApiKeysPage.tsx +377 -0
  146. package/src/components/features/bundles/BundleActivityChart.tsx +193 -0
  147. package/src/components/features/bundles/BundleAnalyticsSummary.tsx +123 -0
  148. package/src/components/features/bundles/BundleChildrenPanel.tsx +201 -0
  149. package/src/components/features/bundles/BundleMetadata.tsx +136 -0
  150. package/src/components/features/bundles/RolloutCohortsDialog.tsx +201 -0
  151. package/src/components/features/channels/ChannelManagementDialog.tsx +228 -0
  152. package/src/components/features/releases/ReleaseEditorSheet.tsx +879 -0
  153. package/src/components/features/releases/ReleaseStateBadge.tsx +20 -0
  154. package/src/components/features/signing/BundleSigningPage.tsx +247 -0
  155. package/src/components/ui/alert-dialog.tsx +185 -0
  156. package/src/components/ui/alert.tsx +76 -0
  157. package/src/components/ui/badge.tsx +49 -0
  158. package/src/components/ui/button.tsx +58 -0
  159. package/src/components/ui/card.tsx +89 -0
  160. package/src/components/ui/chart.tsx +371 -0
  161. package/src/components/ui/combobox.tsx +303 -0
  162. package/src/components/ui/dialog.tsx +156 -0
  163. package/src/components/ui/dropdown-menu.tsx +270 -0
  164. package/src/components/ui/field.tsx +235 -0
  165. package/src/components/ui/input-group.tsx +147 -0
  166. package/src/components/ui/input.tsx +19 -0
  167. package/src/components/ui/label.tsx +25 -0
  168. package/src/components/ui/progress.tsx +84 -0
  169. package/src/components/ui/select.tsx +205 -0
  170. package/src/components/ui/separator.tsx +23 -0
  171. package/src/components/ui/sheet.tsx +132 -0
  172. package/src/components/ui/sidebar.tsx +779 -0
  173. package/src/components/ui/skeleton.tsx +13 -0
  174. package/src/components/ui/slider.tsx +56 -0
  175. package/src/components/ui/sonner.tsx +45 -0
  176. package/src/components/ui/switch.tsx +25 -0
  177. package/src/components/ui/table.tsx +114 -0
  178. package/src/components/ui/textarea.tsx +18 -0
  179. package/src/components/ui/toggle-group.tsx +89 -0
  180. package/src/components/ui/toggle.tsx +43 -0
  181. package/src/components/ui/tooltip.tsx +66 -0
  182. package/src/hooks/use-mobile.ts +30 -0
  183. package/src/index.ts +48 -0
  184. package/src/lib/analytics-api.ts +145 -0
  185. package/src/lib/analytics-error.ts +19 -0
  186. package/src/lib/analytics-input.ts +187 -0
  187. package/src/lib/analytics-overview.ts +170 -0
  188. package/src/lib/analytics-rpc.ts +218 -0
  189. package/src/lib/api-keys-api.ts +69 -0
  190. package/src/lib/api-keys-rpc.ts +87 -0
  191. package/src/lib/api-rpc.ts +478 -0
  192. package/src/lib/api.ts +444 -0
  193. package/src/lib/auth-rpc.ts +16 -0
  194. package/src/lib/bundle-signing-api.ts +14 -0
  195. package/src/lib/bundle-signing-rpc.ts +16 -0
  196. package/src/lib/constants.ts +1 -0
  197. package/src/lib/git.ts +51 -0
  198. package/src/lib/server/auth.server.ts +43 -0
  199. package/src/lib/server/bundle-signing.server.ts +100 -0
  200. package/src/lib/server/config.server.ts +109 -0
  201. package/src/lib/server/console-runtime.server.ts +70 -0
  202. package/src/lib/server/deleteBundle.ts +145 -0
  203. package/src/lib/server/downloadBundle.ts +46 -0
  204. package/src/lib/server/getBundleChildren.ts +130 -0
  205. package/src/lib/server/listReleases.ts +61 -0
  206. package/src/lib/server/releaseActivity.ts +27 -0
  207. package/src/lib/server/releaseReachability.ts +95 -0
  208. package/src/lib/server/runtime.server.ts +172 -0
  209. package/src/lib/utils.ts +9 -0
  210. package/src/logo.svg +1 -0
  211. package/src/routeTree.gen.ts +199 -0
  212. package/src/router.tsx +21 -0
  213. package/src/routes/-installations-search.ts +19 -0
  214. package/src/routes/-releases-search.ts +74 -0
  215. package/src/routes/__root.tsx +174 -0
  216. package/src/routes/analytics.tsx +139 -0
  217. package/src/routes/api/auth/$.ts +19 -0
  218. package/src/routes/api/bundles/$bundleId/download.ts +17 -0
  219. package/src/routes/api-keys.tsx +37 -0
  220. package/src/routes/index.tsx +982 -0
  221. package/src/routes/installations.tsx +194 -0
  222. package/src/routes/signing.tsx +37 -0
  223. package/src/styles.css +152 -0
  224. package/src/virtual-modules.d.ts +21 -0
  225. package/src/vite-env.d.ts +1 -0
  226. package/src/vite-internal.ts +195 -0
  227. package/src/vite.ts +12 -0
  228. package/.output/public/assets/dist-DJX53faP.js +0 -3
  229. package/.output/public/assets/index-Iylis5kY.js +0 -10
  230. package/.output/public/assets/routes-HT21hs0C.js +0 -54
  231. package/.output/public/assets/styles-C0Re4quB.css +0 -2
  232. package/.output/server/__23tanstack-start-server-fn-resolver-CgwgZ0-i.mjs +0 -66
  233. package/.output/server/_libs/@floating-ui/core+[...].mjs +0 -698
  234. package/.output/server/_libs/@floating-ui/dom+[...].mjs +0 -644
  235. package/.output/server/_libs/@floating-ui/react-dom+[...].mjs +0 -839
  236. package/.output/server/_libs/@radix-ui/react-alert-dialog+[...].mjs +0 -2138
  237. package/.output/server/_libs/@radix-ui/react-popper+[...].mjs +0 -308
  238. package/.output/server/_libs/@radix-ui/react-select+[...].mjs +0 -1038
  239. package/.output/server/_libs/@tanstack/devtools-event-client+[...].mjs +0 -196
  240. package/.output/server/_libs/@tanstack/form-core+[...].mjs +0 -3228
  241. package/.output/server/_libs/@tanstack/react-form+[...].mjs +0 -430
  242. package/.output/server/_libs/@tanstack/react-table+[...].mjs +0 -2382
  243. package/.output/server/_libs/radix-ui__number.mjs +0 -6
  244. package/.output/server/_libs/radix-ui__primitive.mjs +0 -9
  245. package/.output/server/_libs/radix-ui__react-arrow.mjs +0 -23
  246. package/.output/server/_libs/radix-ui__react-collection.mjs +0 -78
  247. package/.output/server/_libs/radix-ui__react-direction.mjs +0 -13
  248. package/.output/server/_libs/radix-ui__react-label.mjs +0 -22
  249. package/.output/server/_libs/radix-ui__react-separator.mjs +0 -30
  250. package/.output/server/_libs/radix-ui__react-slider.mjs +0 -510
  251. package/.output/server/_libs/radix-ui__react-switch.mjs +0 -157
  252. package/.output/server/_libs/radix-ui__react-tooltip.mjs +0 -493
  253. package/.output/server/_ssr/api-rpc-BIbDUKSz.mjs +0 -292
  254. package/.output/server/_ssr/config.server-BSS366KT.mjs +0 -41
  255. package/.output/server/_ssr/deleteBundle-BgbZTtpW.mjs +0 -141
  256. package/.output/server/_ssr/dist-KboydRVk.mjs +0 -120
  257. package/.output/server/_ssr/router-C3hqEy3k.mjs +0 -307
  258. package/.output/server/_ssr/routes-B0lQvvlC.mjs +0 -3048
  259. package/.output/server/_ssr/start-02sLteYG.mjs +0 -4
  260. package/.output/server/_ssr/storageProfile-FVkjPoGb.mjs +0 -9
  261. package/.output/server/_tanstack-start-manifest_v-Bpx52WOU.mjs +0 -20
  262. package/.output/server/node_modules/tslib/modules/index.js +0 -70
  263. package/.output/server/node_modules/tslib/modules/package.json +0 -3
  264. package/.output/server/node_modules/tslib/package.json +0 -47
  265. package/.output/server/node_modules/tslib/tslib.js +0 -484
  266. package/.output/server/package.json +0 -9
@@ -1,3048 +0,0 @@
1
- import { r as __toESM } from "../_runtime.mjs";
2
- import { c as createServerFn, i as TSS_SERVER_FUNCTION } from "./createServerFn-DzU0k62V.mjs";
3
- import { c as getPatchBaseFileHash, d as isValidCohort, f as normalizeCohortValue, l as getPatchFileHash, n as NUMERIC_COHORT_SIZE, o as getNumericCohortRolloutPosition, p as normalizeRolloutCohortCount, s as getPatchBaseBundleId, t as INVALID_COHORT_ERROR_MESSAGE } from "./dist-KboydRVk.mjs";
4
- import { u as require_react } from "../_libs/@floating-ui/react-dom+[...].mjs";
5
- import { A as Slot, P as require_jsx_runtime, a as Overlay2, c as Title2, d as Description, f as Overlay, g as Trigger, h as Title, i as Description2, l as Close, m as Root, n as Cancel, o as Portal2, p as Portal, r as Content2, s as Root2, t as Action, u as Content } from "../_libs/@radix-ui/react-alert-dialog+[...].mjs";
6
- import { C as ChevronLeft, E as ArrowRight, S as ChevronRight, T as Check, _ as ExternalLink, a as RotateCcw, b as CircleCheck, c as Package, d as Minus, f as LoaderCircle, g as FingerprintPattern, h as Funnel, n as TriangleAlert, o as Plus, p as List, r as Trash2, t as X, v as Download, w as ChevronDown, x as ChevronUp, y as CircleX } from "../_libs/lucide-react.mjs";
7
- import { t as cva } from "../_libs/class-variance-authority+clsx.mjs";
8
- import { a as SelectItemIndicator, c as SelectScrollDownButton$1, d as SelectValue$1, f as SelectViewport, i as SelectItem$1, l as SelectScrollUpButton$1, n as SelectContent$1, o as SelectItemText, r as SelectIcon, s as SelectPortal, t as Select, u as SelectTrigger$1 } from "../_libs/@radix-ui/react-select+[...].mjs";
9
- import { t as Root$1 } from "../_libs/radix-ui__react-label.mjs";
10
- import { i as SliderTrack, n as SliderRange, r as SliderThumb, t as Slider } from "../_libs/radix-ui__react-slider.mjs";
11
- import { n as SwitchThumb, t as Switch } from "../_libs/radix-ui__react-switch.mjs";
12
- import { C as useIsMobile, S as cn, _ as Skeleton, g as SidebarTrigger, n as Input, r as Separator$1, t as Button, v as Tooltip$1, x as TooltipTrigger, y as TooltipContent } from "./sidebar-Cv_H0_Jv.mjs";
13
- import { m as useSearch, p as useNavigate } from "../_libs/@tanstack/react-router+[...].mjs";
14
- import { n as useStore, t as useForm } from "../_libs/@tanstack/react-form+[...].mjs";
15
- import { i as useQueryClient, n as useQuery, t as useMutation } from "../_libs/tanstack__react-query.mjs";
16
- import { n as toast } from "../_libs/sonner.mjs";
17
- import { t as getServerFnById } from "../__23tanstack-start-server-fn-resolver-CgwgZ0-i.mjs";
18
- import { t as normalizeRange } from "../_libs/verkit.mjs";
19
- import { i as getCoreRowModel, n as useReactTable, r as createColumnHelper, t as flexRender } from "../_libs/@tanstack/react-table+[...].mjs";
20
- import { n as require_dayjs_min, t as require_relativeTime } from "../_libs/dayjs.mjs";
21
- //#region node_modules/.nitro/vite/services/ssr/assets/routes-B0lQvvlC.js
22
- var import_react = /* @__PURE__ */ __toESM(require_react());
23
- var import_jsx_runtime = require_jsx_runtime();
24
- var import_dayjs_min = /* @__PURE__ */ __toESM(require_dayjs_min());
25
- var import_relativeTime = /* @__PURE__ */ __toESM(require_relativeTime());
26
- var extractTimestampFromUUIDv7 = (uuid) => {
27
- const timestampHex = uuid.split("-").join("").slice(0, 12);
28
- return Number.parseInt(timestampHex, 16);
29
- };
30
- function createUUIDv7FromTimestampHex(timestampHex) {
31
- const randomBytes = new Uint8Array(10);
32
- crypto.getRandomValues(randomBytes);
33
- const randomHex = Array.from(randomBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
34
- const randA = randomHex.slice(0, 3);
35
- const randBHex = randomHex.slice(3, 19);
36
- const versionAndRandA = `7${randA}`;
37
- const variantAndFirstRandB = (128 | parseInt(randBHex.slice(0, 2), 16) & 63).toString(16).padStart(2, "0");
38
- return [
39
- timestampHex.slice(0, 8),
40
- timestampHex.slice(8, 12),
41
- versionAndRandA,
42
- variantAndFirstRandB + randBHex.slice(2, 4),
43
- randBHex.slice(4, 16)
44
- ].join("-");
45
- }
46
- var createUUIDv7 = () => createUUIDv7FromTimestampHex(Date.now().toString(16).padStart(12, "0"));
47
- function Dialog$1({ ...props }) {
48
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Root, {
49
- "data-slot": "dialog",
50
- ...props
51
- });
52
- }
53
- function DialogTrigger({ ...props }) {
54
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Trigger, {
55
- "data-slot": "dialog-trigger",
56
- ...props
57
- });
58
- }
59
- function DialogPortal({ ...props }) {
60
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Portal, {
61
- "data-slot": "dialog-portal",
62
- ...props
63
- });
64
- }
65
- function DialogOverlay({ className, ...props }) {
66
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Overlay, {
67
- "data-slot": "dialog-overlay",
68
- className: cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/80 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-50", className),
69
- ...props
70
- });
71
- }
72
- function DialogContent({ className, children, showCloseButton = true, ...props }) {
73
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogPortal, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogOverlay, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Content, {
74
- "data-slot": "dialog-content",
75
- className: cn("bg-background data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-4 rounded-xl p-4 text-xs/relaxed ring-1 duration-100 sm:max-w-sm fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2", className),
76
- ...props,
77
- children: [children, showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Close, {
78
- "data-slot": "dialog-close",
79
- asChild: true,
80
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
81
- variant: "ghost",
82
- className: "absolute top-2 right-2",
83
- size: "icon-sm",
84
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
85
- className: "sr-only",
86
- children: "Close"
87
- })]
88
- })
89
- })]
90
- })] });
91
- }
92
- function DialogHeader({ className, ...props }) {
93
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
94
- "data-slot": "dialog-header",
95
- className: cn("gap-1 flex flex-col", className),
96
- ...props
97
- });
98
- }
99
- function DialogFooter({ className, showCloseButton = false, children, ...props }) {
100
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
101
- "data-slot": "dialog-footer",
102
- className: cn("gap-2 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className),
103
- ...props,
104
- children: [children, showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Close, {
105
- asChild: true,
106
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
107
- variant: "outline",
108
- children: "Close"
109
- })
110
- })]
111
- });
112
- }
113
- function DialogTitle({ className, ...props }) {
114
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Title, {
115
- "data-slot": "dialog-title",
116
- className: cn("text-sm font-medium", className),
117
- ...props
118
- });
119
- }
120
- function DialogDescription({ className, ...props }) {
121
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Description, {
122
- "data-slot": "dialog-description",
123
- className: cn("text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed *:[a]:underline *:[a]:underline-offset-3", className),
124
- ...props
125
- });
126
- }
127
- function Sheet({ ...props }) {
128
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Root, {
129
- "data-slot": "sheet",
130
- ...props
131
- });
132
- }
133
- function SheetPortal({ ...props }) {
134
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Portal, {
135
- "data-slot": "sheet-portal",
136
- ...props
137
- });
138
- }
139
- function SheetOverlay({ className, ...props }) {
140
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Overlay, {
141
- "data-slot": "sheet-overlay",
142
- className: cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/80 duration-100 data-ending-style:opacity-0 data-starting-style:opacity-0 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 z-50", className),
143
- ...props
144
- });
145
- }
146
- function SheetContent({ className, children, overlayClassName, side = "right", showCloseButton = true, ...props }) {
147
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SheetPortal, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SheetOverlay, { className: overlayClassName }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Content, {
148
- "data-slot": "sheet-content",
149
- "data-side": side,
150
- className: cn("bg-background data-open:animate-in data-closed:animate-out data-[side=right]:data-closed:slide-out-to-right-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=top]:data-closed:slide-out-to-top-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:fade-out-0 data-open:fade-in-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=bottom]:data-open:slide-in-from-bottom-10 fixed z-50 flex flex-col bg-clip-padding text-xs/relaxed shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=bottom]:border-t data-[side=left]:inset-y-0 data-[side=left]:left-0 data-[side=left]:h-full data-[side=left]:w-3/4 data-[side=left]:border-r data-[side=right]:inset-y-0 data-[side=right]:right-0 data-[side=right]:h-full data-[side=right]:w-3/4 data-[side=right]:border-l data-[side=top]:inset-x-0 data-[side=top]:top-0 data-[side=top]:h-auto data-[side=top]:border-b data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm", className),
151
- ...props,
152
- children: [children, showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Close, {
153
- "data-slot": "sheet-close",
154
- asChild: true,
155
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
156
- variant: "ghost",
157
- className: "absolute top-4 right-4",
158
- size: "icon-sm",
159
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
160
- className: "sr-only",
161
- children: "Close"
162
- })]
163
- })
164
- })]
165
- })] });
166
- }
167
- function SheetHeader({ className, ...props }) {
168
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
169
- "data-slot": "sheet-header",
170
- className: cn("gap-1.5 p-6 flex flex-col", className),
171
- ...props
172
- });
173
- }
174
- function SheetTitle({ className, ...props }) {
175
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Title, {
176
- "data-slot": "sheet-title",
177
- className: cn("text-foreground text-sm font-medium", className),
178
- ...props
179
- });
180
- }
181
- function SheetDescription({ className, ...props }) {
182
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Description, {
183
- "data-slot": "sheet-description",
184
- className: cn("text-muted-foreground text-xs/relaxed", className),
185
- ...props
186
- });
187
- }
188
- function BundleIdDisplay({ bundleId, className }) {
189
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
190
- translate: "no",
191
- className: cn("break-all font-mono text-xs tabular-nums", className),
192
- children: bundleId
193
- });
194
- }
195
- function AppleIcon({ className }) {
196
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
197
- "aria-label": "iOS",
198
- viewBox: "0 0 24 24",
199
- xmlns: "http://www.w3.org/2000/svg",
200
- className,
201
- fill: "currentColor",
202
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12.152 6.896c-.948 0-2.415-1.078-3.96-1.04-2.04.027-3.91 1.183-4.961 3.014-2.117 3.675-.546 9.103 1.519 12.09 1.013 1.454 2.208 3.09 3.792 3.039 1.52-.065 2.09-.987 3.935-.987 1.831 0 2.35.987 3.96.948 1.637-.026 2.676-1.48 3.676-2.948 1.156-1.688 1.636-3.325 1.662-3.415-.039-.013-3.182-1.221-3.22-4.857-.026-3.04 2.48-4.494 2.597-4.559-1.429-2.09-3.623-2.324-4.39-2.376-2-.156-3.675 1.09-4.61 1.09zM15.53 3.83c.843-1.012 1.4-2.427 1.245-3.83-1.207.052-2.662.805-3.532 1.818-.78.896-1.454 2.338-1.273 3.714 1.338.104 2.715-.688 3.559-1.701" })
203
- });
204
- }
205
- function AndroidIcon({ className }) {
206
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
207
- "aria-label": "Android",
208
- viewBox: "0 0 24 24",
209
- xmlns: "http://www.w3.org/2000/svg",
210
- className,
211
- fill: "currentColor",
212
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M18.4395 5.5586c-.675 1.1664-1.352 2.3318-2.0274 3.498-.0366-.0155-.0742-.0286-.1113-.043-1.8249-.6957-3.484-.8-4.42-.787-1.8551.0185-3.3544.4643-4.2597.8203-.084-.1494-1.7526-3.021-2.0215-3.4864a1.1451 1.1451 0 0 0-.1406-.1914c-.3312-.364-.9054-.4859-1.379-.203-.475.282-.7136.9361-.3886 1.5019 1.9466 3.3696-.0966-.2158 1.9473 3.3593.0172.031-.4946.2642-1.3926 1.0177C2.8987 12.176.452 14.772 0 18.9902h24c-.119-1.1108-.3686-2.099-.7461-3.0683-.7438-1.9118-1.8435-3.2928-2.7402-4.1836a12.1048 12.1048 0 0 0-2.1309-1.6875c.6594-1.122 1.312-2.2559 1.9649-3.3848.2077-.3615.1886-.7956-.0079-1.1191a1.1001 1.1001 0 0 0-.8515-.5332c-.5225-.0536-.9392.3128-1.0488.5449zm-.0391 8.461c.3944.5926.324 1.3306-.1563 1.6503-.4799.3197-1.188.0985-1.582-.4941-.3944-.5927-.324-1.3307.1563-1.6504.4727-.315 1.1812-.1086 1.582.4941zM7.207 13.5273c.4803.3197.5506 1.0577.1563 1.6504-.394.5926-1.1038.8138-1.584.4941-.48-.3197-.5503-1.0577-.1563-1.6504.4008-.6021 1.1087-.8106 1.584-.4941z" })
213
- });
214
- }
215
- function PlatformIcon({ platform, className }) {
216
- if (platform === "ios") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppleIcon, { className });
217
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AndroidIcon, { className });
218
- }
219
- function BundleBasicInfo({ bundle }) {
220
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
221
- className: "mt-1 flex flex-col gap-3 text-sm",
222
- children: [
223
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
224
- className: "flex flex-wrap items-center gap-2",
225
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
226
- className: "flex items-center gap-2",
227
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlatformIcon, {
228
- platform: bundle.platform,
229
- className: "h-4 w-4"
230
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
231
- className: "font-medium",
232
- children: bundle.platform === "ios" ? "iOS" : "Android"
233
- })]
234
- })
235
- }),
236
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
237
- className: "flex flex-wrap items-start gap-2",
238
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
239
- className: "font-medium text-muted-foreground",
240
- children: "Bundle"
241
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
242
- className: "min-w-0 basis-full sm:basis-auto",
243
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
244
- bundleId: bundle.id,
245
- maxLength: 18,
246
- fullOnMobile: true
247
- })
248
- })]
249
- }),
250
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
251
- className: "flex flex-wrap items-center gap-2",
252
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
253
- className: "font-medium text-muted-foreground",
254
- children: "Channel"
255
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
256
- className: "text-xs text-foreground",
257
- translate: "no",
258
- children: bundle.channel
259
- })]
260
- }),
261
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
262
- className: "flex flex-wrap items-center gap-2",
263
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
264
- className: "font-medium text-muted-foreground",
265
- children: "Platform"
266
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
267
- className: "text-xs text-foreground",
268
- children: bundle.platform === "ios" ? "iOS" : "Android"
269
- })]
270
- })
271
- ]
272
- });
273
- }
274
- var badgeVariants = cva("inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", {
275
- variants: { variant: {
276
- default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
277
- secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
278
- destructive: "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
279
- outline: "text-foreground"
280
- } },
281
- defaultVariants: { variant: "default" }
282
- });
283
- function Badge({ className, variant = "default", asChild = false, ...props }) {
284
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(asChild ? Slot : "span", {
285
- "data-slot": "badge",
286
- "data-variant": variant,
287
- className: cn(badgeVariants({ variant }), className),
288
- ...props
289
- });
290
- }
291
- function Textarea({ className, ...props }) {
292
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", {
293
- "data-slot": "textarea",
294
- className: cn("flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", className),
295
- ...props
296
- });
297
- }
298
- function InputGroup({ className, ...props }) {
299
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
300
- "data-slot": "input-group",
301
- className: cn("border-input bg-input/20 dark:bg-input/30 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/30 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 h-7 rounded-md border transition-colors has-data-[align=block-end]:rounded-md has-data-[align=block-start]:rounded-md has-[[data-slot=input-group-control]:focus-visible]:ring-[2px] has-[[data-slot][aria-invalid=true]]:ring-[2px] has-[textarea]:rounded-md has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5 [[data-slot=combobox-content]_&]:focus-within:border-inherit [[data-slot=combobox-content]_&]:focus-within:ring-0 group/input-group relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto", className),
302
- ...props
303
- });
304
- }
305
- var inputGroupAddonVariants = cva("text-muted-foreground **:data-[slot=kbd]:bg-muted-foreground/10 h-auto gap-1 py-2 text-xs/relaxed font-medium group-data-[disabled=true]/input-group:opacity-50 **:data-[slot=kbd]:rounded-[calc(var(--radius-sm)-2px)] **:data-[slot=kbd]:px-1 **:data-[slot=kbd]:text-[0.625rem] [&>svg:not([class*='size-'])]:size-3.5 flex cursor-text items-center justify-center select-none", {
306
- variants: { align: {
307
- "inline-start": "pl-2 has-[>button]:ml-[-0.275rem] has-[>kbd]:ml-[-0.275rem] order-first",
308
- "inline-end": "pr-2 has-[>button]:mr-[-0.275rem] has-[>kbd]:mr-[-0.275rem] order-last",
309
- "block-start": "px-2 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2 order-first w-full justify-start",
310
- "block-end": "px-2 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2 order-last w-full justify-start"
311
- } },
312
- defaultVariants: { align: "inline-start" }
313
- });
314
- function InputGroupAddon({ className, align = "inline-start", ...props }) {
315
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
316
- "data-slot": "input-group-addon",
317
- "data-align": align,
318
- className: cn(inputGroupAddonVariants({ align }), className),
319
- ...props
320
- });
321
- }
322
- cva("gap-2 rounded-md text-xs/relaxed shadow-none flex items-center", {
323
- variants: { size: {
324
- xs: "h-5 gap-1 rounded-[calc(var(--radius-sm)-2px)] px-1 [&>svg:not([class*='size-'])]:size-3",
325
- sm: "",
326
- "icon-xs": "size-6 p-0 has-[>svg]:p-0",
327
- "icon-sm": "size-8 p-0 has-[>svg]:p-0"
328
- } },
329
- defaultVariants: { size: "xs" }
330
- });
331
- function InputGroupText({ className, ...props }) {
332
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
333
- className: cn("text-muted-foreground gap-2 text-xs/relaxed [&_svg:not([class*='size-'])]:size-4 flex items-center [&_svg]:pointer-events-none", className),
334
- ...props
335
- });
336
- }
337
- function InputGroupInput({ className, ...props }) {
338
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
339
- "data-slot": "input-group-control",
340
- className: cn("rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent flex-1", className),
341
- ...props
342
- });
343
- }
344
- function Label$1({ className, ...props }) {
345
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Root$1, {
346
- "data-slot": "label",
347
- className: cn("gap-2 text-xs/relaxed leading-none font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed", className),
348
- ...props
349
- });
350
- }
351
- function Slider$1({ className, defaultValue, value, min = 0, max = 100, ...props }) {
352
- const _values = import_react.useMemo(() => Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max], [
353
- value,
354
- defaultValue,
355
- min,
356
- max
357
- ]);
358
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Slider, {
359
- "data-slot": "slider",
360
- defaultValue,
361
- value,
362
- min,
363
- max,
364
- className: cn("relative flex w-full touch-none select-none items-center", className),
365
- ...props,
366
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SliderTrack, {
367
- "data-slot": "slider-track",
368
- className: "relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20",
369
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SliderRange, {
370
- "data-slot": "slider-range",
371
- className: "absolute h-full bg-primary"
372
- })
373
- }), Array.from({ length: _values.length }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SliderThumb, {
374
- "data-slot": "slider-thumb",
375
- className: "block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50"
376
- }, index))]
377
- });
378
- }
379
- function Switch$1({ className, ...props }) {
380
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Switch, {
381
- "data-slot": "switch",
382
- className: cn("peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input", className),
383
- ...props,
384
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SwitchThumb, {
385
- "data-slot": "switch-thumb",
386
- className: "pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
387
- })
388
- });
389
- }
390
- var createSsrRpc = (functionId) => {
391
- const url = "/_serverFn/" + functionId;
392
- const serverFnMeta = { id: functionId };
393
- const fn = async (...args) => {
394
- return (await getServerFnById(functionId, { origin: "server" }))(...args);
395
- };
396
- return Object.assign(fn, {
397
- url,
398
- serverFnMeta,
399
- [TSS_SERVER_FUNCTION]: true
400
- });
401
- };
402
- var getConfig = createServerFn().handler(createSsrRpc("51a34c05479a893c7d320bd4cd1604427289d667698c48b9f30a01aabf8a5e68"));
403
- var getChannels = createServerFn().handler(createSsrRpc("79ada05964de8f2123bfcd62b10097d59bf8fd689ef5c7161031e5871d8396c5"));
404
- createServerFn().handler(createSsrRpc("f8bdca3d0579adb812d6404f55d9261a89cdb025e920b1fcad82883646a5fe9e"));
405
- var getBundles = createServerFn({ method: "GET" }).inputValidator((input) => input).handler(createSsrRpc("00ccacb4a0212c83ec29f4d11719046ad91ea8291cfc557d514dbf00d3bd7f5f"));
406
- var getBundle = createServerFn({ method: "GET" }).inputValidator((input) => input).handler(createSsrRpc("1bd85c2a50e24785cb6abb023a247a9f048f37ddfa85cbb1c57e579563bad013"));
407
- var getBundleChildren = createServerFn({ method: "GET" }).inputValidator((input) => input).handler(createSsrRpc("4370f1bf70b91f1168d85379d3008449a1bedb915e9878ca4ef89d5f3a80fa7f"));
408
- var getBundleChildCounts = createServerFn({ method: "GET" }).inputValidator((input) => input).handler(createSsrRpc("4ca367070b00f58179a572f0bb8196a4ccdd3881e9b1dbfe33deee0cb6e1f549"));
409
- var getBundleDownloadUrl = createServerFn({ method: "GET" }).inputValidator((input) => input).handler(createSsrRpc("e000081a14772a0496dfdf615232fbbb1a23b89191d0e32f89a05dd67a1916f7"));
410
- var updateBundle = createServerFn({ method: "POST" }).inputValidator((input) => input).handler(createSsrRpc("67f90ce4cd10fd0226cd9d77cdcd8d0f25a59a6ac406360b655e44296bc4208b"));
411
- var promoteBundle = createServerFn({ method: "POST" }).inputValidator((input) => input).handler(createSsrRpc("c4ef3bbb77ea8a4410623fd18a4741e2ff0668b1a97510bcdd032a8428eaa5da"));
412
- createServerFn({ method: "POST" }).inputValidator((input) => input).handler(createSsrRpc("16cc7c2f080ea5b73e0c6bba815b110dbd7727796036d248ba0d8ae819ddef08"));
413
- var deleteBundle = createServerFn({ method: "POST" }).inputValidator((input) => input).handler(createSsrRpc("3a27ff5679228b86a346b3fe42142e7a5bf0264f73173c0dbe5f07fb37c43cf7"));
414
- var deleteBundles = createServerFn({ method: "POST" }).inputValidator((input) => input).handler(createSsrRpc("61de3b69779c5ce2a5f5d3c27e2cfaca8fba8f14c8803559b9cd733c456494fa"));
415
- var bundleListQueryKey = ["bundles"];
416
- var queryKeys = {
417
- config: ["config"],
418
- channels: ["channels"],
419
- configLoaded: ["config-loaded"],
420
- bundles: {
421
- all: bundleListQueryKey,
422
- list: (filters) => [...bundleListQueryKey, filters ?? {}]
423
- },
424
- bundleChildren: {
425
- all: ["bundle-children"],
426
- list: (baseBundleId) => ["bundle-children", baseBundleId],
427
- counts: (bundleIds) => [
428
- "bundle-children",
429
- "counts",
430
- ...bundleIds
431
- ]
432
- },
433
- bundle: (bundleId) => ["bundle", bundleId]
434
- };
435
- function replaceBundleInQueryData(data, updatedBundle) {
436
- if (!data) return data;
437
- return {
438
- ...data,
439
- data: data.data.map((bundle) => bundle.id === updatedBundle.id ? updatedBundle : bundle)
440
- };
441
- }
442
- function removeBundleFromQueryData(data, bundleId) {
443
- if (!data) return data;
444
- return {
445
- ...data,
446
- data: data.data.filter((bundle) => bundle.id !== bundleId)
447
- };
448
- }
449
- function removeBundlesFromQueryData(data, bundleIds) {
450
- if (!data) return data;
451
- const bundleIdSet = new Set(bundleIds);
452
- return {
453
- ...data,
454
- data: data.data.filter((bundle) => !bundleIdSet.has(bundle.id))
455
- };
456
- }
457
- var hasOwn = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
458
- var invalidateInBackground = (queryClient, queryKey) => {
459
- queryClient.invalidateQueries({ queryKey }).catch(() => void 0);
460
- };
461
- function useConfigQuery() {
462
- return useQuery({
463
- queryKey: queryKeys.config,
464
- queryFn: () => getConfig(),
465
- staleTime: Infinity
466
- });
467
- }
468
- function useChannelsQuery() {
469
- return useQuery({
470
- queryKey: queryKeys.channels,
471
- queryFn: () => getChannels(),
472
- staleTime: Infinity
473
- });
474
- }
475
- function useBundlesQuery(filters) {
476
- return useQuery({
477
- queryKey: queryKeys.bundles.list(filters),
478
- queryFn: () => getBundles({ data: filters }),
479
- staleTime: Infinity,
480
- placeholderData: (previousData) => previousData
481
- });
482
- }
483
- function useBundleQuery(bundleId) {
484
- return useQuery({
485
- queryKey: queryKeys.bundle(bundleId),
486
- queryFn: () => getBundle({ data: { bundleId } }),
487
- staleTime: Infinity,
488
- enabled: !!bundleId
489
- });
490
- }
491
- function useBundleChildrenQuery(baseBundleId) {
492
- return useQuery({
493
- queryKey: queryKeys.bundleChildren.list(baseBundleId),
494
- queryFn: () => getBundleChildren({ data: { baseBundleId } }),
495
- staleTime: Infinity,
496
- enabled: !!baseBundleId
497
- });
498
- }
499
- function useBundleChildCountsQuery(bundleIds) {
500
- const normalizedBundleIds = [...bundleIds].sort((left, right) => left.localeCompare(right));
501
- return useQuery({
502
- queryKey: queryKeys.bundleChildren.counts(normalizedBundleIds),
503
- queryFn: () => getBundleChildCounts({ data: { bundleIds: normalizedBundleIds } }),
504
- staleTime: Infinity,
505
- enabled: normalizedBundleIds.length > 0
506
- });
507
- }
508
- function useBundleDownloadUrlMutation() {
509
- return useMutation({ mutationFn: (params) => getBundleDownloadUrl({ data: params }) });
510
- }
511
- function useUpdateBundleMutation() {
512
- const queryClient = useQueryClient();
513
- return useMutation({
514
- mutationFn: (params) => updateBundle({ data: params }),
515
- onSuccess: ({ bundle: updatedBundle }, vars) => {
516
- queryClient.setQueryData(queryKeys.bundle(vars.bundleId), updatedBundle);
517
- queryClient.setQueriesData({ queryKey: queryKeys.bundles.all }, (data) => replaceBundleInQueryData(data, updatedBundle));
518
- invalidateInBackground(queryClient, queryKeys.bundles.all);
519
- if (hasOwn(vars.bundle, "patches") || hasOwn(vars.bundle, "channel") || hasOwn(vars.bundle, "platform")) invalidateInBackground(queryClient, queryKeys.bundleChildren.all);
520
- if (hasOwn(vars.bundle, "channel")) invalidateInBackground(queryClient, queryKeys.channels);
521
- }
522
- });
523
- }
524
- function usePromoteBundleMutation() {
525
- const queryClient = useQueryClient();
526
- return useMutation({
527
- mutationFn: (params) => promoteBundle({ data: params }),
528
- onSuccess: async ({ bundle }) => {
529
- queryClient.setQueryData(queryKeys.bundle(bundle.id), bundle);
530
- await Promise.all([
531
- queryClient.invalidateQueries({ queryKey: queryKeys.bundles.all }),
532
- queryClient.invalidateQueries({ queryKey: queryKeys.bundleChildren.all }),
533
- queryClient.invalidateQueries({ queryKey: queryKeys.channels }),
534
- queryClient.invalidateQueries({ queryKey: queryKeys.bundle(bundle.id) })
535
- ]);
536
- }
537
- });
538
- }
539
- function useDeleteBundleMutation() {
540
- const queryClient = useQueryClient();
541
- return useMutation({
542
- mutationFn: (params) => deleteBundle({ data: params }),
543
- onSuccess: (_, vars) => {
544
- queryClient.removeQueries({ queryKey: queryKeys.bundle(vars.bundleId) });
545
- queryClient.setQueriesData({ queryKey: queryKeys.bundles.all }, (data) => removeBundleFromQueryData(data, vars.bundleId));
546
- invalidateInBackground(queryClient, queryKeys.bundles.all);
547
- invalidateInBackground(queryClient, queryKeys.bundleChildren.all);
548
- invalidateInBackground(queryClient, queryKeys.channels);
549
- }
550
- });
551
- }
552
- function useDeleteBundlesMutation() {
553
- const queryClient = useQueryClient();
554
- return useMutation({
555
- mutationFn: (params) => deleteBundles({ data: params }),
556
- onSuccess: (_, vars) => {
557
- for (const bundleId of vars.bundleIds) queryClient.removeQueries({ queryKey: queryKeys.bundle(bundleId) });
558
- queryClient.setQueriesData({ queryKey: queryKeys.bundles.all }, (data) => removeBundlesFromQueryData(data, vars.bundleIds));
559
- invalidateInBackground(queryClient, queryKeys.bundles.all);
560
- invalidateInBackground(queryClient, queryKeys.bundleChildren.all);
561
- invalidateInBackground(queryClient, queryKeys.channels);
562
- }
563
- });
564
- }
565
- function AlertDialog$1({ ...props }) {
566
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Root2, {
567
- "data-slot": "alert-dialog",
568
- ...props
569
- });
570
- }
571
- function AlertDialogPortal({ ...props }) {
572
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Portal2, {
573
- "data-slot": "alert-dialog-portal",
574
- ...props
575
- });
576
- }
577
- function AlertDialogOverlay({ className, ...props }) {
578
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Overlay2, {
579
- "data-slot": "alert-dialog-overlay",
580
- className: cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/80 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 z-50", className),
581
- ...props
582
- });
583
- }
584
- function AlertDialogContent({ className, size = "default", ...props }) {
585
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogPortal, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogOverlay, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Content2, {
586
- "data-slot": "alert-dialog-content",
587
- "data-size": size,
588
- className: cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-3 rounded-xl p-4 ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-64 data-[size=default]:sm:max-w-sm group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none", className),
589
- ...props
590
- })] });
591
- }
592
- function AlertDialogHeader({ className, ...props }) {
593
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
594
- "data-slot": "alert-dialog-header",
595
- className: cn("grid grid-rows-[auto_1fr] place-items-center gap-1 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]", className),
596
- ...props
597
- });
598
- }
599
- function AlertDialogFooter({ className, ...props }) {
600
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
601
- "data-slot": "alert-dialog-footer",
602
- className: cn("flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end", className),
603
- ...props
604
- });
605
- }
606
- function AlertDialogTitle({ className, ...props }) {
607
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Title2, {
608
- "data-slot": "alert-dialog-title",
609
- className: cn("text-sm font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2", className),
610
- ...props
611
- });
612
- }
613
- function AlertDialogDescription({ className, ...props }) {
614
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Description2, {
615
- "data-slot": "alert-dialog-description",
616
- className: cn("text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3", className),
617
- ...props
618
- });
619
- }
620
- function AlertDialogAction({ className, variant = "default", size = "default", ...props }) {
621
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
622
- variant,
623
- size,
624
- asChild: true,
625
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Action, {
626
- "data-slot": "alert-dialog-action",
627
- className: cn(className),
628
- ...props
629
- })
630
- });
631
- }
632
- function AlertDialogCancel({ className, variant = "outline", size = "default", ...props }) {
633
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
634
- variant,
635
- size,
636
- asChild: true,
637
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Cancel, {
638
- "data-slot": "alert-dialog-cancel",
639
- className: cn(className),
640
- ...props
641
- })
642
- });
643
- }
644
- function DeleteBundleDialog({ bundle, open, onOpenChange, onSuccess }) {
645
- const deleteBundleMutation = useDeleteBundleMutation();
646
- const isDeleting = deleteBundleMutation.isPending;
647
- const handleOpenChange = (nextOpen) => {
648
- if (!nextOpen && isDeleting) return;
649
- onOpenChange(nextOpen);
650
- };
651
- const handleDelete = async () => {
652
- if (isDeleting) return;
653
- try {
654
- await deleteBundleMutation.mutateAsync({ bundleId: bundle.id });
655
- toast.success("Bundle deleted successfully");
656
- onOpenChange(false);
657
- onSuccess();
658
- } catch (error) {
659
- toast.error("Failed to delete bundle");
660
- console.error(error);
661
- }
662
- };
663
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialog$1, {
664
- open,
665
- onOpenChange: handleOpenChange,
666
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogContent, {
667
- onEscapeKeyDown: (event) => {
668
- if (isDeleting) event.preventDefault();
669
- },
670
- children: [
671
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogTitle, { children: "Are you sure?" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogDescription, { children: "This action cannot be undone. This will permanently delete the bundle and remove it from storage." })] }),
672
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
673
- className: "my-4 p-4 bg-muted rounded-lg",
674
- children: [
675
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
676
- className: "text-sm font-medium mb-1",
677
- children: "Bundle ID:"
678
- }),
679
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
680
- className: "text-xs font-mono text-muted-foreground break-all",
681
- children: bundle.id
682
- }),
683
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
684
- className: "text-sm font-medium mt-3 mb-1",
685
- children: "Channel:"
686
- }),
687
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
688
- className: "text-xs text-muted-foreground",
689
- children: bundle.channel
690
- })
691
- ]
692
- }),
693
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogFooter, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogCancel, {
694
- disabled: isDeleting,
695
- children: "Cancel"
696
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogAction, {
697
- onClick: (event) => {
698
- event.preventDefault();
699
- handleDelete();
700
- },
701
- className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
702
- disabled: isDeleting,
703
- children: isDeleting ? "Deleting..." : "Delete"
704
- })] })
705
- ]
706
- })
707
- });
708
- }
709
- function Select$1({ ...props }) {
710
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Select, {
711
- "data-slot": "select",
712
- ...props
713
- });
714
- }
715
- function SelectValue({ ...props }) {
716
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue$1, {
717
- "data-slot": "select-value",
718
- ...props
719
- });
720
- }
721
- function SelectTrigger({ className, children, ...props }) {
722
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectTrigger$1, {
723
- "data-slot": "select-trigger",
724
- className: cn("flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1", className),
725
- ...props,
726
- children: [children, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectIcon, {
727
- asChild: true,
728
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, { className: "h-4 w-4 opacity-50" })
729
- })]
730
- });
731
- }
732
- function SelectContent({ className, children, position = "item-aligned", align = "center", ...props }) {
733
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectPortal, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectContent$1, {
734
- "data-slot": "select-content",
735
- className: cn("bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 border border-border min-w-32 rounded-lg shadow-md duration-100 relative z-50 max-h-(--radix-select-content-available-height) origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className),
736
- position,
737
- align,
738
- ...props,
739
- children: [
740
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectScrollUpButton, {}),
741
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectViewport, {
742
- "data-position": position,
743
- className: cn("data-[position=popper]:h-[var(--radix-select-trigger-height)] data-[position=popper]:w-full data-[position=popper]:min-w-[var(--radix-select-trigger-width)]", position === "popper" && ""),
744
- children
745
- }),
746
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectScrollDownButton, {})
747
- ]
748
- }) });
749
- }
750
- function SelectItem({ className, children, ...props }) {
751
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectItem$1, {
752
- "data-slot": "select-item",
753
- className: cn("relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className),
754
- ...props,
755
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
756
- className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center",
757
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItemIndicator, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: "h-4 w-4" }) })
758
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItemText, { children })]
759
- });
760
- }
761
- function SelectScrollUpButton({ className, ...props }) {
762
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectScrollUpButton$1, {
763
- "data-slot": "select-scroll-up-button",
764
- className: cn("bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-3.5", className),
765
- ...props,
766
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronUp, {})
767
- });
768
- }
769
- function SelectScrollDownButton({ className, ...props }) {
770
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectScrollDownButton$1, {
771
- "data-slot": "select-scroll-down-button",
772
- className: cn("bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-3.5", className),
773
- ...props,
774
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, {})
775
- });
776
- }
777
- function useFilterParams() {
778
- const search = useSearch({ from: "/" });
779
- const navigate = useNavigate();
780
- const filters = {
781
- channel: search.channel,
782
- platform: search.platform,
783
- targetAppVersion: search.targetAppVersion,
784
- page: search.page,
785
- after: search.after,
786
- before: search.before
787
- };
788
- const bundleId = search.bundleId;
789
- const expandedBundleId = search.expandedBundleId;
790
- const navigateWithSearch = (nextSearch, options) => {
791
- navigate({
792
- to: "/",
793
- search: nextSearch,
794
- resetScroll: options?.resetScroll
795
- });
796
- };
797
- const getNextFilters = (newFilters) => {
798
- const hasChannel = Object.hasOwn(newFilters, "channel");
799
- const hasPlatform = Object.hasOwn(newFilters, "platform");
800
- const hasTargetAppVersion = Object.hasOwn(newFilters, "targetAppVersion");
801
- const hasPage = Object.hasOwn(newFilters, "page");
802
- const hasAfter = Object.hasOwn(newFilters, "after");
803
- const hasBefore = Object.hasOwn(newFilters, "before");
804
- const shouldResetPagination = hasChannel || hasPlatform || hasTargetAppVersion;
805
- const nextPage = hasPage && newFilters.page !== void 0 && newFilters.page > 1 ? newFilters.page : void 0;
806
- return {
807
- channel: hasChannel ? newFilters.channel : filters.channel,
808
- platform: hasPlatform ? newFilters.platform : filters.platform,
809
- targetAppVersion: hasTargetAppVersion ? newFilters.targetAppVersion : filters.targetAppVersion,
810
- page: shouldResetPagination ? void 0 : hasPage ? nextPage : filters.page,
811
- after: shouldResetPagination ? void 0 : hasAfter ? newFilters.after : filters.after,
812
- before: shouldResetPagination ? void 0 : hasBefore ? newFilters.before : filters.before
813
- };
814
- };
815
- const setFilters = (newFilters) => {
816
- navigateWithSearch({
817
- ...getNextFilters(newFilters),
818
- bundleId: void 0,
819
- expandedBundleId: void 0
820
- });
821
- };
822
- const setBundleId = (nextBundleId, newFilters = {}) => {
823
- navigateWithSearch({
824
- ...getNextFilters(newFilters),
825
- bundleId: nextBundleId,
826
- expandedBundleId: void 0
827
- }, { resetScroll: false });
828
- };
829
- const setExpandedBundleId = (nextExpandedBundleId, newFilters = {}) => {
830
- navigateWithSearch({
831
- ...getNextFilters(newFilters),
832
- bundleId,
833
- expandedBundleId: nextExpandedBundleId
834
- }, { resetScroll: false });
835
- };
836
- const resetFilters = () => {
837
- navigateWithSearch({
838
- channel: void 0,
839
- platform: void 0,
840
- targetAppVersion: void 0,
841
- page: void 0,
842
- after: void 0,
843
- before: void 0,
844
- bundleId: void 0,
845
- expandedBundleId: void 0
846
- });
847
- };
848
- return {
849
- filters,
850
- bundleId,
851
- expandedBundleId,
852
- setFilters,
853
- setBundleId,
854
- setExpandedBundleId,
855
- resetFilters
856
- };
857
- }
858
- function PromoteChannelDialog({ bundle, open, onOpenChange, onSuccess }) {
859
- const [targetChannel, setTargetChannel] = (0, import_react.useState)("");
860
- const [action, setAction] = (0, import_react.useState)("move");
861
- const [copyBundleId, setCopyBundleId] = (0, import_react.useState)("");
862
- const { setBundleId } = useFilterParams();
863
- const { data: channels = [] } = useChannelsQuery();
864
- const promoteBundleMutation = usePromoteBundleMutation();
865
- const isPromoting = promoteBundleMutation.isPending;
866
- const availableChannels = channels.filter((c) => c !== bundle.channel);
867
- const isCopy = action === "copy";
868
- const normalizedTargetChannel = targetChannel.trim();
869
- const isSameChannel = normalizedTargetChannel === bundle.channel;
870
- const displayedCopyBundleId = copyBundleId || "Generating bundle ID...";
871
- const resetDialogState = () => {
872
- setTargetChannel("");
873
- setAction("move");
874
- setCopyBundleId("");
875
- };
876
- const closeDialog = () => {
877
- onOpenChange(false);
878
- resetDialogState();
879
- };
880
- const handleOpenChange = (nextOpen) => {
881
- if (!nextOpen && isPromoting) return;
882
- onOpenChange(nextOpen);
883
- if (!nextOpen) resetDialogState();
884
- };
885
- const handleActionChange = (value) => {
886
- const nextAction = value;
887
- setAction(nextAction);
888
- if (nextAction === "copy") setCopyBundleId((current) => current || createUUIDv7());
889
- };
890
- const openBundleDetail = (nextBundleId, nextChannel) => {
891
- setBundleId(nextBundleId, {
892
- channel: nextChannel,
893
- after: void 0,
894
- before: void 0
895
- });
896
- };
897
- const handlePromote = async () => {
898
- if (!normalizedTargetChannel) {
899
- toast.error("Please select a target channel");
900
- return;
901
- }
902
- if (isSameChannel) {
903
- toast.error("Target channel must be different from the current channel");
904
- return;
905
- }
906
- try {
907
- const nextBundleId = isCopy ? copyBundleId || createUUIDv7() : void 0;
908
- const { bundle: promotedBundle } = await promoteBundleMutation.mutateAsync({
909
- action,
910
- bundleId: bundle.id,
911
- nextBundleId,
912
- targetChannel: normalizedTargetChannel
913
- });
914
- const promotedBundleId = promotedBundle.id;
915
- closeDialog();
916
- onSuccess?.();
917
- toast.success(isCopy ? `Bundle copied to ${normalizedTargetChannel}` : `Bundle moved to ${normalizedTargetChannel}`, {
918
- description: `bundleId: ${promotedBundleId}`,
919
- action: {
920
- label: "Show Detail",
921
- onClick: () => openBundleDetail(promotedBundleId, normalizedTargetChannel)
922
- }
923
- });
924
- } catch (error) {
925
- toast.error(error instanceof Error ? error.message : "Failed to promote bundle");
926
- console.error(error);
927
- }
928
- };
929
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog$1, {
930
- open,
931
- onOpenChange: handleOpenChange,
932
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogContent, {
933
- showCloseButton: !isPromoting,
934
- onEscapeKeyDown: (event) => {
935
- if (isPromoting) event.preventDefault();
936
- },
937
- onInteractOutside: (event) => {
938
- if (isPromoting) event.preventDefault();
939
- },
940
- children: [
941
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle, { children: "Promote to Channel" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogDescription, { children: "Choose how to promote this bundle, then select the target channel." })] }),
942
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
943
- className: "space-y-4 py-4",
944
- children: [
945
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
946
- className: "space-y-2",
947
- children: [
948
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, {
949
- htmlFor: "promote-action",
950
- children: "Action"
951
- }),
952
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Select$1, {
953
- value: action,
954
- onValueChange: handleActionChange,
955
- disabled: isPromoting,
956
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, {
957
- id: "promote-action",
958
- disabled: isPromoting,
959
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue, { placeholder: "Select an action" })
960
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectContent, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
961
- value: "move",
962
- children: "Move bundle"
963
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
964
- value: "copy",
965
- children: "Copy bundle"
966
- })] })]
967
- }),
968
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
969
- className: "text-xs text-muted-foreground",
970
- children: isCopy ? "Create a new bundle in the target channel and keep the original in the current channel." : "Move the current bundle to the target channel without creating a new bundle ID."
971
- })
972
- ]
973
- }),
974
- isCopy && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
975
- className: "space-y-2",
976
- children: [
977
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, {
978
- htmlFor: "copy-bundle-id",
979
- children: "New Bundle ID"
980
- }),
981
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
982
- id: "copy-bundle-id",
983
- value: displayedCopyBundleId,
984
- readOnly: true,
985
- disabled: isPromoting
986
- }),
987
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
988
- className: "font-mono text-xs text-muted-foreground",
989
- children: displayedCopyBundleId
990
- })
991
- ]
992
- }),
993
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
994
- className: "space-y-2",
995
- children: [
996
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, {
997
- htmlFor: "target-channel",
998
- children: "Target Channel"
999
- }),
1000
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
1001
- id: "target-channel",
1002
- value: targetChannel,
1003
- onChange: (event) => setTargetChannel(event.target.value),
1004
- placeholder: "Enter a channel name",
1005
- list: "available-channels",
1006
- "aria-invalid": isSameChannel,
1007
- disabled: isPromoting
1008
- }),
1009
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("datalist", {
1010
- id: "available-channels",
1011
- children: availableChannels.map((channel) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: channel }, channel))
1012
- }),
1013
- availableChannels.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1014
- className: "flex flex-wrap gap-2",
1015
- children: availableChannels.map((channel) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
1016
- type: "button",
1017
- variant: "outline",
1018
- size: "xs",
1019
- "aria-label": `Use ${channel} as target channel`,
1020
- onClick: () => setTargetChannel(channel),
1021
- disabled: isPromoting,
1022
- children: channel
1023
- }, channel))
1024
- }),
1025
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1026
- className: "text-xs text-muted-foreground",
1027
- children: "Choose an existing channel or enter a new one."
1028
- }),
1029
- isSameChannel && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1030
- className: "text-xs text-destructive",
1031
- role: "alert",
1032
- children: "Target channel must be different from the current channel."
1033
- })
1034
- ]
1035
- })
1036
- ]
1037
- }),
1038
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogFooter, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
1039
- variant: "outline",
1040
- onClick: () => handleOpenChange(false),
1041
- disabled: isPromoting,
1042
- children: "Cancel"
1043
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
1044
- onClick: handlePromote,
1045
- disabled: !normalizedTargetChannel || isCopy && !copyBundleId || isSameChannel || isPromoting,
1046
- children: isPromoting ? isCopy ? "Copying..." : "Moving..." : isCopy ? "Copy" : "Move"
1047
- })] })
1048
- ]
1049
- })
1050
- });
1051
- }
1052
- function Card({ className, ...props }) {
1053
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1054
- "data-slot": "card",
1055
- className: cn("rounded-xl border bg-card text-card-foreground shadow", className),
1056
- ...props
1057
- });
1058
- }
1059
- function CardHeader({ className, ...props }) {
1060
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1061
- "data-slot": "card-header",
1062
- className: cn("flex flex-col space-y-1.5 p-6", className),
1063
- ...props
1064
- });
1065
- }
1066
- function CardTitle({ className, ...props }) {
1067
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1068
- "data-slot": "card-title",
1069
- className: cn("font-semibold leading-none tracking-tight", className),
1070
- ...props
1071
- });
1072
- }
1073
- function CardDescription({ className, ...props }) {
1074
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1075
- "data-slot": "card-description",
1076
- className: cn("text-sm text-muted-foreground", className),
1077
- ...props
1078
- });
1079
- }
1080
- function CardContent({ className, ...props }) {
1081
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1082
- "data-slot": "card-content",
1083
- className: cn("p-6 pt-0", className),
1084
- ...props
1085
- });
1086
- }
1087
- function RolloutCohortsDialog({ bundleId, rolloutCohortCount, targetCohorts, triggerLabel = "View Cohorts", triggerVariant = "outline", triggerSize = "sm", triggerClassName }) {
1088
- const normalizedRolloutCount = normalizeRolloutCohortCount(rolloutCohortCount);
1089
- const normalizedTargetCohorts = targetCohorts ?? [];
1090
- const hasTargetCohorts = normalizedTargetCohorts.length > 0;
1091
- if (!(normalizedRolloutCount > 0 && normalizedRolloutCount < 1e3)) return null;
1092
- const rolloutCohorts = Array.from({ length: NUMERIC_COHORT_SIZE }, (_, index) => index + 1).filter((cohortValue) => getNumericCohortRolloutPosition(bundleId, cohortValue) < normalizedRolloutCount);
1093
- const rolloutPercentage = (normalizedRolloutCount / 10).toFixed(1);
1094
- const excludedCount = NUMERIC_COHORT_SIZE - rolloutCohorts.length;
1095
- const isMobile = useIsMobile();
1096
- const dialogBody = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1097
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1098
- className: "grid gap-3 grid-cols-2 sm:grid-cols-3",
1099
- children: [
1100
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
1101
- className: "p-4",
1102
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Selected Cohorts" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
1103
- className: "font-mono text-xl",
1104
- children: rolloutCohorts.length
1105
- })]
1106
- }) }),
1107
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
1108
- className: "p-4",
1109
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Excluded Cohorts" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
1110
- className: "font-mono text-xl",
1111
- children: excludedCount
1112
- })]
1113
- }) }),
1114
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, {
1115
- className: "col-span-2 sm:col-span-1",
1116
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
1117
- className: "p-4",
1118
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Bundle ID" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
1119
- className: "font-mono text-xs break-all leading-relaxed",
1120
- children: bundleId
1121
- })]
1122
- })
1123
- })
1124
- ]
1125
- }),
1126
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
1127
- className: "p-4 pb-3",
1128
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
1129
- className: "text-sm",
1130
- children: "Numeric Cohorts"
1131
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Listed in ascending order for readability." })]
1132
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, {
1133
- className: "p-4 pt-0",
1134
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1135
- className: "max-h-[50vh] overflow-y-auto rounded-lg border bg-muted/20 p-3 sm:max-h-[45vh]",
1136
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1137
- className: "grid grid-cols-3 gap-2 sm:grid-cols-6 lg:grid-cols-8",
1138
- children: rolloutCohorts.map((cohortValue) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
1139
- variant: "outline",
1140
- className: "justify-center font-mono",
1141
- children: cohortValue
1142
- }, cohortValue))
1143
- })
1144
- })
1145
- })] }),
1146
- hasTargetCohorts ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
1147
- className: "p-4 pb-3",
1148
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
1149
- className: "text-sm",
1150
- children: "Target Cohorts"
1151
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "These cohorts are also included, even if they are outside the numeric rollout." })]
1152
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, {
1153
- className: "p-4 pt-0",
1154
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1155
- className: "flex flex-wrap gap-2 rounded-lg border bg-muted/20 p-3",
1156
- children: normalizedTargetCohorts.map((cohort) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
1157
- variant: "secondary",
1158
- className: "max-w-full font-mono",
1159
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1160
- className: "truncate",
1161
- children: cohort
1162
- })
1163
- }, cohort))
1164
- })
1165
- })] }) : null
1166
- ] });
1167
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Dialog$1, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTrigger, {
1168
- asChild: true,
1169
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
1170
- type: "button",
1171
- variant: triggerVariant,
1172
- size: triggerSize,
1173
- className: triggerClassName,
1174
- onClick: (event) => event.stopPropagation(),
1175
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(List, { className: "h-3.5 w-3.5" }), triggerLabel]
1176
- })
1177
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogContent, {
1178
- className: isMobile ? "top-0 left-0 h-dvh max-w-none translate-x-0 translate-y-0 rounded-none border-0 p-0" : "sm:max-w-3xl",
1179
- children: isMobile ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1180
- className: "flex h-full flex-col overflow-hidden",
1181
- children: [
1182
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, {
1183
- className: "shrink-0 border-b border-border/70 px-4 py-4",
1184
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle, { children: "Rolled Out Cohorts" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogDescription, { children: [
1185
- rolloutPercentage,
1186
- "% rollout currently targets",
1187
- " ",
1188
- rolloutCohorts.length,
1189
- " of ",
1190
- NUMERIC_COHORT_SIZE,
1191
- " numeric cohorts. The selected set stays stable for this bundle as you expand or shrink rollout.",
1192
- hasTargetCohorts ? " Target Cohorts are added on top of this numeric rollout." : ""
1193
- ] })]
1194
- }),
1195
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1196
- className: "flex-1 overflow-y-auto px-4 py-4",
1197
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1198
- className: "flex flex-col gap-4",
1199
- children: dialogBody
1200
- })
1201
- }),
1202
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogFooter, {
1203
- className: "shrink-0 border-t border-border/70 px-4 py-3",
1204
- showCloseButton: true
1205
- })
1206
- ]
1207
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1208
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle, { children: "Rolled Out Cohorts" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogDescription, { children: [
1209
- rolloutPercentage,
1210
- "% rollout currently targets",
1211
- " ",
1212
- rolloutCohorts.length,
1213
- " of ",
1214
- NUMERIC_COHORT_SIZE,
1215
- " numeric cohorts. The selected set stays stable for this bundle as you expand or shrink rollout.",
1216
- hasTargetCohorts ? " Target Cohorts are added on top of this numeric rollout." : ""
1217
- ] })] }),
1218
- dialogBody,
1219
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogFooter, { showCloseButton: true })
1220
- ] })
1221
- })] });
1222
- }
1223
- var MIN_ROLLOUT_COHORT_COUNT = 10;
1224
- var MAX_ROLLOUT_COHORT_COUNT = 1e3;
1225
- function getTargetAppVersionValidation(value) {
1226
- const normalizedValue = value.trim();
1227
- if (normalizedValue.length === 0) return {
1228
- error: "Invalid target app version",
1229
- normalizedRange: null
1230
- };
1231
- const normalizedRange = normalizeRange(normalizedValue);
1232
- if (!normalizedRange) return {
1233
- error: "Invalid target app version",
1234
- normalizedRange: null
1235
- };
1236
- return {
1237
- error: void 0,
1238
- normalizedRange
1239
- };
1240
- }
1241
- function getDefaultValues(bundle) {
1242
- return {
1243
- message: bundle.message || "",
1244
- targetAppVersion: bundle.targetAppVersion || "",
1245
- enabled: bundle.enabled,
1246
- shouldForceUpdate: bundle.shouldForceUpdate,
1247
- rolloutCohortCount: bundle.rolloutCohortCount ?? 1e3,
1248
- targetCohorts: bundle.targetCohorts ?? []
1249
- };
1250
- }
1251
- var formatRolloutPercentage = (rolloutCohortCount) => (rolloutCohortCount / 10).toFixed(1);
1252
- function clampRolloutCohortCount(value) {
1253
- return Math.min(Math.max(value, MIN_ROLLOUT_COHORT_COUNT), MAX_ROLLOUT_COHORT_COUNT);
1254
- }
1255
- function formatRolloutInputValue(value) {
1256
- const normalizedValue = value.replace(",", ".");
1257
- const [integerValue = "", ...decimalValues] = normalizedValue.split(".");
1258
- const integerDigits = integerValue.replace(/\D/g, "").slice(0, 3);
1259
- const decimalDigits = decimalValues.join("").replace(/\D/g, "").slice(0, 1);
1260
- if (normalizedValue.includes(".")) return `${integerDigits}.${decimalDigits}`;
1261
- return integerDigits;
1262
- }
1263
- function parseRolloutPercentageInput(value) {
1264
- const parsedValue = Number.parseFloat(value);
1265
- if (!Number.isFinite(parsedValue)) return null;
1266
- return clampRolloutCohortCount(Math.trunc(parsedValue * 10));
1267
- }
1268
- function RolloutPercentageInput({ value, onChange }) {
1269
- const [inputValue, setInputValue] = (0, import_react.useState)(formatRolloutPercentage(value));
1270
- const [isFocused, setIsFocused] = (0, import_react.useState)(false);
1271
- (0, import_react.useEffect)(() => {
1272
- if (!isFocused) setInputValue(formatRolloutPercentage(value));
1273
- }, [isFocused, value]);
1274
- const handleInputChange = (e) => {
1275
- const nextInputValue = formatRolloutInputValue(e.target.value);
1276
- const nextRolloutCohortCount = parseRolloutPercentageInput(nextInputValue);
1277
- setInputValue(nextInputValue);
1278
- if (nextRolloutCohortCount !== null) onChange(nextRolloutCohortCount);
1279
- };
1280
- const handleInputBlur = () => {
1281
- const nextRolloutCohortCount = parseRolloutPercentageInput(inputValue) ?? value;
1282
- setIsFocused(false);
1283
- onChange(nextRolloutCohortCount);
1284
- setInputValue(formatRolloutPercentage(nextRolloutCohortCount));
1285
- };
1286
- const handleSliderChange = ([nextValue]) => {
1287
- const nextRolloutCohortCount = clampRolloutCohortCount(nextValue);
1288
- onChange(nextRolloutCohortCount);
1289
- setInputValue(formatRolloutPercentage(nextRolloutCohortCount));
1290
- };
1291
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1292
- className: "flex items-center gap-3",
1293
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Slider$1, {
1294
- id: "rolloutCohortCount",
1295
- value: [value],
1296
- onValueChange: handleSliderChange,
1297
- min: MIN_ROLLOUT_COHORT_COUNT,
1298
- max: MAX_ROLLOUT_COHORT_COUNT,
1299
- step: 1,
1300
- className: "min-w-0 flex-1"
1301
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(InputGroup, {
1302
- className: "w-24 shrink-0",
1303
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(InputGroupInput, {
1304
- id: "rolloutPercentage",
1305
- value: inputValue,
1306
- onChange: handleInputChange,
1307
- onFocus: () => setIsFocused(true),
1308
- onBlur: handleInputBlur,
1309
- inputMode: "decimal",
1310
- "aria-label": "Rollout Percentage",
1311
- className: "text-right tabular-nums"
1312
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InputGroupAddon, {
1313
- align: "inline-end",
1314
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InputGroupText, { children: "%" })
1315
- })]
1316
- })]
1317
- });
1318
- }
1319
- function BundleEditorForm({ bundle, onClose, onBusyChange }) {
1320
- const bundleDownloadUrlMutation = useBundleDownloadUrlMutation();
1321
- const updateBundleMutation = useUpdateBundleMutation();
1322
- const [showPromoteDialog, setShowPromoteDialog] = (0, import_react.useState)(false);
1323
- const [showDeleteDialog, setShowDeleteDialog] = (0, import_react.useState)(false);
1324
- const [newCohort, setNewCohort] = (0, import_react.useState)("");
1325
- const shouldEditTargetAppVersion = Boolean(bundle.targetAppVersion);
1326
- const form = useForm({
1327
- defaultValues: getDefaultValues(bundle),
1328
- onSubmit: async ({ value }) => {
1329
- const targetAppVersion = value.targetAppVersion.trim();
1330
- const { error } = shouldEditTargetAppVersion ? getTargetAppVersionValidation(targetAppVersion) : { error: void 0 };
1331
- if (error) {
1332
- toast.error(error);
1333
- return;
1334
- }
1335
- const normalizedTargetCohorts = Array.from(new Set(value.targetCohorts.map((cohort) => normalizeCohortValue(cohort))));
1336
- if (normalizedTargetCohorts.find((cohort) => !isValidCohort(cohort))) {
1337
- toast.error(INVALID_COHORT_ERROR_MESSAGE);
1338
- return;
1339
- }
1340
- try {
1341
- await updateBundleMutation.mutateAsync({
1342
- bundleId: bundle.id,
1343
- bundle: {
1344
- message: value.message,
1345
- targetAppVersion: targetAppVersion || void 0,
1346
- enabled: value.enabled,
1347
- shouldForceUpdate: value.shouldForceUpdate,
1348
- rolloutCohortCount: value.rolloutCohortCount,
1349
- targetCohorts: normalizedTargetCohorts.length > 0 ? normalizedTargetCohorts : null
1350
- }
1351
- });
1352
- toast.success("Bundle updated successfully");
1353
- onClose();
1354
- } catch (error) {
1355
- toast.error("Failed to update bundle");
1356
- console.error(error);
1357
- }
1358
- }
1359
- });
1360
- const hasChanges = useStore(form.store, (state) => !state.isDefaultValue);
1361
- const isSubmitting = useStore(form.store, (state) => state.isSubmitting);
1362
- const targetAppVersion = useStore(form.store, (state) => state.values.targetAppVersion);
1363
- const targetCohorts = useStore(form.store, (state) => state.values.targetCohorts);
1364
- const isSaving = isSubmitting || updateBundleMutation.isPending;
1365
- const targetAppVersionValidation = shouldEditTargetAppVersion ? getTargetAppVersionValidation(targetAppVersion) : {
1366
- error: void 0,
1367
- normalizedRange: null
1368
- };
1369
- const hasTargetAppVersionError = Boolean(targetAppVersionValidation.error);
1370
- const isDownloading = bundleDownloadUrlMutation.isPending;
1371
- (0, import_react.useEffect)(() => {
1372
- onBusyChange?.(isSaving);
1373
- }, [isSaving, onBusyChange]);
1374
- (0, import_react.useEffect)(() => {
1375
- return () => {
1376
- onBusyChange?.(false);
1377
- };
1378
- }, [onBusyChange]);
1379
- const handleAddCohort = () => {
1380
- const normalizedCohort = normalizeCohortValue(newCohort);
1381
- if (!normalizedCohort) return;
1382
- if (!isValidCohort(normalizedCohort)) {
1383
- toast.error(INVALID_COHORT_ERROR_MESSAGE);
1384
- return;
1385
- }
1386
- const currentCohorts = form.getFieldValue("targetCohorts");
1387
- if (currentCohorts.includes(normalizedCohort)) {
1388
- toast.error("Cohort already exists");
1389
- return;
1390
- }
1391
- form.setFieldValue("targetCohorts", [...currentCohorts, normalizedCohort]);
1392
- setNewCohort("");
1393
- };
1394
- const handleRemoveCohort = (cohortToRemove) => {
1395
- const currentCohorts = form.getFieldValue("targetCohorts");
1396
- form.setFieldValue("targetCohorts", currentCohorts.filter((cohort) => cohort !== cohortToRemove));
1397
- };
1398
- const handleKeyDown = (e) => {
1399
- if (e.key === "Enter") {
1400
- e.preventDefault();
1401
- handleAddCohort();
1402
- }
1403
- };
1404
- const handleDownloadBundle = async () => {
1405
- const downloadWindow = window.open("", "_blank");
1406
- try {
1407
- const { fileUrl } = await bundleDownloadUrlMutation.mutateAsync({ bundleId: bundle.id });
1408
- if (!fileUrl) throw new Error("Bundle download URL is empty");
1409
- if (downloadWindow) {
1410
- downloadWindow.opener = null;
1411
- downloadWindow.location.href = fileUrl;
1412
- } else window.open(fileUrl, "_blank", "noopener,noreferrer");
1413
- toast.success("Bundle download started");
1414
- } catch (error) {
1415
- downloadWindow?.close();
1416
- const message = error instanceof Error ? error.message : "Failed to download bundle";
1417
- toast.error(message);
1418
- console.error(error);
1419
- }
1420
- };
1421
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1422
- className: "space-y-6",
1423
- children: [
1424
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", {
1425
- onSubmit: (e) => {
1426
- e.preventDefault();
1427
- e.stopPropagation();
1428
- if (form.state.isDefaultValue || isSaving || hasTargetAppVersionError) return;
1429
- form.handleSubmit();
1430
- },
1431
- className: "space-y-6",
1432
- children: [
1433
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(form.Field, {
1434
- name: "message",
1435
- children: (field) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1436
- className: "space-y-2",
1437
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, {
1438
- htmlFor: "message",
1439
- children: "Message"
1440
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Textarea, {
1441
- id: "message",
1442
- value: field.state.value,
1443
- onChange: (e) => field.handleChange(e.target.value),
1444
- placeholder: "Update message...",
1445
- rows: 3
1446
- })]
1447
- })
1448
- }),
1449
- shouldEditTargetAppVersion && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(form.Field, {
1450
- name: "targetAppVersion",
1451
- children: (field) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1452
- className: "space-y-2",
1453
- children: [
1454
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, {
1455
- htmlFor: "targetAppVersion",
1456
- children: "Target App Version"
1457
- }),
1458
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
1459
- id: "targetAppVersion",
1460
- value: field.state.value,
1461
- onChange: (e) => field.handleChange(e.target.value),
1462
- placeholder: "1.0.0",
1463
- "aria-invalid": hasTargetAppVersionError
1464
- }),
1465
- hasTargetAppVersionError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1466
- className: "text-xs text-destructive",
1467
- role: "alert",
1468
- children: targetAppVersionValidation.error
1469
- }) : targetAppVersionValidation.normalizedRange && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1470
- className: "text-xs text-muted-foreground",
1471
- children: targetAppVersionValidation.normalizedRange
1472
- })
1473
- ]
1474
- })
1475
- }),
1476
- bundle.fingerprintHash && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1477
- className: "space-y-2",
1478
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, { children: "Fingerprint Hash" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
1479
- value: bundle.fingerprintHash,
1480
- disabled: true,
1481
- className: "font-mono text-xs"
1482
- })]
1483
- }),
1484
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(form.Field, {
1485
- name: "enabled",
1486
- children: (field) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1487
- className: "flex items-center justify-between",
1488
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, {
1489
- htmlFor: "enabled",
1490
- children: "Enabled"
1491
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Switch$1, {
1492
- id: "enabled",
1493
- checked: field.state.value,
1494
- onCheckedChange: field.handleChange
1495
- })]
1496
- })
1497
- }),
1498
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(form.Field, {
1499
- name: "shouldForceUpdate",
1500
- children: (field) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1501
- className: "flex items-center justify-between",
1502
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, {
1503
- htmlFor: "shouldForceUpdate",
1504
- children: "Force Update"
1505
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Switch$1, {
1506
- id: "shouldForceUpdate",
1507
- checked: field.state.value,
1508
- onCheckedChange: field.handleChange
1509
- })]
1510
- })
1511
- }),
1512
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(form.Field, {
1513
- name: "rolloutCohortCount",
1514
- children: (field) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1515
- className: "space-y-2",
1516
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1517
- className: "flex items-center justify-between",
1518
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, {
1519
- htmlFor: "rolloutPercentage",
1520
- children: "Rollout Percentage"
1521
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1522
- className: "flex items-center gap-2",
1523
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RolloutCohortsDialog, {
1524
- bundleId: bundle.id,
1525
- rolloutCohortCount: field.state.value,
1526
- targetCohorts,
1527
- triggerLabel: "Preview Cohorts",
1528
- triggerVariant: "outline",
1529
- triggerSize: "sm"
1530
- })
1531
- })]
1532
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RolloutPercentageInput, {
1533
- value: field.state.value,
1534
- onChange: field.handleChange
1535
- })]
1536
- })
1537
- }),
1538
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(form.Field, {
1539
- name: "targetCohorts",
1540
- children: (field) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1541
- className: "space-y-2",
1542
- children: [
1543
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, { children: "Target Cohorts (optional)" }),
1544
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1545
- className: "flex gap-2",
1546
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
1547
- value: newCohort,
1548
- onChange: (e) => setNewCohort(e.target.value),
1549
- onKeyDown: handleKeyDown,
1550
- placeholder: "Enter cohort...",
1551
- className: "font-mono text-xs"
1552
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
1553
- type: "button",
1554
- variant: "outline",
1555
- size: "icon",
1556
- onClick: handleAddCohort,
1557
- disabled: !newCohort.trim(),
1558
- "aria-label": "Add cohort",
1559
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, { className: "h-4 w-4" })
1560
- })]
1561
- }),
1562
- field.state.value.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1563
- className: "flex flex-wrap gap-2 mt-2",
1564
- children: field.state.value.map((cohort) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
1565
- variant: "secondary",
1566
- className: "font-mono text-xs gap-1 pr-1",
1567
- children: [cohort.length > 20 ? `${cohort.slice(0, 20)}...` : cohort, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1568
- type: "button",
1569
- onClick: () => handleRemoveCohort(cohort),
1570
- "aria-label": `Remove cohort ${cohort}`,
1571
- className: "ml-1 rounded-full hover:bg-muted-foreground/20 p-0.5",
1572
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "h-3 w-3" })
1573
- })]
1574
- }, cohort))
1575
- }),
1576
- field.state.value.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
1577
- className: "text-xs text-muted-foreground mt-2",
1578
- children: [
1579
- field.state.value.length,
1580
- " cohort",
1581
- field.state.value.length !== 1 ? "s" : "",
1582
- " targeted"
1583
- ]
1584
- })
1585
- ]
1586
- })
1587
- }),
1588
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1589
- className: "pt-2",
1590
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
1591
- type: "submit",
1592
- size: "lg",
1593
- className: "w-full",
1594
- disabled: !hasChanges || isSaving || hasTargetAppVersionError,
1595
- children: isSaving ? "Saving..." : "Save Changes"
1596
- })
1597
- })
1598
- ]
1599
- }),
1600
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Separator$1, { className: "my-8" }),
1601
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1602
- className: "space-y-4",
1603
- children: [
1604
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
1605
- className: "text-sm font-medium",
1606
- children: "Actions"
1607
- }),
1608
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
1609
- variant: "outline",
1610
- size: "sm",
1611
- className: "w-full",
1612
- onClick: () => setShowPromoteDialog(true),
1613
- disabled: isSaving,
1614
- children: "Promote to Channel"
1615
- }),
1616
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
1617
- variant: "outline",
1618
- size: "sm",
1619
- className: "w-full",
1620
- onClick: handleDownloadBundle,
1621
- disabled: isSaving || isDownloading,
1622
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Download, { className: "h-4 w-4" }), isDownloading ? "Preparing Download..." : "Download Bundle"]
1623
- }),
1624
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
1625
- variant: "destructive",
1626
- size: "sm",
1627
- className: "w-full",
1628
- onClick: () => setShowDeleteDialog(true),
1629
- disabled: isSaving,
1630
- children: "Delete Bundle"
1631
- })
1632
- ]
1633
- }),
1634
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PromoteChannelDialog, {
1635
- bundle,
1636
- open: showPromoteDialog,
1637
- onOpenChange: setShowPromoteDialog,
1638
- onSuccess: onClose
1639
- }),
1640
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DeleteBundleDialog, {
1641
- bundle,
1642
- open: showDeleteDialog,
1643
- onOpenChange: setShowDeleteDialog,
1644
- onSuccess: onClose
1645
- })
1646
- ]
1647
- });
1648
- }
1649
- function HashValueDisplay({ value, maxLength = 12, className }) {
1650
- const isTruncated = value.length > maxLength;
1651
- const truncated = isTruncated ? value.slice(0, maxLength) : value;
1652
- const copyValue = async () => {
1653
- try {
1654
- await navigator.clipboard.writeText(value);
1655
- toast.success("Copied to clipboard", { description: value });
1656
- } catch {
1657
- toast.error("Failed to copy value");
1658
- }
1659
- };
1660
- const handleClick = (event) => {
1661
- event.preventDefault();
1662
- event.stopPropagation();
1663
- copyValue();
1664
- };
1665
- const handleKeyDown = (event) => {
1666
- if (event.key === "Enter" || event.key === " ") {
1667
- event.preventDefault();
1668
- event.stopPropagation();
1669
- copyValue();
1670
- }
1671
- };
1672
- const content = /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1673
- translate: "no",
1674
- className: cn("font-mono text-xs tabular-nums whitespace-nowrap", className),
1675
- children: truncated
1676
- });
1677
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Tooltip$1, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipTrigger, {
1678
- asChild: true,
1679
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
1680
- type: "button",
1681
- translate: "no",
1682
- className: cn("ring-ring/30 bg-muted/40 border-border/70 inline-flex min-w-0 max-w-full cursor-pointer items-center rounded-md border px-1.5 py-0.5 align-top shadow-xs outline-none transition-[background-color,border-color,transform,box-shadow]", "hover:bg-muted/70 active:scale-[0.98] active:bg-muted/85 focus-visible:ring-[2px]", "touch-manipulation select-none", !isTruncated && "cursor-copy"),
1683
- onClick: handleClick,
1684
- onKeyDown: handleKeyDown,
1685
- children: content
1686
- })
1687
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipContent, {
1688
- hidden: !isTruncated,
1689
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
1690
- translate: "no",
1691
- className: "break-all font-mono text-xs tabular-nums",
1692
- children: value
1693
- })
1694
- })] });
1695
- }
1696
- var normalizeGitUrl = (gitUrl) => {
1697
- const trimmedGitUrl = gitUrl.trim();
1698
- if (trimmedGitUrl.startsWith("git@")) {
1699
- const sshMatch = trimmedGitUrl.match(/^git@([^:]+):(.+?)(?:\.git)?$/);
1700
- if (sshMatch) return `https://${sshMatch[1]}/${sshMatch[2]}`;
1701
- }
1702
- if (trimmedGitUrl.startsWith("ssh://")) try {
1703
- const url = new URL(trimmedGitUrl);
1704
- return `https://${url.hostname}${url.pathname}`.replace(/\/+$/, "").replace(/\.git$/, "");
1705
- } catch {
1706
- return trimmedGitUrl.replace(/\/+$/, "").replace(/\.git$/, "");
1707
- }
1708
- return trimmedGitUrl.replace(/\/+$/, "").replace(/\.git$/, "");
1709
- };
1710
- var getCommitUrl = (gitUrl, commitHash) => {
1711
- if (!gitUrl?.trim()) return null;
1712
- const normalizedGitUrl = normalizeGitUrl(gitUrl);
1713
- try {
1714
- const { hostname } = new URL(normalizedGitUrl);
1715
- if (hostname.includes("gitlab")) return `${normalizedGitUrl}/-/commit/${commitHash}`;
1716
- if (hostname.includes("bitbucket")) return `${normalizedGitUrl}/commits/${commitHash}`;
1717
- } catch {}
1718
- return `${normalizedGitUrl}/commit/${commitHash}`;
1719
- };
1720
- function Row({ label, value }) {
1721
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1722
- className: "flex flex-col gap-1.5 sm:flex-row sm:items-start sm:justify-between sm:gap-4",
1723
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1724
- className: "text-muted-foreground",
1725
- children: label
1726
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1727
- className: "min-w-0 text-left text-sm sm:text-right",
1728
- children: value
1729
- })]
1730
- });
1731
- }
1732
- function BundleMetadata({ bundle }) {
1733
- const { data: configData, isFetched } = useConfigQuery();
1734
- const patchBaseBundleId = getPatchBaseBundleId(bundle);
1735
- const hbcPatchFileHash = getPatchFileHash(bundle);
1736
- const hbcPatchBaseFileHash = getPatchBaseFileHash(bundle);
1737
- const hasMetadata = bundle.targetAppVersion || bundle.fingerprintHash || bundle.gitCommitHash || bundle.fileHash || patchBaseBundleId || hbcPatchBaseFileHash || hbcPatchFileHash;
1738
- const gitCommitUrl = bundle.gitCommitHash && isFetched ? getCommitUrl(configData?.console.gitUrl, bundle.gitCommitHash) : null;
1739
- if (!hasMetadata) return null;
1740
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
1741
- className: "text-sm font-medium",
1742
- children: "Metadata"
1743
- }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardContent, {
1744
- className: "flex flex-col gap-3 text-sm",
1745
- children: [
1746
- bundle.targetAppVersion ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
1747
- label: "App Version",
1748
- value: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1749
- translate: "no",
1750
- className: "font-mono",
1751
- children: bundle.targetAppVersion
1752
- })
1753
- }) : null,
1754
- bundle.fingerprintHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
1755
- label: "Fingerprint",
1756
- value: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HashValueDisplay, {
1757
- value: bundle.fingerprintHash,
1758
- maxLength: 16
1759
- })
1760
- }) : null,
1761
- bundle.gitCommitHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
1762
- label: "Git Commit",
1763
- value: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1764
- className: "flex items-center justify-start gap-1 sm:justify-end",
1765
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(HashValueDisplay, {
1766
- value: bundle.gitCommitHash,
1767
- maxLength: 12,
1768
- className: gitCommitUrl ? "text-primary" : void 0
1769
- }), gitCommitUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
1770
- href: gitCommitUrl,
1771
- target: "_blank",
1772
- rel: "noopener noreferrer",
1773
- className: "shrink-0 text-primary hover:underline",
1774
- "aria-label": "Open git commit",
1775
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ExternalLink, {
1776
- "aria-hidden": "true",
1777
- className: "h-3 w-3"
1778
- })
1779
- }) : null]
1780
- })
1781
- }) : null,
1782
- bundle.fileHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
1783
- label: "Bundle Hash",
1784
- value: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HashValueDisplay, {
1785
- value: bundle.fileHash,
1786
- maxLength: 16
1787
- })
1788
- }) : null,
1789
- patchBaseBundleId ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
1790
- label: "Patch Base",
1791
- value: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
1792
- bundleId: patchBaseBundleId,
1793
- maxLength: 18,
1794
- fullOnMobile: true
1795
- })
1796
- }) : null,
1797
- hbcPatchBaseFileHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
1798
- label: "Base Hash",
1799
- value: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HashValueDisplay, {
1800
- value: hbcPatchBaseFileHash,
1801
- maxLength: 16
1802
- })
1803
- }) : null,
1804
- hbcPatchFileHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Row, {
1805
- label: "Patch Hash",
1806
- value: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HashValueDisplay, {
1807
- value: hbcPatchFileHash,
1808
- maxLength: 16
1809
- })
1810
- }) : null
1811
- ]
1812
- })] });
1813
- }
1814
- function BundleEditorSheet({ bundleId, bundle, loading = false, open, onOpenChange }) {
1815
- const [isSaving, setIsSaving] = (0, import_react.useState)(false);
1816
- const isMobile = useIsMobile();
1817
- (0, import_react.useEffect)(() => {
1818
- if (!open) setIsSaving(false);
1819
- }, [open]);
1820
- (0, import_react.useEffect)(() => {
1821
- if (typeof window === "undefined") return;
1822
- const refreshThemeChrome = () => {
1823
- window.dispatchEvent(new Event("hot-updater:refresh-theme-chrome"));
1824
- };
1825
- refreshThemeChrome();
1826
- const timeoutId = window.setTimeout(refreshThemeChrome, 180);
1827
- return () => window.clearTimeout(timeoutId);
1828
- }, [open]);
1829
- const handleOpenChange = (nextOpen) => {
1830
- if (!nextOpen && isSaving) return;
1831
- if (!nextOpen) setIsSaving(false);
1832
- onOpenChange(nextOpen);
1833
- };
1834
- const closeSheet = () => {
1835
- setIsSaving(false);
1836
- onOpenChange(false);
1837
- };
1838
- const headerContent = bundle ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleBasicInfo, { bundle }) : loading ? bundleId ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1839
- translate: "no",
1840
- className: "font-mono text-xs",
1841
- children: [
1842
- "Loading ",
1843
- bundleId,
1844
- "…"
1845
- ]
1846
- }) : "Loading bundle details…" : bundleId ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
1847
- translate: "no",
1848
- className: "font-mono text-xs",
1849
- children: ["Bundle not found: ", bundleId]
1850
- }) : "Bundle details unavailable";
1851
- const bodyContent = bundle ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1852
- className: "flex flex-col gap-6 px-4 pb-4 sm:px-6 sm:pb-6",
1853
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleEditorForm, {
1854
- bundle,
1855
- onClose: closeSheet,
1856
- onBusyChange: setIsSaving
1857
- }, bundle.id), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleMetadata, { bundle })]
1858
- }) : loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1859
- className: "flex flex-col gap-4 px-4 pb-4 sm:px-6 sm:pb-6",
1860
- children: [
1861
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" }),
1862
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-28 w-full" }),
1863
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" }),
1864
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" })
1865
- ]
1866
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1867
- className: "px-4 pb-4 text-sm text-muted-foreground sm:px-6 sm:pb-6",
1868
- children: "The requested bundle could not be loaded."
1869
- });
1870
- if (isMobile) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog$1, {
1871
- open,
1872
- onOpenChange: handleOpenChange,
1873
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogContent, {
1874
- className: "top-0 left-0 h-dvh max-w-none translate-x-0 translate-y-0 rounded-none border-0 p-0",
1875
- showCloseButton: !isSaving,
1876
- onEscapeKeyDown: (event) => {
1877
- if (isSaving) event.preventDefault();
1878
- },
1879
- onInteractOutside: (event) => {
1880
- if (isSaving) event.preventDefault();
1881
- },
1882
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1883
- className: "flex h-full flex-col overflow-hidden",
1884
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, {
1885
- className: "shrink-0 border-b border-border/70 px-4 py-4 sm:px-6",
1886
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle, { children: bundle ? "Bundle Detail" : "Bundle Details" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogDescription, {
1887
- asChild: true,
1888
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: headerContent })
1889
- })]
1890
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1891
- className: "flex-1 overflow-y-auto",
1892
- children: bodyContent
1893
- })]
1894
- })
1895
- })
1896
- });
1897
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sheet, {
1898
- open,
1899
- onOpenChange: handleOpenChange,
1900
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SheetContent, {
1901
- className: "w-[600px] overflow-y-auto sm:max-w-[600px]",
1902
- showCloseButton: !isSaving,
1903
- onEscapeKeyDown: (event) => {
1904
- if (isSaving) event.preventDefault();
1905
- },
1906
- onInteractOutside: (event) => {
1907
- if (isSaving) event.preventDefault();
1908
- },
1909
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SheetHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SheetTitle, { children: bundle ? "Bundle Detail" : "Bundle Details" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SheetDescription, { children: headerContent })] }), bodyContent]
1910
- })
1911
- });
1912
- }
1913
- var channelColors = {
1914
- production: "bg-green-500/10 text-green-700 dark:text-green-400 border-green-500/20",
1915
- dev: "bg-blue-500/10 text-blue-700 dark:text-blue-400 border-blue-500/20",
1916
- staging: "bg-yellow-500/10 text-yellow-700 dark:text-yellow-400 border-yellow-500/20"
1917
- };
1918
- function ChannelBadge({ channel, className }) {
1919
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
1920
- variant: "outline",
1921
- className: cn(channelColors[channel.toLowerCase()] || "bg-gray-500/10 text-gray-700 dark:text-gray-400 border-gray-500/20", className),
1922
- children: channel
1923
- });
1924
- }
1925
- function EnabledStatusIcon({ enabled, className, falseIcon = "x", colorMode = "semantic" }) {
1926
- const iconColorClassName = colorMode === "inherit" ? "text-current" : void 0;
1927
- if (enabled) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: cn("h-4 w-4", iconColorClassName ?? "text-green-600 dark:text-green-400", className) });
1928
- if (falseIcon === "minus") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Minus, { className: cn("h-4 w-4", iconColorClassName ?? "text-muted-foreground", className) });
1929
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: cn("h-4 w-4", iconColorClassName ?? "text-red-600 dark:text-red-400", className) });
1930
- }
1931
- function RolloutPercentageBadge({ percentage, className }) {
1932
- const isPartialRollout = percentage < 100;
1933
- const formattedPercentage = percentage.toFixed(1);
1934
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
1935
- variant: isPartialRollout ? "secondary" : "default",
1936
- className: cn("gap-1", className),
1937
- children: [
1938
- isPartialRollout && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriangleAlert, { className: "h-3 w-3" }),
1939
- formattedPercentage,
1940
- "%"
1941
- ]
1942
- });
1943
- }
1944
- import_dayjs_min.default.extend(import_relativeTime.default);
1945
- function TimestampDisplay({ uuid, format = "relative" }) {
1946
- const date = (0, import_dayjs_min.default)(extractTimestampFromUUIDv7(uuid));
1947
- if (format === "relative") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1948
- className: "text-sm text-muted-foreground",
1949
- children: date.fromNow()
1950
- });
1951
- if (format === "absolute") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1952
- className: "text-sm text-muted-foreground",
1953
- children: date.format("YYYY-MM-DD HH:mm:ss")
1954
- });
1955
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
1956
- className: "flex flex-col",
1957
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1958
- className: "text-sm",
1959
- children: date.fromNow()
1960
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
1961
- className: "text-xs text-muted-foreground",
1962
- children: date.format("YYYY-MM-DD HH:mm:ss")
1963
- })]
1964
- });
1965
- }
1966
- function Table({ className, ...props }) {
1967
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
1968
- "data-slot": "table-container",
1969
- className: "relative w-full overflow-x-auto",
1970
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("table", {
1971
- "data-slot": "table",
1972
- className: cn("w-full caption-bottom text-xs", className),
1973
- ...props
1974
- })
1975
- });
1976
- }
1977
- function TableHeader({ className, ...props }) {
1978
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", {
1979
- "data-slot": "table-header",
1980
- className: cn("[&_tr]:border-b", className),
1981
- ...props
1982
- });
1983
- }
1984
- function TableBody({ className, ...props }) {
1985
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", {
1986
- "data-slot": "table-body",
1987
- className: cn("[&_tr:last-child]:border-0", className),
1988
- ...props
1989
- });
1990
- }
1991
- function TableRow({ className, ...props }) {
1992
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", {
1993
- "data-slot": "table-row",
1994
- className: cn("hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", className),
1995
- ...props
1996
- });
1997
- }
1998
- function TableHead({ className, ...props }) {
1999
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
2000
- "data-slot": "table-head",
2001
- className: cn("text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0", className),
2002
- ...props
2003
- });
2004
- }
2005
- function TableCell({ className, ...props }) {
2006
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
2007
- "data-slot": "table-cell",
2008
- className: cn("p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0", className),
2009
- ...props
2010
- });
2011
- }
2012
- function BundleChildrenPanel({ panelId, bundle, bundles, loading, onDetailClick }) {
2013
- const isMobile = useIsMobile();
2014
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2015
- id: panelId,
2016
- className: "border-t bg-muted/10 p-3 sm:p-4",
2017
- "aria-live": "polite",
2018
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2019
- className: "flex flex-col gap-4",
2020
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2021
- className: "flex flex-wrap items-center justify-between gap-3",
2022
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2023
- className: "flex min-w-0 items-start gap-2 text-sm sm:items-center",
2024
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2025
- className: "text-muted-foreground",
2026
- children: "Base bundle"
2027
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
2028
- bundleId: bundle.id,
2029
- maxLength: 18,
2030
- fullOnMobile: true
2031
- })]
2032
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
2033
- variant: "outline",
2034
- children: [
2035
- bundles.length,
2036
- " ",
2037
- bundles.length === 1 ? "patch" : "patches"
2038
- ]
2039
- })]
2040
- }), loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2041
- className: "flex flex-col gap-2",
2042
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" })]
2043
- }) : bundles.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2044
- className: "flex flex-col gap-2",
2045
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2046
- className: "text-xs font-semibold uppercase text-muted-foreground/70",
2047
- children: "Patch bundles from this base"
2048
- }), isMobile ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2049
- className: "flex flex-col gap-2",
2050
- children: bundles.map((childBundle) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2051
- className: "rounded-md border bg-background p-3",
2052
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2053
- className: "flex flex-col gap-3",
2054
- children: [
2055
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2056
- className: "space-y-1",
2057
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2058
- className: "text-[11px] font-medium uppercase text-muted-foreground/70",
2059
- children: "Patch Bundle"
2060
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
2061
- bundleId: childBundle.id,
2062
- maxLength: 18,
2063
- fullOnMobile: true
2064
- })]
2065
- }),
2066
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2067
- className: "space-y-1",
2068
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2069
- className: "text-[11px] font-medium uppercase text-muted-foreground/70",
2070
- children: "Relation"
2071
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2072
- className: "flex flex-col items-start gap-1 text-sm",
2073
- children: [
2074
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
2075
- bundleId: bundle.id,
2076
- maxLength: 12,
2077
- fullOnMobile: true
2078
- }),
2079
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowRight, { className: "h-4 w-4 rotate-90 text-muted-foreground" }),
2080
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
2081
- bundleId: childBundle.id,
2082
- maxLength: 12,
2083
- fullOnMobile: true
2084
- })
2085
- ]
2086
- })]
2087
- }),
2088
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2089
- className: "flex items-center justify-between gap-3",
2090
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2091
- className: "space-y-1",
2092
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2093
- className: "text-[11px] font-medium uppercase text-muted-foreground/70",
2094
- children: "Artifact"
2095
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
2096
- variant: "secondary",
2097
- children: "bsdiff"
2098
- })]
2099
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2100
- className: "space-y-1 text-right",
2101
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2102
- className: "text-[11px] font-medium uppercase text-muted-foreground/70",
2103
- children: "Created"
2104
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2105
- className: "text-xs tabular-nums text-foreground",
2106
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TimestampDisplay, { uuid: childBundle.id })
2107
- })]
2108
- })]
2109
- }),
2110
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
2111
- type: "button",
2112
- variant: "outline",
2113
- size: "sm",
2114
- className: "w-full",
2115
- onClick: () => onDetailClick(childBundle),
2116
- children: "Detail"
2117
- })
2118
- ]
2119
- })
2120
- }, childBundle.id))
2121
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2122
- className: "overflow-x-auto rounded-md border bg-background",
2123
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Table, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, {
2124
- className: "hover:bg-transparent",
2125
- children: [
2126
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Patch Bundle" }),
2127
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Relation" }),
2128
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Artifact" }),
2129
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Created" }),
2130
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
2131
- className: "w-[96px] text-right",
2132
- children: "Detail"
2133
- })
2134
- ]
2135
- }) }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableBody, { children: bundles.map((childBundle) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, { children: [
2136
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
2137
- bundleId: childBundle.id,
2138
- maxLength: 18,
2139
- fullOnMobile: true
2140
- }) }),
2141
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2142
- className: "flex min-w-[280px] flex-col items-start gap-1 sm:flex-row sm:items-center sm:gap-2",
2143
- children: [
2144
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
2145
- bundleId: bundle.id,
2146
- maxLength: 12,
2147
- fullOnMobile: true
2148
- }),
2149
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ArrowRight, { className: "h-4 w-4 shrink-0 rotate-90 text-muted-foreground sm:rotate-0" }),
2150
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
2151
- bundleId: childBundle.id,
2152
- maxLength: 12,
2153
- fullOnMobile: true
2154
- })
2155
- ]
2156
- }) }),
2157
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
2158
- variant: "secondary",
2159
- children: "bsdiff"
2160
- }) }),
2161
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
2162
- className: "tabular-nums",
2163
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TimestampDisplay, { uuid: childBundle.id })
2164
- }),
2165
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
2166
- className: "text-right",
2167
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
2168
- type: "button",
2169
- variant: "outline",
2170
- size: "sm",
2171
- onClick: () => onDetailClick(childBundle),
2172
- children: "Detail"
2173
- })
2174
- })
2175
- ] }, childBundle.id)) })] })
2176
- })]
2177
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2178
- className: "flex flex-col gap-2",
2179
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2180
- className: "text-xs font-semibold uppercase text-muted-foreground/70",
2181
- children: "Patch bundles from this base"
2182
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2183
- className: "rounded-md border bg-background p-3 text-sm text-muted-foreground",
2184
- children: "No direct patch bundles."
2185
- })]
2186
- })]
2187
- })
2188
- });
2189
- }
2190
- var columnHelper = createColumnHelper();
2191
- function BundleIdCell({ bundle, expandedBundleId, onDetailClick, onToggleExpand }) {
2192
- const isExpanded = bundle.id === expandedBundleId;
2193
- const panelId = `bundle-lineage-panel-${bundle.id}`;
2194
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2195
- className: "flex min-w-[240px] items-center gap-3",
2196
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
2197
- type: "button",
2198
- variant: "ghost",
2199
- size: "icon",
2200
- className: "size-8 shrink-0 touch-manipulation",
2201
- "aria-label": isExpanded ? "Hide Lineage" : "Show Lineage",
2202
- "aria-controls": panelId,
2203
- "aria-expanded": isExpanded,
2204
- onClick: (event) => {
2205
- event.stopPropagation();
2206
- onToggleExpand(bundle);
2207
- },
2208
- children: isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, { "aria-hidden": "true" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { "aria-hidden": "true" })
2209
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
2210
- type: "button",
2211
- className: cn("flex min-w-0 flex-col items-start rounded-sm text-left transition-colors", "focus-visible:ring-ring/30 focus-visible:ring-[2px] outline-none", "text-muted-foreground hover:text-foreground"),
2212
- "aria-label": `Open details for bundle ${bundle.id}`,
2213
- onClick: (event) => {
2214
- event.stopPropagation();
2215
- onDetailClick(bundle);
2216
- },
2217
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2218
- className: "min-w-0 text-foreground",
2219
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
2220
- bundleId: bundle.id,
2221
- fullOnMobile: true
2222
- })
2223
- })
2224
- })]
2225
- });
2226
- }
2227
- var createBundleColumns = ({ expandedBundleId, patchCountsByBundleId, onDetailClick, onToggleExpand }) => [
2228
- columnHelper.accessor("id", {
2229
- header: "Bundle ID",
2230
- cell: (info) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdCell, {
2231
- bundle: info.row.original,
2232
- expandedBundleId,
2233
- onDetailClick,
2234
- onToggleExpand
2235
- })
2236
- }),
2237
- columnHelper.accessor("channel", {
2238
- header: "Channel",
2239
- cell: (info) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChannelBadge, { channel: info.getValue() })
2240
- }),
2241
- columnHelper.accessor("platform", {
2242
- header: "Platform",
2243
- cell: (info) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2244
- className: "flex items-center gap-2",
2245
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlatformIcon, {
2246
- platform: info.getValue(),
2247
- className: "h-4 w-4"
2248
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: info.getValue() === "ios" ? "iOS" : "Android" })]
2249
- })
2250
- }),
2251
- columnHelper.display({
2252
- id: "patches",
2253
- header: "Patches",
2254
- cell: (info) => {
2255
- const count = patchCountsByBundleId[info.row.original.id];
2256
- if (count === void 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2257
- className: "text-sm text-muted-foreground",
2258
- children: "Checking"
2259
- });
2260
- if (count === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2261
- className: "text-sm text-muted-foreground",
2262
- children: "-"
2263
- });
2264
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
2265
- variant: "secondary",
2266
- children: [
2267
- count,
2268
- " ",
2269
- count === 1 ? "patch" : "patches"
2270
- ]
2271
- });
2272
- }
2273
- }),
2274
- columnHelper.display({
2275
- id: "target",
2276
- header: "Target",
2277
- cell: (info) => {
2278
- const row = info.row.original;
2279
- if (row.fingerprintHash) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2280
- className: "flex min-w-[220px] items-start gap-2",
2281
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FingerprintPattern, { className: "mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HashValueDisplay, {
2282
- value: row.fingerprintHash,
2283
- maxLength: 12
2284
- })]
2285
- });
2286
- if (row.targetAppVersion) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2287
- className: "flex items-center gap-2",
2288
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Package, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2289
- translate: "no",
2290
- className: "text-sm",
2291
- children: row.targetAppVersion
2292
- })]
2293
- });
2294
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2295
- className: "text-sm text-muted-foreground",
2296
- children: "-"
2297
- });
2298
- }
2299
- }),
2300
- columnHelper.accessor("enabled", {
2301
- header: "Enabled",
2302
- cell: (info) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnabledStatusIcon, { enabled: info.getValue() })
2303
- }),
2304
- columnHelper.accessor("shouldForceUpdate", {
2305
- header: "Force Update",
2306
- cell: (info) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnabledStatusIcon, {
2307
- enabled: info.getValue(),
2308
- falseIcon: "minus"
2309
- })
2310
- }),
2311
- columnHelper.accessor("rolloutCohortCount", {
2312
- header: "Rollout",
2313
- cell: (info) => {
2314
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RolloutPercentageBadge, { percentage: (info.getValue() ?? 1e3) / 10 });
2315
- }
2316
- }),
2317
- columnHelper.accessor("message", {
2318
- header: "Message",
2319
- cell: (info) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2320
- className: "text-sm text-muted-foreground",
2321
- children: info.getValue() || "-"
2322
- })
2323
- }),
2324
- columnHelper.accessor("id", {
2325
- id: "created",
2326
- header: "Created",
2327
- cell: (info) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TimestampDisplay, { uuid: info.getValue() })
2328
- })
2329
- ];
2330
- var statusLabels = {
2331
- queued: "Queued",
2332
- deleting: "Deleting",
2333
- deleted: "Deleted",
2334
- failed: "Failed"
2335
- };
2336
- var getDeleteErrorMessage = (error) => error instanceof Error ? error.message : "Delete request failed";
2337
- var createDeleteItems = (bundles) => bundles.map((bundle) => ({
2338
- bundle,
2339
- status: "queued"
2340
- }));
2341
- function getStatusIcon(status) {
2342
- switch (status) {
2343
- case "failed": return {
2344
- className: "size-3.5 text-destructive",
2345
- Icon: CircleX
2346
- };
2347
- case "deleting": return {
2348
- className: "size-3.5 animate-spin text-primary",
2349
- Icon: LoaderCircle
2350
- };
2351
- case "deleted": return {
2352
- className: "size-3.5 text-primary",
2353
- Icon: CircleCheck
2354
- };
2355
- }
2356
- }
2357
- function DeleteStatusIcon({ status }) {
2358
- if (status === "queued") return null;
2359
- const label = statusLabels[status];
2360
- const { className, Icon } = getStatusIcon(status);
2361
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Tooltip$1, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipTrigger, {
2362
- asChild: true,
2363
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2364
- "aria-label": label,
2365
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { className })
2366
- })
2367
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipContent, { children: label })] });
2368
- }
2369
- function SelectedBundlesDeleteDialog({ bundles, open, onOpenChange, onComplete }) {
2370
- const deleteBundlesMutation = useDeleteBundlesMutation();
2371
- const [phase, setPhase] = (0, import_react.useState)("confirming");
2372
- const [items, setItems] = (0, import_react.useState)(() => createDeleteItems(bundles));
2373
- const isDeleting = phase === "deleting";
2374
- const totalCount = items.length;
2375
- const activeCount = items.filter((item) => item.status === "deleted").length + items.filter((item) => item.status === "failed").length;
2376
- const failedBundleIds = items.filter((item) => item.status === "failed").map((item) => item.bundle.id);
2377
- const hasFailures = failedBundleIds.length > 0;
2378
- const title = phase === "confirming" ? "Delete selected bundles?" : "Deleting bundles";
2379
- const description = phase === "confirming" ? `This action cannot be undone. This deletes ${bundles.length} bundle records and schedules artifact cleanup; shared assets are reclaimed by storage prune.` : phase === "deleting" ? `Deleting ${totalCount} bundles in one batch.` : `${activeCount} of ${totalCount} delete requests finished.`;
2380
- const deleteButtonLabel = isDeleting ? "Deleting..." : "Delete";
2381
- (0, import_react.useEffect)(() => {
2382
- if (open && phase === "confirming") setItems(createDeleteItems(bundles));
2383
- }, [
2384
- bundles,
2385
- open,
2386
- phase
2387
- ]);
2388
- const handleOpenChange = (nextOpen) => {
2389
- if (!nextOpen && isDeleting) return;
2390
- onOpenChange(nextOpen);
2391
- if (!nextOpen) {
2392
- setPhase("confirming");
2393
- setItems(createDeleteItems(bundles));
2394
- }
2395
- };
2396
- const runDeletion = async (bundleIds) => {
2397
- if (isDeleting || bundleIds.length === 0) return;
2398
- const bundleIdSet = new Set(bundleIds);
2399
- setPhase("deleting");
2400
- setItems((currentItems) => currentItems.map((item) => bundleIdSet.has(item.bundle.id) ? {
2401
- bundle: item.bundle,
2402
- status: "deleting"
2403
- } : item));
2404
- try {
2405
- const result = await deleteBundlesMutation.mutateAsync({ bundleIds: [...bundleIds] });
2406
- setItems((currentItems) => currentItems.map((item) => bundleIdSet.has(item.bundle.id) ? {
2407
- bundle: item.bundle,
2408
- status: "deleted"
2409
- } : item));
2410
- setPhase("complete");
2411
- onComplete({
2412
- deletedBundleIds: bundleIds,
2413
- failedBundleIds: []
2414
- });
2415
- toast.success(result.missingBundleIds.length > 0 ? `${result.deletedBundleIds.length} deleted, ${result.missingBundleIds.length} already absent` : `${bundleIds.length} bundles deleted successfully`);
2416
- } catch (error) {
2417
- setItems((currentItems) => currentItems.map((item) => bundleIdSet.has(item.bundle.id) ? {
2418
- bundle: item.bundle,
2419
- message: getDeleteErrorMessage(error),
2420
- status: "failed"
2421
- } : item));
2422
- setPhase("complete");
2423
- onComplete({
2424
- deletedBundleIds: [],
2425
- failedBundleIds: bundleIds
2426
- });
2427
- toast.error(`0 deleted, ${bundleIds.length} failed`);
2428
- }
2429
- };
2430
- const handleDelete = () => {
2431
- runDeletion(items.map((item) => item.bundle.id));
2432
- };
2433
- const handleRetryFailed = () => {
2434
- runDeletion(failedBundleIds);
2435
- };
2436
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog$1, {
2437
- open,
2438
- onOpenChange: handleOpenChange,
2439
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogContent, {
2440
- showCloseButton: !isDeleting,
2441
- className: "sm:max-w-lg",
2442
- onEscapeKeyDown: (event) => {
2443
- if (isDeleting) event.preventDefault();
2444
- },
2445
- onInteractOutside: (event) => {
2446
- if (isDeleting) event.preventDefault();
2447
- },
2448
- children: [
2449
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle, { children: title }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogDescription, { children: description })] }),
2450
- phase === "confirming" ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, {
2451
- className: "max-h-[50vh] overflow-y-auto p-0",
2452
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Table, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHeader, {
2453
- className: "sticky top-0 bg-card",
2454
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
2455
- className: "w-12 text-center",
2456
- children: "Status"
2457
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, { children: "Bundle" })] })
2458
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableBody, { children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
2459
- className: "text-center align-middle",
2460
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2461
- className: "flex justify-center",
2462
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DeleteStatusIcon, { status: item.status })
2463
- })
2464
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
2465
- className: "whitespace-normal",
2466
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2467
- className: "min-w-0",
2468
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2469
- className: "break-all font-mono text-[11px] text-foreground",
2470
- children: item.bundle.id
2471
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2472
- className: "mt-0.5 flex flex-wrap gap-x-2 gap-y-1 text-[11px] text-muted-foreground",
2473
- children: [
2474
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: item.bundle.channel }),
2475
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: item.bundle.platform }),
2476
- item.message ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2477
- className: "text-destructive",
2478
- children: item.message
2479
- }) : null
2480
- ]
2481
- })]
2482
- })
2483
- })] }, item.bundle.id)) })] })
2484
- }) }),
2485
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogFooter, { children: [
2486
- phase === "confirming" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
2487
- variant: "outline",
2488
- onClick: () => handleOpenChange(false),
2489
- children: "Cancel"
2490
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
2491
- variant: "destructive",
2492
- onClick: handleDelete,
2493
- disabled: totalCount === 0,
2494
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Trash2, { "data-icon": "inline-start" }), "Delete"]
2495
- })] }) : null,
2496
- phase === "deleting" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
2497
- variant: "destructive",
2498
- disabled: true,
2499
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(LoaderCircle, {
2500
- "data-icon": "inline-start",
2501
- className: "animate-spin"
2502
- }), deleteButtonLabel]
2503
- }) : null,
2504
- phase === "complete" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
2505
- variant: "outline",
2506
- onClick: () => handleOpenChange(false),
2507
- children: "Close"
2508
- }), hasFailures ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
2509
- onClick: handleRetryFailed,
2510
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(RotateCcw, { "data-icon": "inline-start" }), "Retry failed"]
2511
- }) : null] }) : null
2512
- ] })
2513
- ]
2514
- })
2515
- });
2516
- }
2517
- function MobileStatusBadge({ enabled, trueLabel, falseLabel, falseIcon = "x", trueTone = "success" }) {
2518
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2519
- className: cn("inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-[11px] font-medium", enabled ? trueTone === "warning" ? "bg-amber-500/14 text-amber-700 dark:text-amber-300" : "bg-emerald-500/12 text-emerald-700 dark:text-emerald-300" : falseIcon === "minus" ? "bg-muted text-muted-foreground" : "bg-red-500/12 text-red-700 dark:text-red-300"),
2520
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnabledStatusIcon, {
2521
- enabled,
2522
- falseIcon,
2523
- colorMode: "inherit",
2524
- className: "h-3.5 w-3.5"
2525
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: enabled ? trueLabel : falseLabel })]
2526
- });
2527
- }
2528
- function BundlesTable({ bundles, pagination, expandedBundleId, selectedBundleId, onExpandedBundleChange, onDetailClick }) {
2529
- const { setFilters } = useFilterParams();
2530
- const isMobile = useIsMobile();
2531
- const [selectedBundleIds, setSelectedBundleIds] = (0, import_react.useState)([]);
2532
- const [deleteDialogOpen, setDeleteDialogOpen] = (0, import_react.useState)(false);
2533
- const cursorPagination = pagination;
2534
- const bundleIds = bundles.map((bundle) => bundle.id);
2535
- const selectedBundles = bundles.filter((bundle) => selectedBundleIds.includes(bundle.id));
2536
- const allBundlesSelected = bundles.length > 0 && selectedBundles.length === bundles.length;
2537
- const { data: patchCountsByBundleId = {} } = useBundleChildCountsQuery(bundleIds);
2538
- const { data: childBundles = [], isLoading: isChildBundlesLoading } = useBundleChildrenQuery(expandedBundleId ?? "");
2539
- const bundleColumns = createBundleColumns({
2540
- expandedBundleId,
2541
- patchCountsByBundleId,
2542
- onDetailClick,
2543
- onToggleExpand: (bundle) => onExpandedBundleChange(expandedBundleId === bundle.id ? void 0 : bundle.id)
2544
- });
2545
- const table = useReactTable({
2546
- data: bundles,
2547
- columns: bundleColumns,
2548
- getCoreRowModel: getCoreRowModel()
2549
- });
2550
- const hasNextPage = pagination?.hasNextPage ?? false;
2551
- const hasPreviousPage = pagination?.hasPreviousPage ?? false;
2552
- const currentPage = pagination?.currentPage ?? 1;
2553
- const totalPages = pagination?.totalPages ?? 0;
2554
- const handlePreviousPage = () => {
2555
- const previousCursor = cursorPagination?.previousCursor ?? bundles[0]?.id;
2556
- if (!previousCursor) return;
2557
- setFilters({
2558
- page: Math.max(1, currentPage - 1),
2559
- after: void 0,
2560
- before: previousCursor
2561
- });
2562
- };
2563
- const handleNextPage = () => {
2564
- const nextCursor = cursorPagination?.nextCursor ?? bundles.at(-1)?.id;
2565
- if (!nextCursor) return;
2566
- setFilters({
2567
- page: currentPage + 1,
2568
- after: nextCursor,
2569
- before: void 0
2570
- });
2571
- };
2572
- const toggleBundleSelection = (bundleId) => {
2573
- setSelectedBundleIds((current) => current.includes(bundleId) ? current.filter((selectedBundleId) => selectedBundleId !== bundleId) : [...current, bundleId]);
2574
- };
2575
- const handleToggleAllBundles = () => {
2576
- setSelectedBundleIds(allBundlesSelected ? [] : bundleIds);
2577
- };
2578
- const startEntry = bundles.length === 0 ? 0 : (currentPage - 1) * 20 + 1;
2579
- const endEntry = startEntry === 0 ? 0 : startEntry + bundles.length - 1;
2580
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2581
- className: "flex min-h-0 min-w-0 flex-1 flex-col gap-4",
2582
- children: [
2583
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2584
- className: "min-h-0 min-w-0 flex-1 overflow-hidden rounded-lg border bg-card text-card-foreground shadow-sm [&_[data-slot=table-container]]:h-full [&_[data-slot=table-container]]:overflow-auto",
2585
- children: isMobile ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2586
- className: "flex h-full flex-col overflow-y-auto",
2587
- children: bundles.length ? bundles.map((bundle) => {
2588
- const isExpanded = bundle.id === expandedBundleId;
2589
- const panelId = `bundle-lineage-panel-${bundle.id}`;
2590
- const rolloutPercentage = (bundle.rolloutCohortCount ?? 1e3) / 10;
2591
- const patchCount = patchCountsByBundleId[bundle.id];
2592
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2593
- "data-state": bundle.id === selectedBundleId ? "selected" : void 0,
2594
- className: cn("border-b border-border/60 last:border-b-0 data-[state=selected]:bg-muted/20", isExpanded && "bg-primary/5"),
2595
- children: [
2596
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", {
2597
- className: "flex items-center gap-2 border-b border-border/60 px-4 py-3 text-xs font-medium text-muted-foreground",
2598
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
2599
- type: "checkbox",
2600
- checked: selectedBundleIds.includes(bundle.id),
2601
- className: "size-4 accent-primary",
2602
- onChange: () => toggleBundleSelection(bundle.id)
2603
- }), "Select bundle"]
2604
- }),
2605
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
2606
- type: "button",
2607
- "aria-label": `Open bundle ${bundle.id}`,
2608
- className: "flex w-full flex-col gap-4 p-4 text-left",
2609
- onClick: () => onDetailClick(bundle),
2610
- children: [
2611
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2612
- className: "flex items-start justify-between gap-3",
2613
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2614
- className: "min-w-0 space-y-2",
2615
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2616
- className: "text-[11px] font-semibold uppercase tracking-[0.18em] text-muted-foreground/70",
2617
- children: "Bundle"
2618
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2619
- className: "min-w-0 text-sm font-medium",
2620
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, {
2621
- bundleId: bundle.id,
2622
- maxLength: 18,
2623
- fullOnMobile: true
2624
- })
2625
- })]
2626
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2627
- className: "shrink-0",
2628
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChannelBadge, { channel: bundle.channel })
2629
- })]
2630
- }),
2631
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2632
- className: "grid grid-cols-2 gap-3 text-sm",
2633
- children: [
2634
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2635
- className: "rounded-md bg-muted/40 p-3",
2636
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2637
- className: "mb-1 text-[11px] font-medium uppercase text-muted-foreground/70",
2638
- children: "Platform"
2639
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2640
- className: "flex items-center gap-2",
2641
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlatformIcon, {
2642
- platform: bundle.platform,
2643
- className: "h-4 w-4"
2644
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: bundle.platform === "ios" ? "iOS" : "Android" })]
2645
- })]
2646
- }),
2647
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2648
- className: "rounded-md bg-muted/40 p-3",
2649
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2650
- className: "mb-1 text-[11px] font-medium uppercase text-muted-foreground/70",
2651
- children: "Created"
2652
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2653
- className: "text-xs text-foreground",
2654
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TimestampDisplay, { uuid: bundle.id })
2655
- })]
2656
- }),
2657
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2658
- className: "rounded-md bg-muted/40 p-3",
2659
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2660
- className: "mb-1 text-[11px] font-medium uppercase text-muted-foreground/70",
2661
- children: "Rollout"
2662
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RolloutPercentageBadge, { percentage: rolloutPercentage })]
2663
- }),
2664
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2665
- className: "rounded-md bg-muted/40 p-3",
2666
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2667
- className: "mb-1 text-[11px] font-medium uppercase text-muted-foreground/70",
2668
- children: "Patches"
2669
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2670
- className: "text-xs text-foreground",
2671
- children: patchCount === void 0 ? "Checking" : patchCount > 0 ? `${patchCount} ${patchCount === 1 ? "patch" : "patches"}` : "-"
2672
- })]
2673
- }),
2674
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2675
- className: "rounded-md bg-muted/40 p-3",
2676
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2677
- className: "mb-1 text-[11px] font-medium uppercase text-muted-foreground/70",
2678
- children: "Status"
2679
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2680
- className: "flex flex-wrap items-center gap-2",
2681
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MobileStatusBadge, {
2682
- enabled: bundle.enabled,
2683
- trueLabel: "Enabled",
2684
- falseLabel: "Disabled"
2685
- }), bundle.shouldForceUpdate ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MobileStatusBadge, {
2686
- enabled: bundle.shouldForceUpdate,
2687
- trueLabel: "Force update",
2688
- falseLabel: "Optional",
2689
- falseIcon: "minus",
2690
- trueTone: "warning"
2691
- }) : null]
2692
- })]
2693
- })
2694
- ]
2695
- }),
2696
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2697
- className: "grid gap-2 rounded-md border border-border/70 bg-background/80 p-3 text-sm",
2698
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2699
- className: "flex items-center justify-between gap-3",
2700
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2701
- className: "text-muted-foreground",
2702
- children: "Target"
2703
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2704
- className: "min-w-0 text-right",
2705
- children: bundle.fingerprintHash ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2706
- translate: "no",
2707
- className: "inline-flex min-w-0 items-start gap-2 font-mono text-xs",
2708
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FingerprintPattern, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(HashValueDisplay, {
2709
- value: bundle.fingerprintHash,
2710
- maxLength: 12
2711
- })]
2712
- }) : bundle.targetAppVersion ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
2713
- translate: "no",
2714
- className: "inline-flex items-center gap-2 text-xs",
2715
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Package, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }), bundle.targetAppVersion]
2716
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2717
- className: "text-xs text-muted-foreground",
2718
- children: "-"
2719
- })
2720
- })]
2721
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2722
- className: "flex items-start justify-between gap-3",
2723
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2724
- className: "text-muted-foreground",
2725
- children: "Message"
2726
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2727
- className: "min-w-0 text-right text-xs text-foreground/80",
2728
- children: bundle.message || "-"
2729
- })]
2730
- })]
2731
- })
2732
- ]
2733
- }),
2734
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2735
- className: "border-t border-border/60 px-4 py-3",
2736
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
2737
- type: "button",
2738
- variant: "ghost",
2739
- className: "h-9 w-full justify-between px-3 text-sm",
2740
- "aria-label": isExpanded ? "Hide Lineage" : "Show Lineage",
2741
- "aria-controls": panelId,
2742
- "aria-expanded": isExpanded,
2743
- onClick: () => onExpandedBundleChange(isExpanded ? void 0 : bundle.id),
2744
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: patchCount === void 0 ? "Patch lineage" : `Patch lineage (${patchCount})` }), isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronUp, {
2745
- className: "h-4 w-4",
2746
- "aria-hidden": "true"
2747
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, {
2748
- className: "h-4 w-4",
2749
- "aria-hidden": "true"
2750
- })]
2751
- })
2752
- }),
2753
- isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleChildrenPanel, {
2754
- panelId,
2755
- bundle,
2756
- bundles: childBundles,
2757
- loading: isChildBundlesLoading,
2758
- onDetailClick
2759
- }) : null
2760
- ]
2761
- }, bundle.id);
2762
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
2763
- className: "flex h-32 items-center justify-center px-6 text-center text-sm text-muted-foreground",
2764
- children: "No bundles found matching your filters."
2765
- })
2766
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Table, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHeader, {
2767
- className: "bg-muted/40",
2768
- children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, {
2769
- className: "border-b border-border/60 hover:bg-transparent",
2770
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
2771
- className: "w-10",
2772
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
2773
- type: "checkbox",
2774
- checked: allBundlesSelected,
2775
- "aria-label": "Select all bundles",
2776
- className: "size-4 accent-primary",
2777
- onChange: handleToggleAllBundles
2778
- })
2779
- }), headerGroup.headers.map((header) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
2780
- className: "h-10 text-xs font-semibold uppercase text-muted-foreground/70",
2781
- children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())
2782
- }, header.id))]
2783
- }, headerGroup.id))
2784
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableBody, { children: table.getRowModel().rows?.length ? table.getRowModel().rows.map((row) => {
2785
- const isExpanded = row.original.id === expandedBundleId;
2786
- const panelId = `bundle-lineage-panel-${row.original.id}`;
2787
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react.Fragment, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(TableRow, {
2788
- "data-state": row.original.id === selectedBundleId ? "selected" : void 0,
2789
- className: cn("cursor-pointer transition-colors hover:bg-muted/10 focus-within:bg-muted/15 data-[state=selected]:bg-muted/15", isExpanded && "bg-primary/5"),
2790
- onClick: () => onDetailClick(row.original),
2791
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
2792
- className: "py-3",
2793
- onClick: (event) => event.stopPropagation(),
2794
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
2795
- type: "checkbox",
2796
- checked: selectedBundleIds.includes(row.original.id),
2797
- "aria-label": `Select bundle ${row.original.id}`,
2798
- className: "size-4 accent-primary",
2799
- onChange: () => toggleBundleSelection(row.original.id)
2800
- })
2801
- }), row.getVisibleCells().map((cell) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
2802
- className: "py-3",
2803
- children: flexRender(cell.column.columnDef.cell, cell.getContext())
2804
- }, cell.id))]
2805
- }), isExpanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableRow, {
2806
- className: "hover:bg-transparent",
2807
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
2808
- colSpan: bundleColumns.length + 1,
2809
- className: "border-t-0 p-0",
2810
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleChildrenPanel, {
2811
- panelId,
2812
- bundle: row.original,
2813
- bundles: childBundles,
2814
- loading: isChildBundlesLoading,
2815
- onDetailClick
2816
- })
2817
- })
2818
- }) : null] }, row.original.id);
2819
- }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableRow, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
2820
- colSpan: bundleColumns.length + 1,
2821
- className: "h-32 text-center text-muted-foreground",
2822
- children: "No bundles found matching your filters."
2823
- }) }) })] })
2824
- }),
2825
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2826
- className: "flex flex-col gap-3 px-2 sm:flex-row sm:items-center sm:justify-between",
2827
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2828
- className: "text-xs font-medium text-muted-foreground",
2829
- children: [
2830
- "Showing ",
2831
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2832
- className: "text-foreground",
2833
- children: startEntry
2834
- }),
2835
- " to",
2836
- " ",
2837
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2838
- className: "text-foreground",
2839
- children: endEntry
2840
- }),
2841
- " entries"
2842
- ]
2843
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2844
- className: "flex flex-wrap items-center gap-3 sm:justify-end",
2845
- children: [
2846
- selectedBundles.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
2847
- type: "button",
2848
- variant: "destructive",
2849
- size: "sm",
2850
- className: "h-8 flex-1 px-3 text-xs sm:flex-none",
2851
- onClick: () => setDeleteDialogOpen(true),
2852
- children: [
2853
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Trash2, { "data-icon": "inline-start" }),
2854
- "Delete selected (",
2855
- selectedBundles.length,
2856
- ")"
2857
- ]
2858
- }) : null,
2859
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2860
- className: "text-xs font-medium text-muted-foreground",
2861
- children: [
2862
- "Page ",
2863
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2864
- className: "text-foreground",
2865
- children: currentPage
2866
- }),
2867
- totalPages > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2868
- " ",
2869
- "of ",
2870
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2871
- className: "text-foreground",
2872
- children: totalPages
2873
- })
2874
- ] }) : null
2875
- ]
2876
- }),
2877
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
2878
- variant: "outline",
2879
- size: "sm",
2880
- onClick: handlePreviousPage,
2881
- disabled: !hasPreviousPage,
2882
- className: "h-8 flex-1 px-3 text-xs sm:flex-none",
2883
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronLeft, { "data-icon": "inline-start" }), "Previous"]
2884
- }),
2885
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
2886
- variant: "outline",
2887
- size: "sm",
2888
- onClick: handleNextPage,
2889
- disabled: !hasNextPage,
2890
- className: "h-8 flex-1 px-3 text-xs sm:flex-none",
2891
- children: ["Next", /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { "data-icon": "inline-end" })]
2892
- })
2893
- ]
2894
- })]
2895
- }),
2896
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectedBundlesDeleteDialog, {
2897
- bundles: selectedBundles,
2898
- open: deleteDialogOpen,
2899
- onOpenChange: setDeleteDialogOpen,
2900
- onComplete: ({ failedBundleIds }) => {
2901
- setSelectedBundleIds([...failedBundleIds]);
2902
- }
2903
- })
2904
- ]
2905
- });
2906
- }
2907
- function FilterToolbar() {
2908
- const { filters, setFilters, resetFilters } = useFilterParams();
2909
- const { data: channels = [] } = useChannelsQuery();
2910
- const [targetAppVersion, setTargetAppVersion] = (0, import_react.useState)(filters.targetAppVersion ?? "");
2911
- (0, import_react.useEffect)(() => {
2912
- setTargetAppVersion(filters.targetAppVersion ?? "");
2913
- }, [filters.targetAppVersion]);
2914
- const hasActiveFilters = filters.channel || filters.platform || filters.targetAppVersion;
2915
- const applyTargetAppVersion = () => {
2916
- setFilters({ targetAppVersion: targetAppVersion.trim() || void 0 });
2917
- };
2918
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
2919
- className: "sticky top-0 z-10 flex shrink-0 flex-wrap items-center gap-2 border-b bg-background px-3 py-3 sm:h-12 sm:flex-nowrap sm:bg-card/70 sm:px-4 sm:py-0 sm:backdrop-blur-sm",
2920
- children: [
2921
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarTrigger, { className: "-ml-1" }),
2922
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
2923
- className: "ml-1 flex items-center gap-1.5 text-muted-foreground sm:ml-2",
2924
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Funnel, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
2925
- className: "text-xs font-medium",
2926
- children: "Filters"
2927
- })]
2928
- }),
2929
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Select$1, {
2930
- value: filters.platform || "all",
2931
- onValueChange: (value) => setFilters({ platform: value === "all" ? void 0 : value }),
2932
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, {
2933
- className: "h-8 w-[calc(50%-0.25rem)] min-w-[132px] text-xs sm:w-[140px]",
2934
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue, { placeholder: "All Platforms" })
2935
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectContent, { children: [
2936
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
2937
- value: "all",
2938
- children: "All Platforms"
2939
- }),
2940
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
2941
- value: "ios",
2942
- children: "iOS"
2943
- }),
2944
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
2945
- value: "android",
2946
- children: "Android"
2947
- })
2948
- ] })]
2949
- }),
2950
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
2951
- "aria-label": "Target app version",
2952
- className: "h-8 w-full min-w-[132px] text-xs sm:w-[160px]",
2953
- placeholder: "Target version",
2954
- value: targetAppVersion,
2955
- onBlur: applyTargetAppVersion,
2956
- onChange: (event) => setTargetAppVersion(event.target.value),
2957
- onKeyDown: (event) => {
2958
- if (event.key === "Enter") applyTargetAppVersion();
2959
- }
2960
- }),
2961
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Select$1, {
2962
- value: filters.channel || "all",
2963
- onValueChange: (value) => setFilters({ channel: value === "all" ? void 0 : value }),
2964
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, {
2965
- className: "h-8 w-[calc(50%-0.25rem)] min-w-[132px] text-xs sm:w-[140px]",
2966
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue, { placeholder: "All Channels" })
2967
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectContent, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
2968
- value: "all",
2969
- children: "All Channels"
2970
- }), channels.map((channel) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
2971
- value: channel,
2972
- children: channel
2973
- }, channel))] })]
2974
- }),
2975
- hasActiveFilters && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
2976
- variant: "ghost",
2977
- size: "sm",
2978
- onClick: resetFilters,
2979
- className: "h-8 px-2 text-xs text-muted-foreground hover:text-foreground sm:ml-auto",
2980
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "h-3.5 w-3.5 mr-1" }), "Clear"]
2981
- })
2982
- ]
2983
- });
2984
- }
2985
- function BundlesPage() {
2986
- const { filters, bundleId, setBundleId } = useFilterParams();
2987
- const [expandedBundleId, setExpandedBundleId] = (0, import_react.useState)();
2988
- const activeBundleId = bundleId ?? "";
2989
- const { data: bundlesData, isLoading } = useBundlesQuery({
2990
- channel: filters.channel,
2991
- platform: filters.platform,
2992
- targetAppVersion: filters.targetAppVersion,
2993
- page: filters.page,
2994
- after: filters.after,
2995
- before: filters.before,
2996
- limit: "20"
2997
- });
2998
- const bundles = bundlesData?.data ?? [];
2999
- const pagination = bundlesData?.pagination;
3000
- const selectedBundleFromList = activeBundleId ? bundles.find((bundle) => bundle.id === activeBundleId) ?? null : null;
3001
- const { data: selectedBundleFromQuery, isPending: isSelectedBundlePending } = useBundleQuery(activeBundleId);
3002
- const selectedBundle = selectedBundleFromQuery ?? selectedBundleFromList;
3003
- const isSelectedBundleLoading = Boolean(activeBundleId) && !selectedBundle && isSelectedBundlePending;
3004
- (0, import_react.useEffect)(() => {
3005
- if (expandedBundleId && !bundles.some((bundle) => bundle.id === expandedBundleId)) setExpandedBundleId(void 0);
3006
- }, [bundles, expandedBundleId]);
3007
- if (isLoading) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
3008
- className: "flex h-svh flex-col",
3009
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FilterToolbar, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
3010
- className: "flex flex-1 flex-col gap-4 bg-muted/5 p-3 sm:p-6",
3011
- children: [
3012
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-12 w-full" }),
3013
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-12 w-full" }),
3014
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-12 w-full" }),
3015
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-12 w-full" })
3016
- ]
3017
- })]
3018
- });
3019
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
3020
- className: "flex h-svh min-h-0 flex-col",
3021
- children: [
3022
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FilterToolbar, {}),
3023
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
3024
- className: "flex min-h-0 min-w-0 flex-1 flex-col gap-6 bg-muted/5 p-3 sm:p-6",
3025
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundlesTable, {
3026
- bundles,
3027
- pagination,
3028
- expandedBundleId,
3029
- selectedBundleId: bundleId,
3030
- onExpandedBundleChange: setExpandedBundleId,
3031
- onDetailClick: (bundle) => {
3032
- setExpandedBundleId(void 0);
3033
- setBundleId(bundle.id);
3034
- }
3035
- })
3036
- }),
3037
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleEditorSheet, {
3038
- bundleId,
3039
- bundle: selectedBundle,
3040
- loading: isSelectedBundleLoading,
3041
- open: Boolean(bundleId),
3042
- onOpenChange: (open) => !open && setBundleId(void 0)
3043
- })
3044
- ]
3045
- });
3046
- }
3047
- //#endregion
3048
- export { BundlesPage as component };