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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/assets/BundleIdDisplay-D4ErdU5_.js +1 -0
  3. package/.output/public/assets/InsightsCapabilityContext-B42VqvP6.js +1 -0
  4. package/.output/public/assets/api-keys-api-DGVTTvTJ.js +1 -0
  5. package/.output/public/assets/api-keys-z16OZIP4.js +1 -0
  6. package/.output/public/assets/badge-Br_KDO0J.js +1 -0
  7. package/.output/public/assets/card-DzuEXvyJ.js +1 -0
  8. package/.output/public/assets/chart-no-axes-combined-FaYMGmQS.js +1 -0
  9. package/.output/public/assets/createServerFn-gM2-LFBy.js +3 -0
  10. package/.output/public/assets/dialog-BeAENgez.js +1 -0
  11. package/.output/public/assets/dist-Bssh_cv0.js +1 -0
  12. package/.output/public/assets/field-Buq148-A.js +1 -0
  13. package/.output/public/assets/index-uOpQAIC4.js +10 -0
  14. package/.output/public/assets/input-group-B3p5UpyW.js +1 -0
  15. package/.output/public/assets/insights-DJTr7Ozn.js +1 -0
  16. package/.output/public/assets/insights-api-DVMOoRMS.js +1 -0
  17. package/.output/public/assets/installations-BY64iIAK.js +2 -0
  18. package/.output/public/assets/installations-D5v9i3AL.js +1 -0
  19. package/.output/public/assets/lazyRouteComponent-DKYu5cZJ.js +1 -0
  20. package/.output/public/assets/preload-helper-CQ36nxok.js +1 -0
  21. package/.output/public/assets/refresh-cw-BkbfZ62O.js +1 -0
  22. package/.output/public/assets/routes-BuoI5-Cr.js +2 -0
  23. package/.output/public/assets/routes-C8fvgETO.js +1 -0
  24. package/.output/public/assets/search-qsIDvS2I.js +1 -0
  25. package/.output/public/assets/shield-check-DE_hVuHu.js +1 -0
  26. package/.output/public/assets/signing-mJD88Y4C.js +1 -0
  27. package/.output/public/assets/styles-BZIG7t42.css +2 -0
  28. package/.output/public/assets/table-RamFEclP.js +1 -0
  29. package/.output/public/assets/useCompositeListItem-ChzDqdpc.js +42 -0
  30. package/.output/public/assets/useMutation-JsGVjQMY.js +1 -0
  31. package/.output/public/assets/useOpenInteractionType-Dwfy-SpA.js +1 -0
  32. package/.output/server/__23tanstack-start-server-fn-resolver-Dftxlm2E.mjs +146 -0
  33. package/.output/server/_libs/@base-ui/react+[...].mjs +16124 -0
  34. package/.output/server/_libs/@reduxjs/toolkit+[...].mjs +1554 -0
  35. package/.output/server/_libs/@tanstack/react-router+[...].mjs +41 -10
  36. package/.output/server/_libs/@tanstack/react-router-ssr-query+[...].mjs +132 -0
  37. package/.output/server/_libs/d3-array.mjs +231 -0
  38. package/.output/server/_libs/d3-color.mjs +330 -0
  39. package/.output/server/_libs/d3-format.mjs +252 -0
  40. package/.output/server/_libs/d3-interpolate.mjs +207 -0
  41. package/.output/server/_libs/d3-path.mjs +77 -0
  42. package/.output/server/_libs/d3-scale+[...].mjs +1907 -0
  43. package/.output/server/_libs/d3-shape.mjs +818 -0
  44. package/.output/server/_libs/decimal.js-light.mjs +884 -0
  45. package/.output/server/_libs/es-toolkit.mjs +1134 -0
  46. package/.output/server/_libs/eventemitter3.mjs +236 -0
  47. package/.output/server/_libs/immer.mjs +532 -0
  48. package/.output/server/_libs/lucide-react.mjs +141 -13
  49. package/.output/server/_libs/next-themes.mjs +2 -2
  50. package/.output/server/_libs/react-redux+[...].mjs +205 -0
  51. package/.output/server/_libs/recharts+[...].mjs +14117 -0
  52. package/.output/server/_libs/sonner.mjs +2 -2
  53. package/.output/server/_libs/tanstack__query-core.mjs +114 -1
  54. package/.output/server/_libs/tanstack__react-query.mjs +3 -4
  55. package/.output/server/_runtime.mjs +10 -1
  56. package/.output/server/_ssr/BundleActivityChart-CWcdPuUn.mjs +288 -0
  57. package/.output/server/_ssr/BundleIdDisplay-BEvB3MvA.mjs +13 -0
  58. package/.output/server/_ssr/InsightsCapabilityContext-Cx8fjm2s.mjs +15 -0
  59. package/.output/server/_ssr/api-keys-CpBqHpuC.mjs +300 -0
  60. package/.output/server/_ssr/api-keys-api-Czkp57UV.mjs +60 -0
  61. package/.output/server/_ssr/api-keys-rpc-BTtMDSoR.mjs +136 -0
  62. package/.output/server/_ssr/api-rpc-BEX8ISai.mjs +1468 -0
  63. package/.output/server/_ssr/auth-BF8mbAgF.mjs +21 -0
  64. package/.output/server/_ssr/auth-rpc-BFMEC-TT.mjs +24 -0
  65. package/.output/server/_ssr/auth.server-DW71WzsD.mjs +47 -0
  66. package/.output/server/_ssr/badge-CpWXQwyF.mjs +26 -0
  67. package/.output/server/_ssr/bundle-signing-rpc-BbCFUQSu.mjs +16 -0
  68. package/.output/server/_ssr/bundle-signing.server-C4NW5mWx.mjs +12 -0
  69. package/.output/server/_ssr/card-Bsc8mRis.mjs +86 -0
  70. package/.output/server/_ssr/config-Dv8qLDZ0.mjs +13 -0
  71. package/.output/server/_ssr/config.server-DQYEr_5X.mjs +361 -0
  72. package/.output/server/_ssr/{createServerFn-DzU0k62V.mjs → createServerFn-B2hcXoPM.mjs} +1 -1
  73. package/.output/server/_ssr/createServerRpc-DB29C_5p.mjs +12 -0
  74. package/.output/server/_ssr/{sidebar-Cv_H0_Jv.mjs → createSsrRpc-DhfrxEjg.mjs} +68 -50
  75. package/.output/server/_ssr/databaseRows-QlCYLi0H.mjs +166 -0
  76. package/.output/server/_ssr/deleteBundle-Cu_XOMt0.mjs +66 -0
  77. package/.output/server/_ssr/dialog-C0vKUp8R.mjs +170 -0
  78. package/.output/server/_ssr/dist-BVSLTpoP.mjs +250 -0
  79. package/.output/server/_ssr/downloadBundle-CNnYxYO2.mjs +24 -0
  80. package/.output/server/_ssr/{empty-plugin-adapters-B4HCzgmM.mjs → empty-plugin-adapters-B5QvGpEl.mjs} +1 -1
  81. package/.output/server/_ssr/field-DASCh-5d.mjs +74 -0
  82. package/.output/server/_ssr/{getBundleChildren-CVtSiH5t.mjs → getBundleChildren-BcRMFoDF.mjs} +10 -18
  83. package/.output/server/_ssr/input-group-etIsBUqv.mjs +288 -0
  84. package/.output/server/_ssr/insights-api-C1F7UreR.mjs +71 -0
  85. package/.output/server/_ssr/insights-input-9fEJF-ZG.mjs +96 -0
  86. package/.output/server/_ssr/insights-rpc-C-8R6kED.mjs +155 -0
  87. package/.output/server/_ssr/insights-ygT_aIfo.mjs +858 -0
  88. package/.output/server/_ssr/installations-DqRT1QTd.mjs +535 -0
  89. package/.output/server/_ssr/installations-K8TCYTYh.mjs +19 -0
  90. package/.output/server/_ssr/router-BxBEvtF1.mjs +513 -0
  91. package/.output/server/_ssr/routes-BC7rn-tf.mjs +37 -0
  92. package/.output/server/_ssr/routes-CM5KAwpB.mjs +2285 -0
  93. package/.output/server/_ssr/runtime.server-la7TRAH_.mjs +527 -0
  94. package/.output/server/_ssr/server-CpzW--lT.mjs +7468 -0
  95. package/.output/server/_ssr/signing-jk4HANnl.mjs +134 -0
  96. package/.output/server/_ssr/ssr.mjs +4 -5740
  97. package/.output/server/_ssr/start-DpViQGnt.mjs +4 -0
  98. package/.output/server/_ssr/table-CU58HeI7.mjs +53 -0
  99. package/.output/server/_ssr/uuidv7-CaKuKXrV.mjs +31 -0
  100. package/.output/server/_tanstack-start-manifest_v-Mz9MmSlD.mjs +103 -0
  101. package/.output/server/index.mjs +229 -47
  102. package/README.md +29 -8
  103. package/lib/index.d.mts +35 -0
  104. package/lib/index.mjs +4 -0
  105. package/lib/vite-internal.mjs +127 -0
  106. package/lib/vite.d.mts +10 -0
  107. package/lib/vite.mjs +5 -0
  108. package/package.json +51 -29
  109. package/public/apple-touch-icon.png +0 -0
  110. package/public/favicon-16x16.png +0 -0
  111. package/public/favicon-32x32.png +0 -0
  112. package/public/favicon.ico +0 -0
  113. package/public/logo.svg +1 -0
  114. package/public/manifest.json +30 -0
  115. package/public/robots.txt +3 -0
  116. package/src/components/AppSidebar.tsx +159 -0
  117. package/src/components/BundleIdDisplay.tsx +18 -0
  118. package/src/components/ChannelBadge.tsx +31 -0
  119. package/src/components/ConsoleAccessPage.tsx +122 -0
  120. package/src/components/EnabledStatusIcon.tsx +54 -0
  121. package/src/components/HashValueDisplay.tsx +85 -0
  122. package/src/components/HotUpdaterLogo.tsx +30 -0
  123. package/src/components/NotFoundPage.tsx +33 -0
  124. package/src/components/PlatformIcon.tsx +39 -0
  125. package/src/components/RolloutPercentageBadge.tsx +28 -0
  126. package/src/components/ThemeProvider.tsx +116 -0
  127. package/src/components/TimestampDisplay.tsx +41 -0
  128. package/src/components/features/api-keys/ApiKeysPage.tsx +377 -0
  129. package/src/components/features/bundles/BundleActivityChart.tsx +193 -0
  130. package/src/components/features/bundles/BundleChildrenPanel.tsx +201 -0
  131. package/src/components/features/bundles/BundleInsightsSummary.tsx +123 -0
  132. package/src/components/features/bundles/BundleMetadata.tsx +136 -0
  133. package/src/components/features/bundles/RolloutCohortsDialog.tsx +201 -0
  134. package/src/components/features/channels/ChannelManagementDialog.tsx +228 -0
  135. package/src/components/features/insights/ActivityChart.tsx +150 -0
  136. package/src/components/features/insights/BundleDistribution.tsx +133 -0
  137. package/src/components/features/insights/BundleSelector.tsx +114 -0
  138. package/src/components/features/insights/InsightsCapabilityContext.tsx +24 -0
  139. package/src/components/features/insights/InsightsControls.tsx +115 -0
  140. package/src/components/features/insights/InsightsErrorAlert.tsx +21 -0
  141. package/src/components/features/insights/InsightsOverview.tsx +208 -0
  142. package/src/components/features/insights/InstallationHistoryCard.tsx +248 -0
  143. package/src/components/features/insights/InstallationMatchesCard.tsx +132 -0
  144. package/src/components/features/insights/InstallationPageHeader.tsx +138 -0
  145. package/src/components/features/insights/InstallationPagination.tsx +48 -0
  146. package/src/components/features/insights/InstallationSearch.tsx +230 -0
  147. package/src/components/features/insights/UpdateOutcomes.tsx +147 -0
  148. package/src/components/features/releases/ReleaseEditorSheet.tsx +879 -0
  149. package/src/components/features/releases/ReleaseStateBadge.tsx +20 -0
  150. package/src/components/features/signing/BundleSigningPage.tsx +139 -0
  151. package/src/components/ui/alert-dialog.tsx +185 -0
  152. package/src/components/ui/alert.tsx +76 -0
  153. package/src/components/ui/badge.tsx +49 -0
  154. package/src/components/ui/button.tsx +58 -0
  155. package/src/components/ui/card.tsx +89 -0
  156. package/src/components/ui/chart.tsx +371 -0
  157. package/src/components/ui/combobox.tsx +303 -0
  158. package/src/components/ui/dialog.tsx +156 -0
  159. package/src/components/ui/dropdown-menu.tsx +270 -0
  160. package/src/components/ui/field.tsx +235 -0
  161. package/src/components/ui/input-group.tsx +147 -0
  162. package/src/components/ui/input.tsx +19 -0
  163. package/src/components/ui/label.tsx +25 -0
  164. package/src/components/ui/progress.tsx +84 -0
  165. package/src/components/ui/select.tsx +205 -0
  166. package/src/components/ui/separator.tsx +23 -0
  167. package/src/components/ui/sheet.tsx +132 -0
  168. package/src/components/ui/sidebar.tsx +779 -0
  169. package/src/components/ui/skeleton.tsx +13 -0
  170. package/src/components/ui/slider.tsx +56 -0
  171. package/src/components/ui/sonner.tsx +45 -0
  172. package/src/components/ui/switch.tsx +25 -0
  173. package/src/components/ui/table.tsx +114 -0
  174. package/src/components/ui/textarea.tsx +18 -0
  175. package/src/components/ui/toggle-group.tsx +89 -0
  176. package/src/components/ui/toggle.tsx +43 -0
  177. package/src/components/ui/tooltip.tsx +66 -0
  178. package/src/hooks/use-mobile.ts +30 -0
  179. package/src/index.ts +47 -0
  180. package/src/lib/api-keys-api.ts +69 -0
  181. package/src/lib/api-keys-rpc.ts +87 -0
  182. package/src/lib/api-rpc.ts +478 -0
  183. package/src/lib/api.ts +444 -0
  184. package/src/lib/auth-rpc.ts +16 -0
  185. package/src/lib/bundle-signing-api.ts +14 -0
  186. package/src/lib/bundle-signing-rpc.ts +16 -0
  187. package/src/lib/constants.ts +1 -0
  188. package/src/lib/git.ts +51 -0
  189. package/src/lib/insights-api.ts +144 -0
  190. package/src/lib/insights-error.ts +19 -0
  191. package/src/lib/insights-input.ts +187 -0
  192. package/src/lib/insights-overview.ts +170 -0
  193. package/src/lib/insights-rpc.ts +218 -0
  194. package/src/lib/server/auth.server.ts +43 -0
  195. package/src/lib/server/bundle-signing.server.ts +23 -0
  196. package/src/lib/server/config.server.ts +109 -0
  197. package/src/lib/server/console-runtime.server.ts +61 -0
  198. package/src/lib/server/deleteBundle.ts +145 -0
  199. package/src/lib/server/downloadBundle.ts +46 -0
  200. package/src/lib/server/getBundleChildren.ts +130 -0
  201. package/src/lib/server/listReleases.ts +61 -0
  202. package/src/lib/server/releaseActivity.ts +27 -0
  203. package/src/lib/server/releaseReachability.ts +95 -0
  204. package/src/lib/server/runtime.server.ts +172 -0
  205. package/src/lib/utils.ts +9 -0
  206. package/src/logo.svg +1 -0
  207. package/src/routeTree.gen.ts +199 -0
  208. package/src/router.tsx +21 -0
  209. package/src/routes/-installations-search.ts +19 -0
  210. package/src/routes/-releases-search.ts +74 -0
  211. package/src/routes/__root.tsx +174 -0
  212. package/src/routes/api/auth/$.ts +19 -0
  213. package/src/routes/api/bundles/$bundleId/download.ts +17 -0
  214. package/src/routes/api-keys.tsx +37 -0
  215. package/src/routes/index.tsx +982 -0
  216. package/src/routes/insights.tsx +139 -0
  217. package/src/routes/installations.tsx +194 -0
  218. package/src/routes/signing.tsx +37 -0
  219. package/src/styles.css +152 -0
  220. package/src/virtual-modules.d.ts +21 -0
  221. package/src/vite-env.d.ts +1 -0
  222. package/src/vite-internal.ts +195 -0
  223. package/src/vite.ts +12 -0
  224. package/.output/public/assets/dist-DJX53faP.js +0 -3
  225. package/.output/public/assets/index-Iylis5kY.js +0 -10
  226. package/.output/public/assets/routes-HT21hs0C.js +0 -54
  227. package/.output/public/assets/styles-C0Re4quB.css +0 -2
  228. package/.output/server/__23tanstack-start-server-fn-resolver-CgwgZ0-i.mjs +0 -66
  229. package/.output/server/_libs/@floating-ui/core+[...].mjs +0 -698
  230. package/.output/server/_libs/@floating-ui/dom+[...].mjs +0 -644
  231. package/.output/server/_libs/@floating-ui/react-dom+[...].mjs +0 -839
  232. package/.output/server/_libs/@radix-ui/react-alert-dialog+[...].mjs +0 -2138
  233. package/.output/server/_libs/@radix-ui/react-popper+[...].mjs +0 -308
  234. package/.output/server/_libs/@radix-ui/react-select+[...].mjs +0 -1038
  235. package/.output/server/_libs/@tanstack/devtools-event-client+[...].mjs +0 -196
  236. package/.output/server/_libs/@tanstack/form-core+[...].mjs +0 -3228
  237. package/.output/server/_libs/@tanstack/react-form+[...].mjs +0 -430
  238. package/.output/server/_libs/@tanstack/react-table+[...].mjs +0 -2382
  239. package/.output/server/_libs/radix-ui__number.mjs +0 -6
  240. package/.output/server/_libs/radix-ui__primitive.mjs +0 -9
  241. package/.output/server/_libs/radix-ui__react-arrow.mjs +0 -23
  242. package/.output/server/_libs/radix-ui__react-collection.mjs +0 -78
  243. package/.output/server/_libs/radix-ui__react-direction.mjs +0 -13
  244. package/.output/server/_libs/radix-ui__react-label.mjs +0 -22
  245. package/.output/server/_libs/radix-ui__react-separator.mjs +0 -30
  246. package/.output/server/_libs/radix-ui__react-slider.mjs +0 -510
  247. package/.output/server/_libs/radix-ui__react-switch.mjs +0 -157
  248. package/.output/server/_libs/radix-ui__react-tooltip.mjs +0 -493
  249. package/.output/server/_ssr/api-rpc-BIbDUKSz.mjs +0 -292
  250. package/.output/server/_ssr/config.server-BSS366KT.mjs +0 -41
  251. package/.output/server/_ssr/deleteBundle-BgbZTtpW.mjs +0 -141
  252. package/.output/server/_ssr/dist-KboydRVk.mjs +0 -120
  253. package/.output/server/_ssr/router-C3hqEy3k.mjs +0 -307
  254. package/.output/server/_ssr/routes-B0lQvvlC.mjs +0 -3048
  255. package/.output/server/_ssr/start-02sLteYG.mjs +0 -4
  256. package/.output/server/_ssr/storageProfile-FVkjPoGb.mjs +0 -9
  257. package/.output/server/_tanstack-start-manifest_v-Bpx52WOU.mjs +0 -20
  258. package/.output/server/node_modules/tslib/modules/index.js +0 -70
  259. package/.output/server/node_modules/tslib/modules/package.json +0 -3
  260. package/.output/server/node_modules/tslib/package.json +0 -47
  261. package/.output/server/node_modules/tslib/tslib.js +0 -484
  262. package/.output/server/package.json +0 -9
@@ -0,0 +1,122 @@
1
+ import { useState } from "react";
2
+
3
+ import { HotUpdaterLogo } from "@/components/HotUpdaterLogo";
4
+ import { Button } from "@/components/ui/button";
5
+
6
+ import type { ConsoleAccess, ConsoleAuthProvider } from "../index";
7
+
8
+ type ConsoleAccessPageProps = {
9
+ readonly access: Exclude<ConsoleAccess, { status: "authorized" }>;
10
+ readonly providers: readonly ConsoleAuthProvider[];
11
+ };
12
+
13
+ const providerLabel: Record<ConsoleAuthProvider, string> = {
14
+ github: "GitHub",
15
+ google: "Google",
16
+ };
17
+
18
+ export function ConsoleAccessPage({
19
+ access,
20
+ providers,
21
+ }: ConsoleAccessPageProps) {
22
+ const [pendingProvider, setPendingProvider] =
23
+ useState<ConsoleAuthProvider | null>(null);
24
+ const [signingOut, setSigningOut] = useState(false);
25
+ const [error, setError] = useState<string | null>(null);
26
+
27
+ const signIn = async (provider: ConsoleAuthProvider) => {
28
+ setPendingProvider(provider);
29
+ setError(null);
30
+ try {
31
+ const response = await fetch("/api/auth/sign-in/social", {
32
+ body: JSON.stringify({ callbackURL: "/", provider }),
33
+ headers: { "content-type": "application/json" },
34
+ method: "POST",
35
+ });
36
+ const result = (await response.json()) as { readonly url?: unknown };
37
+ if (!response.ok || typeof result.url !== "string") {
38
+ throw new Error("The identity provider did not return a sign-in URL.");
39
+ }
40
+ window.location.assign(result.url);
41
+ } catch (cause) {
42
+ setPendingProvider(null);
43
+ setError(cause instanceof Error ? cause.message : "Sign-in failed.");
44
+ }
45
+ };
46
+
47
+ const signOut = async () => {
48
+ setSigningOut(true);
49
+ setError(null);
50
+ try {
51
+ const response = await fetch("/api/auth/sign-out", { method: "POST" });
52
+ if (!response.ok) {
53
+ throw new Error("Sign-out failed.");
54
+ }
55
+ window.location.reload();
56
+ } catch (cause) {
57
+ setSigningOut(false);
58
+ setError(cause instanceof Error ? cause.message : "Sign-out failed.");
59
+ }
60
+ };
61
+
62
+ return (
63
+ <main className="bg-background text-foreground flex min-h-screen items-center justify-center p-6">
64
+ <section className="border-border bg-card w-full max-w-sm rounded-xl border p-6 shadow-sm">
65
+ <div className="mb-6 flex items-center gap-3">
66
+ <HotUpdaterLogo className="h-10 w-7" />
67
+ <div>
68
+ <h1 className="text-lg font-semibold">Hot Updater Console</h1>
69
+ <p className="text-muted-foreground text-sm">
70
+ Sign in to manage OTA releases.
71
+ </p>
72
+ </div>
73
+ </div>
74
+
75
+ {access.status === "forbidden" ? (
76
+ <div className="space-y-3">
77
+ <p className="text-sm font-medium">Access denied</p>
78
+ <p className="text-muted-foreground text-sm">
79
+ {access.principal.email} is not in the console allowlist.
80
+ </p>
81
+ <Button
82
+ className="w-full"
83
+ disabled={signingOut}
84
+ onClick={() => void signOut()}
85
+ size="lg"
86
+ variant="outline"
87
+ >
88
+ {signingOut ? "Signing out…" : "Sign out"}
89
+ </Button>
90
+ </div>
91
+ ) : providers.length === 0 ? (
92
+ <p className="text-muted-foreground text-sm">
93
+ No OAuth provider is configured for this console.
94
+ </p>
95
+ ) : (
96
+ <div className="grid gap-2">
97
+ {providers.map((provider) => (
98
+ <Button
99
+ className="w-full"
100
+ disabled={pendingProvider !== null}
101
+ key={provider}
102
+ onClick={() => void signIn(provider)}
103
+ size="lg"
104
+ variant="outline"
105
+ >
106
+ {pendingProvider === provider
107
+ ? "Redirecting…"
108
+ : `Continue with ${providerLabel[provider]}`}
109
+ </Button>
110
+ ))}
111
+ </div>
112
+ )}
113
+
114
+ {error ? (
115
+ <p className="text-destructive mt-3 text-sm" role="alert">
116
+ {error}
117
+ </p>
118
+ ) : null}
119
+ </section>
120
+ </main>
121
+ );
122
+ }
@@ -0,0 +1,54 @@
1
+ import { Check, Minus, X } from "lucide-react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ interface EnabledStatusIconProps {
6
+ enabled: boolean;
7
+ className?: string;
8
+ falseIcon?: "minus" | "x";
9
+ colorMode?: "semantic" | "inherit";
10
+ }
11
+
12
+ export function EnabledStatusIcon({
13
+ enabled,
14
+ className,
15
+ falseIcon = "x",
16
+ colorMode = "semantic",
17
+ }: EnabledStatusIconProps) {
18
+ const iconColorClassName =
19
+ colorMode === "inherit" ? "text-current" : undefined;
20
+
21
+ if (enabled) {
22
+ return (
23
+ <Check
24
+ className={cn(
25
+ "h-4 w-4",
26
+ iconColorClassName ?? "text-green-600 dark:text-green-400",
27
+ className,
28
+ )}
29
+ />
30
+ );
31
+ }
32
+
33
+ if (falseIcon === "minus") {
34
+ return (
35
+ <Minus
36
+ className={cn(
37
+ "h-4 w-4",
38
+ iconColorClassName ?? "text-muted-foreground",
39
+ className,
40
+ )}
41
+ />
42
+ );
43
+ }
44
+
45
+ return (
46
+ <X
47
+ className={cn(
48
+ "h-4 w-4",
49
+ iconColorClassName ?? "text-red-600 dark:text-red-400",
50
+ className,
51
+ )}
52
+ />
53
+ );
54
+ }
@@ -0,0 +1,85 @@
1
+ import { toast } from "sonner";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
6
+
7
+ interface HashValueDisplayProps {
8
+ value: string;
9
+ maxLength?: number;
10
+ className?: string;
11
+ }
12
+
13
+ export function HashValueDisplay({
14
+ value,
15
+ maxLength = 12,
16
+ className,
17
+ }: HashValueDisplayProps) {
18
+ const isTruncated = value.length > maxLength;
19
+ const truncated = isTruncated ? value.slice(0, maxLength) : value;
20
+
21
+ const copyValue = async () => {
22
+ try {
23
+ await navigator.clipboard.writeText(value);
24
+ toast.success("Copied to clipboard", {
25
+ description: value,
26
+ });
27
+ } catch {
28
+ toast.error("Failed to copy value");
29
+ }
30
+ };
31
+
32
+ const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
33
+ event.preventDefault();
34
+ event.stopPropagation();
35
+ void copyValue();
36
+ };
37
+
38
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {
39
+ if (event.key === "Enter" || event.key === " ") {
40
+ event.preventDefault();
41
+ event.stopPropagation();
42
+ void copyValue();
43
+ }
44
+ };
45
+
46
+ const content = (
47
+ <span
48
+ translate="no"
49
+ className={cn(
50
+ "font-mono text-xs tabular-nums whitespace-nowrap",
51
+ className,
52
+ )}
53
+ >
54
+ {truncated}
55
+ </span>
56
+ );
57
+
58
+ return (
59
+ <Tooltip>
60
+ <TooltipTrigger
61
+ render={
62
+ <button
63
+ type="button"
64
+ translate="no"
65
+ className={cn(
66
+ "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]",
67
+ "hover:bg-muted/70 active:scale-[0.98] active:bg-muted/85 focus-visible:ring-[2px]",
68
+ "touch-manipulation select-none",
69
+ !isTruncated && "cursor-copy",
70
+ )}
71
+ onClick={handleClick}
72
+ onKeyDown={handleKeyDown}
73
+ />
74
+ }
75
+ >
76
+ {content}
77
+ </TooltipTrigger>
78
+ <TooltipContent hidden={!isTruncated}>
79
+ <p translate="no" className="break-all font-mono text-xs tabular-nums">
80
+ {value}
81
+ </p>
82
+ </TooltipContent>
83
+ </Tooltip>
84
+ );
85
+ }
@@ -0,0 +1,30 @@
1
+ export function HotUpdaterLogo({ className }: { className?: string }) {
2
+ return (
3
+ <svg
4
+ aria-label="Hot Updater"
5
+ className={className}
6
+ viewBox="0 0 447 652"
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ >
9
+ <defs>
10
+ <linearGradient
11
+ id="hot-updater-gradient"
12
+ x1="223.54"
13
+ y1="0.36"
14
+ x2="223.54"
15
+ y2="711.23"
16
+ gradientUnits="userSpaceOnUse"
17
+ >
18
+ <stop offset="0" stopColor="#ff7f33" />
19
+ <stop offset="0.4" stopColor="#f9602b" />
20
+ <stop offset="0.76" stopColor="#f54c25" />
21
+ <stop offset="1" stopColor="#f44523" />
22
+ </linearGradient>
23
+ </defs>
24
+ <path
25
+ d="M150.25,8.58c16.28,31.85,42.09,116-68.18,212.22C75.46,226.57,70,233,63.72,239.62c-28.48,30-84.71,114.89-55.82,207,.69,2.18,1.31,4.37,1.91,6.57,5.25,19.33,38.08,121,155.56,157.25a5.14,5.14,0,0,0,6-7.39c-14.72-26.53-46.07-96.89-8.89-162.55,0,0,11.5,44.5,50.5,83.5,6,6,14.72,15.64,19.53,21.23,14.47,16.77,32.24,53.55,1.82,98.18a5.63,5.63,0,0,0,6.85,8.34c75.64-32.1,333.29-164.6,128.68-410.85a7,7,0,0,0-12.46,4.15c-1.77,39.31-14.84,129-96.18,148.06a31.08,31.08,0,0,1-24.3-4.16c-13.34-8.9-25.41-32.06.53-92.3a250.9,250.9,0,0,1,20.12-37.29C282.78,220.56,356.18,79.83,157.33.77A5.64,5.64,0,0,0,150.25,8.58Z"
26
+ fill="url(#hot-updater-gradient)"
27
+ />
28
+ </svg>
29
+ );
30
+ }
@@ -0,0 +1,33 @@
1
+ import { Link } from "@tanstack/react-router";
2
+
3
+ import { buttonVariants } from "@/components/ui/button";
4
+
5
+ const homeSearch = {
6
+ channel: undefined,
7
+ platform: undefined,
8
+ targetAppVersion: undefined,
9
+ page: undefined,
10
+ after: undefined,
11
+ before: undefined,
12
+ bundleId: undefined,
13
+ expandedBundleId: undefined,
14
+ } as const;
15
+
16
+ export function NotFoundPage() {
17
+ return (
18
+ <div className="flex min-h-svh flex-1 items-center justify-center px-6 py-10">
19
+ <section className="flex max-w-md flex-col items-center text-center">
20
+ <h1 className="text-2xl font-semibold tracking-tight text-foreground">
21
+ Page not found
22
+ </h1>
23
+ <Link
24
+ className={buttonVariants({ className: "mt-6", size: "lg" })}
25
+ to="/"
26
+ search={homeSearch}
27
+ >
28
+ Go to home
29
+ </Link>
30
+ </section>
31
+ </div>
32
+ );
33
+ }
@@ -0,0 +1,39 @@
1
+ interface PlatformIconProps {
2
+ platform: "ios" | "android";
3
+ className?: string;
4
+ }
5
+
6
+ function AppleIcon({ className }: { className?: string }) {
7
+ return (
8
+ <svg
9
+ aria-hidden="true"
10
+ viewBox="0 0 24 24"
11
+ xmlns="http://www.w3.org/2000/svg"
12
+ className={className}
13
+ fill="currentColor"
14
+ >
15
+ <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" />
16
+ </svg>
17
+ );
18
+ }
19
+
20
+ function AndroidIcon({ className }: { className?: string }) {
21
+ return (
22
+ <svg
23
+ aria-hidden="true"
24
+ viewBox="0 0 24 24"
25
+ xmlns="http://www.w3.org/2000/svg"
26
+ className={className}
27
+ fill="currentColor"
28
+ >
29
+ <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" />
30
+ </svg>
31
+ );
32
+ }
33
+
34
+ export function PlatformIcon({ platform, className }: PlatformIconProps) {
35
+ if (platform === "ios") {
36
+ return <AppleIcon className={className} />;
37
+ }
38
+ return <AndroidIcon className={className} />;
39
+ }
@@ -0,0 +1,28 @@
1
+ import { AlertTriangle } from "lucide-react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ import { Badge } from "./ui/badge";
6
+
7
+ interface RolloutPercentageBadgeProps {
8
+ percentage: number;
9
+ className?: string;
10
+ }
11
+
12
+ export function RolloutPercentageBadge({
13
+ percentage,
14
+ className,
15
+ }: RolloutPercentageBadgeProps) {
16
+ const isPartialRollout = percentage < 100;
17
+ const formattedPercentage = percentage.toFixed(1);
18
+
19
+ return (
20
+ <Badge
21
+ className={cn("gap-1", className)}
22
+ variant={isPartialRollout ? "secondary" : "default"}
23
+ >
24
+ {isPartialRollout ? <AlertTriangle className="h-3 w-3" /> : null}
25
+ {formattedPercentage}%
26
+ </Badge>
27
+ );
28
+ }
@@ -0,0 +1,116 @@
1
+ import { createContext, useContext, useEffect, useState } from "react";
2
+
3
+ type Theme = "dark" | "light" | "system";
4
+
5
+ interface ThemeContextType {
6
+ theme: Theme;
7
+ setTheme: (theme: Theme) => void;
8
+ }
9
+
10
+ const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
11
+ const LIGHT_THEME_COLOR = "#fbfbfa";
12
+ const DARK_THEME_COLOR = "#1f1d1c";
13
+ const REFRESH_THEME_CHROME_EVENT = "hot-updater:refresh-theme-chrome";
14
+
15
+ export function ThemeProvider({
16
+ children,
17
+ defaultTheme = "dark",
18
+ storageKey = "hot-updater-theme",
19
+ }: {
20
+ children: React.ReactNode;
21
+ defaultTheme?: Theme;
22
+ storageKey?: string;
23
+ }) {
24
+ const [theme, setTheme] = useState<Theme>(defaultTheme);
25
+
26
+ useEffect(() => {
27
+ const stored = localStorage.getItem(storageKey) as Theme | null;
28
+ if (stored) {
29
+ setTheme(stored);
30
+ }
31
+ }, [storageKey]);
32
+
33
+ useEffect(() => {
34
+ const root = window.document.documentElement;
35
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
36
+ const themeColorMeta = window.document.querySelector(
37
+ 'meta[name="theme-color"]',
38
+ );
39
+ const colorSchemeMeta = window.document.querySelector(
40
+ 'meta[name="color-scheme"]',
41
+ );
42
+
43
+ const applyResolvedTheme = (resolvedTheme: "dark" | "light") => {
44
+ root.classList.remove("light", "dark");
45
+ root.classList.add(resolvedTheme);
46
+ root.style.colorScheme = resolvedTheme;
47
+
48
+ if (themeColorMeta) {
49
+ themeColorMeta.setAttribute(
50
+ "content",
51
+ resolvedTheme === "dark" ? DARK_THEME_COLOR : LIGHT_THEME_COLOR,
52
+ );
53
+ }
54
+
55
+ if (colorSchemeMeta) {
56
+ colorSchemeMeta.setAttribute(
57
+ "content",
58
+ resolvedTheme === "dark" ? "dark light" : "light dark",
59
+ );
60
+ }
61
+ };
62
+
63
+ const syncTheme = () => {
64
+ const resolvedTheme =
65
+ theme === "system" ? (mediaQuery.matches ? "dark" : "light") : theme;
66
+
67
+ applyResolvedTheme(resolvedTheme);
68
+ };
69
+
70
+ syncTheme();
71
+
72
+ const refreshThemeChrome = () => {
73
+ window.requestAnimationFrame(syncTheme);
74
+ };
75
+
76
+ window.addEventListener(REFRESH_THEME_CHROME_EVENT, refreshThemeChrome);
77
+ window.addEventListener("pageshow", refreshThemeChrome);
78
+ document.addEventListener("visibilitychange", refreshThemeChrome);
79
+
80
+ if (theme === "system") {
81
+ mediaQuery.addEventListener("change", syncTheme);
82
+ }
83
+
84
+ return () => {
85
+ if (theme === "system") {
86
+ mediaQuery.removeEventListener("change", syncTheme);
87
+ }
88
+ window.removeEventListener(
89
+ REFRESH_THEME_CHROME_EVENT,
90
+ refreshThemeChrome,
91
+ );
92
+ window.removeEventListener("pageshow", refreshThemeChrome);
93
+ document.removeEventListener("visibilitychange", refreshThemeChrome);
94
+ };
95
+ }, [theme]);
96
+
97
+ const value = {
98
+ theme,
99
+ setTheme: (newTheme: Theme) => {
100
+ localStorage.setItem(storageKey, newTheme);
101
+ setTheme(newTheme);
102
+ },
103
+ };
104
+
105
+ return (
106
+ <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
107
+ );
108
+ }
109
+
110
+ export function useTheme() {
111
+ const context = useContext(ThemeContext);
112
+ if (context === undefined) {
113
+ throw new Error("useTheme must be used within a ThemeProvider");
114
+ }
115
+ return context;
116
+ }
@@ -0,0 +1,41 @@
1
+ import { extractTimestampFromUUIDv7 } from "@hot-updater/plugin-core";
2
+ import dayjs from "dayjs";
3
+ import relativeTime from "dayjs/plugin/relativeTime";
4
+
5
+ dayjs.extend(relativeTime);
6
+
7
+ interface TimestampDisplayProps {
8
+ uuid: string;
9
+ format?: "relative" | "absolute" | "both";
10
+ }
11
+
12
+ export function TimestampDisplay({
13
+ uuid,
14
+ format = "relative",
15
+ }: TimestampDisplayProps) {
16
+ const timestamp = extractTimestampFromUUIDv7(uuid);
17
+ const date = dayjs(timestamp);
18
+
19
+ if (format === "relative") {
20
+ return (
21
+ <span className="text-sm text-muted-foreground">{date.fromNow()}</span>
22
+ );
23
+ }
24
+
25
+ if (format === "absolute") {
26
+ return (
27
+ <span className="text-sm text-muted-foreground">
28
+ {date.format("YYYY-MM-DD HH:mm:ss")}
29
+ </span>
30
+ );
31
+ }
32
+
33
+ return (
34
+ <div className="flex flex-col">
35
+ <span className="text-sm">{date.fromNow()}</span>
36
+ <span className="text-xs text-muted-foreground">
37
+ {date.format("YYYY-MM-DD HH:mm:ss")}
38
+ </span>
39
+ </div>
40
+ );
41
+ }