@hot-updater/console 0.28.0 → 0.29.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.
- package/.output/nitro.json +17 -0
- package/.output/public/apple-touch-icon.png +0 -0
- package/.output/public/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/.output/public/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/.output/public/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/.output/public/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/.output/public/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/.output/public/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/.output/public/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/.output/public/assets/main-Dlx8-qN-.js +61 -0
- package/.output/public/assets/routes-DB0tWmiJ.js +10 -0
- package/.output/public/assets/styles-Bfxg4M1x.css +2 -0
- package/.output/public/favicon-16x16.png +0 -0
- package/.output/public/favicon-32x32.png +0 -0
- package/.output/public/favicon.ico +0 -0
- package/.output/public/logo.svg +1 -0
- package/.output/public/manifest.json +30 -0
- package/.output/public/robots.txt +3 -0
- package/.output/server/_chunks/ssr-renderer.mjs +15 -0
- package/.output/server/_libs/@floating-ui/core+[...].mjs +698 -0
- package/.output/server/_libs/@floating-ui/dom+[...].mjs +644 -0
- package/.output/server/_libs/@floating-ui/react-dom+[...].mjs +839 -0
- package/.output/server/_libs/@radix-ui/react-alert-dialog+[...].mjs +2093 -0
- package/.output/server/_libs/@radix-ui/react-popper+[...].mjs +287 -0
- package/.output/server/_libs/@radix-ui/react-select+[...].mjs +1003 -0
- package/.output/server/_libs/@tanstack/devtools-event-client+[...].mjs +196 -0
- package/.output/server/_libs/@tanstack/form-core+[...].mjs +2396 -0
- package/.output/server/_libs/@tanstack/react-form+[...].mjs +298 -0
- package/.output/server/_libs/@tanstack/react-router+[...].mjs +13068 -0
- package/.output/server/_libs/@tanstack/react-table+[...].mjs +2372 -0
- package/.output/server/_libs/chownr.mjs +60 -0
- package/.output/server/_libs/class-variance-authority+clsx.mjs +69 -0
- package/.output/server/_libs/core-util-is.mjs +67 -0
- package/.output/server/_libs/dayjs.mjs +408 -0
- package/.output/server/_libs/h3+rou3+srvx.mjs +1158 -0
- package/.output/server/_libs/hookable.mjs +41 -0
- package/.output/server/_libs/immediate.mjs +57 -0
- package/.output/server/_libs/inherits.mjs +39 -0
- package/.output/server/_libs/isaacs__fs-minipass+minipass.mjs +1120 -0
- package/.output/server/_libs/isarray.mjs +10 -0
- package/.output/server/_libs/jszip+[...].mjs +8311 -0
- package/.output/server/_libs/lucide-react.mjs +371 -0
- package/.output/server/_libs/minizlib.mjs +345 -0
- package/.output/server/_libs/next-themes.mjs +49 -0
- package/.output/server/_libs/radix-ui__number.mjs +6 -0
- package/.output/server/_libs/radix-ui__primitive.mjs +9 -0
- package/.output/server/_libs/radix-ui__react-arrow.mjs +23 -0
- package/.output/server/_libs/radix-ui__react-collection.mjs +78 -0
- package/.output/server/_libs/radix-ui__react-direction.mjs +11 -0
- package/.output/server/_libs/radix-ui__react-label.mjs +22 -0
- package/.output/server/_libs/radix-ui__react-separator.mjs +31 -0
- package/.output/server/_libs/radix-ui__react-slider.mjs +451 -0
- package/.output/server/_libs/radix-ui__react-switch.mjs +118 -0
- package/.output/server/_libs/radix-ui__react-tooltip.mjs +491 -0
- package/.output/server/_libs/semver.mjs +1339 -0
- package/.output/server/_libs/sonner.mjs +908 -0
- package/.output/server/_libs/tailwind-merge.mjs +1962 -0
- package/.output/server/_libs/tanstack__history.mjs +322 -0
- package/.output/server/_libs/tanstack__query-core.mjs +2073 -0
- package/.output/server/_libs/tanstack__react-query.mjs +146 -0
- package/.output/server/_libs/tanstack__router-core.mjs +6 -0
- package/.output/server/_libs/tar.mjs +1996 -0
- package/.output/server/_libs/ufo.mjs +64 -0
- package/.output/server/_runtime.mjs +26 -0
- package/.output/server/_ssr/api-rpc-D3ZehMIN.mjs +217 -0
- package/.output/server/_ssr/config.server-JUYQ7UbI.mjs +26 -0
- package/.output/server/_ssr/deleteBundle-DWUxu9-K.mjs +22 -0
- package/.output/server/_ssr/extract-timestamp-from-uuidv7-B90UBADU.mjs +24 -0
- package/.output/server/_ssr/promoteBundle-DtMHuubR.mjs +1571 -0
- package/.output/server/_ssr/router-pgc7NX76.mjs +250 -0
- package/.output/server/_ssr/routes-PqTTQSoI.mjs +1833 -0
- package/.output/server/_ssr/sidebar-DXng0IOP.mjs +439 -0
- package/.output/server/_ssr/ssr.mjs +5050 -0
- package/.output/server/_ssr/start-DQK0r85G.mjs +4 -0
- package/.output/server/_tanstack-start-manifest_v-DTbQVOpU.mjs +17 -0
- package/.output/server/index.mjs +417 -0
- package/.output/server/node_modules/tslib/modules/index.js +70 -0
- package/.output/server/node_modules/tslib/modules/package.json +3 -0
- package/.output/server/node_modules/tslib/package.json +47 -0
- package/.output/server/node_modules/tslib/tslib.js +484 -0
- package/.output/server/package.json +9 -0
- package/README.md +191 -2
- package/package.json +77 -50
- package/dist/.gitkeep +0 -0
- package/dist/assets/favicon-BkwcEHsj.ico +0 -0
- package/dist/assets/index-DUlKsori.css +0 -1
- package/dist/assets/index-ijmIcyn1.js +0 -27
- package/dist/assets/logo-BYNFyja1.png +0 -0
- package/dist/index.cjs +0 -2129
- package/dist/index.d.cts +0 -218
- package/dist/index.d.ts +0 -218
- package/dist/index.html +0 -14
- package/dist/index.js +0 -2125
|
@@ -0,0 +1,1833 @@
|
|
|
1
|
+
import { r as __toESM } from "../_runtime.mjs";
|
|
2
|
+
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";
|
|
3
|
+
import { d as useNavigate, f as useSearch } from "../_libs/@tanstack/react-router+[...].mjs";
|
|
4
|
+
import { u as require_react } from "../_libs/@floating-ui/react-dom+[...].mjs";
|
|
5
|
+
import { n as useStore, t as useForm } from "../_libs/@tanstack/react-form+[...].mjs";
|
|
6
|
+
import { n as createServerFn, r as TSS_SERVER_FUNCTION, t as getServerFnById } from "./ssr.mjs";
|
|
7
|
+
import { n as extractTimestampFromUUIDv7, t as createUUIDv7 } from "./extract-timestamp-from-uuidv7-B90UBADU.mjs";
|
|
8
|
+
import { S as Check, b as ChevronLeft, d as List, g as Download, h as ExternalLink, i as Plus, l as Minus, m as FingerprintPattern, n as TriangleAlert, o as Package, p as Funnel, t as X, v as ChevronUp, x as ChevronDown, y as ChevronRight } from "../_libs/lucide-react.mjs";
|
|
9
|
+
import { t as cva } from "../_libs/class-variance-authority+clsx.mjs";
|
|
10
|
+
import { a as ItemText, c as ScrollDownButton, d as Value, f as Viewport, i as ItemIndicator, l as ScrollUpButton, n as Icon, o as Portal$1, r as Item, s as Root2$1, t as Content2$1, u as Trigger$1 } from "../_libs/@radix-ui/react-select+[...].mjs";
|
|
11
|
+
import { t as Root$1 } from "../_libs/radix-ui__react-label.mjs";
|
|
12
|
+
import { i as Track, n as Root$2, r as Thumb, t as Range } from "../_libs/radix-ui__react-slider.mjs";
|
|
13
|
+
import { n as Thumb$1, t as Root$3 } from "../_libs/radix-ui__react-switch.mjs";
|
|
14
|
+
import { C as Tooltip$1, D as cn, E as TooltipTrigger, S as Skeleton, T as TooltipProvider, a as SheetContent, c as SheetTitle, i as Sheet, n as Input, o as SheetDescription, r as Separator$1, s as SheetHeader, t as Button, w as TooltipContent, x as SidebarTrigger } from "./sidebar-DXng0IOP.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 require_semver } from "../_libs/semver.mjs";
|
|
18
|
+
import { i as getCoreRowModel, n as useReactTable, r as createColumnHelper, t as flexRender } from "../_libs/@tanstack/react-table+[...].mjs";
|
|
19
|
+
import { n as require_dayjs_min, t as require_relativeTime } from "../_libs/dayjs.mjs";
|
|
20
|
+
//#region node_modules/.nitro/vite/services/ssr/assets/routes-PqTTQSoI.js
|
|
21
|
+
var import_jsx_runtime = require_jsx_runtime();
|
|
22
|
+
var import_react = /* @__PURE__ */ __toESM(require_react());
|
|
23
|
+
var import_semver = /* @__PURE__ */ __toESM(require_semver());
|
|
24
|
+
var import_dayjs_min = /* @__PURE__ */ __toESM(require_dayjs_min());
|
|
25
|
+
var import_relativeTime = /* @__PURE__ */ __toESM(require_relativeTime());
|
|
26
|
+
function AppleIcon({ className }) {
|
|
27
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
|
|
28
|
+
role: "img",
|
|
29
|
+
"aria-label": "iOS",
|
|
30
|
+
viewBox: "0 0 24 24",
|
|
31
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
32
|
+
className,
|
|
33
|
+
fill: "currentColor",
|
|
34
|
+
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" })
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function AndroidIcon({ className }) {
|
|
38
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", {
|
|
39
|
+
role: "img",
|
|
40
|
+
"aria-label": "Android",
|
|
41
|
+
viewBox: "0 0 24 24",
|
|
42
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
43
|
+
className,
|
|
44
|
+
fill: "currentColor",
|
|
45
|
+
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" })
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function PlatformIcon({ platform, className }) {
|
|
49
|
+
if (platform === "ios") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AppleIcon, { className });
|
|
50
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AndroidIcon, { className });
|
|
51
|
+
}
|
|
52
|
+
function BundleBasicInfo({ bundle }) {
|
|
53
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
54
|
+
className: "flex flex-col gap-3 text-sm mt-1",
|
|
55
|
+
children: [
|
|
56
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
57
|
+
className: "flex items-center gap-2",
|
|
58
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlatformIcon, {
|
|
59
|
+
platform: bundle.platform,
|
|
60
|
+
className: "h-4 w-4"
|
|
61
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
62
|
+
className: "font-medium",
|
|
63
|
+
children: bundle.platform === "ios" ? "iOS" : "Android"
|
|
64
|
+
})]
|
|
65
|
+
}),
|
|
66
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
67
|
+
className: "flex items-center gap-2",
|
|
68
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
69
|
+
className: "font-medium text-muted-foreground",
|
|
70
|
+
children: "Bundle ID"
|
|
71
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
72
|
+
className: "text-xs text-foreground",
|
|
73
|
+
children: bundle.id
|
|
74
|
+
})]
|
|
75
|
+
}),
|
|
76
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
77
|
+
className: "flex items-center gap-2",
|
|
78
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
79
|
+
className: "font-medium text-muted-foreground",
|
|
80
|
+
children: "Channel"
|
|
81
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
82
|
+
className: "text-xs text-foreground",
|
|
83
|
+
children: bundle.channel
|
|
84
|
+
})]
|
|
85
|
+
})
|
|
86
|
+
]
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
var NUMERIC_COHORT_SIZE = 1e3;
|
|
90
|
+
var DEFAULT_ROLLOUT_COHORT_COUNT = NUMERIC_COHORT_SIZE;
|
|
91
|
+
var INVALID_COHORT_ERROR_MESSAGE = `Invalid cohort. Use 1-1000 or a lowercase slug without spaces, up to 64 characters.`;
|
|
92
|
+
var CUSTOM_COHORT_PATTERN = /^[a-z0-9-]+$/;
|
|
93
|
+
function parseNumericCohortValue(cohort) {
|
|
94
|
+
if (!/^\d+$/.test(cohort)) return null;
|
|
95
|
+
const parsed = Number.parseInt(cohort, 10);
|
|
96
|
+
if (Number.isNaN(parsed) || parsed < 1 || parsed > 1e3) return null;
|
|
97
|
+
return parsed;
|
|
98
|
+
}
|
|
99
|
+
function positiveMod(value, modulus) {
|
|
100
|
+
return (value % modulus + modulus) % modulus;
|
|
101
|
+
}
|
|
102
|
+
function hashString(value) {
|
|
103
|
+
let hash = 0;
|
|
104
|
+
for (let i = 0; i < value.length; i++) {
|
|
105
|
+
const char = value.charCodeAt(i);
|
|
106
|
+
hash = (hash << 5) - hash + char;
|
|
107
|
+
hash |= 0;
|
|
108
|
+
}
|
|
109
|
+
return hash;
|
|
110
|
+
}
|
|
111
|
+
function gcd(a, b) {
|
|
112
|
+
let x = Math.abs(a);
|
|
113
|
+
let y = Math.abs(b);
|
|
114
|
+
while (y !== 0) {
|
|
115
|
+
const next = x % y;
|
|
116
|
+
x = y;
|
|
117
|
+
y = next;
|
|
118
|
+
}
|
|
119
|
+
return x;
|
|
120
|
+
}
|
|
121
|
+
function modularInverse(value, modulus) {
|
|
122
|
+
let t = 0;
|
|
123
|
+
let newT = 1;
|
|
124
|
+
let r = modulus;
|
|
125
|
+
let newR = positiveMod(value, modulus);
|
|
126
|
+
while (newR !== 0) {
|
|
127
|
+
const quotient = Math.floor(r / newR);
|
|
128
|
+
[t, newT] = [newT, t - quotient * newT];
|
|
129
|
+
[r, newR] = [newR, r - quotient * newR];
|
|
130
|
+
}
|
|
131
|
+
if (r > 1) throw new Error(`No modular inverse for ${value} mod ${modulus}`);
|
|
132
|
+
return positiveMod(t, modulus);
|
|
133
|
+
}
|
|
134
|
+
function getRolloutShuffleParameters(bundleId) {
|
|
135
|
+
let multiplier = positiveMod(hashString(`${bundleId}:multiplier`), 997);
|
|
136
|
+
if (multiplier === 0) multiplier = 1;
|
|
137
|
+
while (gcd(multiplier, NUMERIC_COHORT_SIZE) !== 1) {
|
|
138
|
+
multiplier = positiveMod(multiplier + 1, NUMERIC_COHORT_SIZE);
|
|
139
|
+
if (multiplier === 0) multiplier = 1;
|
|
140
|
+
}
|
|
141
|
+
const offset = positiveMod(hashString(`${bundleId}:offset`), NUMERIC_COHORT_SIZE);
|
|
142
|
+
return {
|
|
143
|
+
multiplier,
|
|
144
|
+
offset,
|
|
145
|
+
inverseMultiplier: modularInverse(multiplier, NUMERIC_COHORT_SIZE)
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function normalizeRolloutCohortCount(rolloutCohortCount) {
|
|
149
|
+
if (rolloutCohortCount === null || rolloutCohortCount === void 0) return DEFAULT_ROLLOUT_COHORT_COUNT;
|
|
150
|
+
if (rolloutCohortCount <= 0) return 0;
|
|
151
|
+
if (rolloutCohortCount >= 1e3) return NUMERIC_COHORT_SIZE;
|
|
152
|
+
return Math.floor(rolloutCohortCount);
|
|
153
|
+
}
|
|
154
|
+
function normalizeCohortValue(cohort) {
|
|
155
|
+
const normalized = cohort.trim().toLowerCase();
|
|
156
|
+
const numericCohort = parseNumericCohortValue(normalized);
|
|
157
|
+
if (numericCohort !== null) return String(numericCohort);
|
|
158
|
+
return normalized;
|
|
159
|
+
}
|
|
160
|
+
function getNumericCohortValue(cohort) {
|
|
161
|
+
return parseNumericCohortValue(normalizeCohortValue(cohort));
|
|
162
|
+
}
|
|
163
|
+
function isNumericCohort(cohort) {
|
|
164
|
+
return getNumericCohortValue(cohort) !== null;
|
|
165
|
+
}
|
|
166
|
+
function isCustomCohort(cohort) {
|
|
167
|
+
const normalized = normalizeCohortValue(cohort);
|
|
168
|
+
return normalized.length > 0 && normalized.length <= 64 && !/^\d+$/.test(normalized) && CUSTOM_COHORT_PATTERN.test(normalized);
|
|
169
|
+
}
|
|
170
|
+
function isValidCohort(cohort) {
|
|
171
|
+
const normalized = normalizeCohortValue(cohort);
|
|
172
|
+
return isNumericCohort(normalized) || isCustomCohort(normalized);
|
|
173
|
+
}
|
|
174
|
+
function getNumericCohortRolloutPosition(bundleId, cohortValue) {
|
|
175
|
+
if (cohortValue < 1 || cohortValue > 1e3) throw new Error(`Invalid numeric cohort: ${cohortValue}`);
|
|
176
|
+
const { offset, inverseMultiplier } = getRolloutShuffleParameters(bundleId);
|
|
177
|
+
return positiveMod(inverseMultiplier * (cohortValue - 1 - offset), NUMERIC_COHORT_SIZE);
|
|
178
|
+
}
|
|
179
|
+
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", {
|
|
180
|
+
variants: { variant: {
|
|
181
|
+
default: "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
|
|
182
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
183
|
+
destructive: "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
|
|
184
|
+
outline: "text-foreground"
|
|
185
|
+
} },
|
|
186
|
+
defaultVariants: { variant: "default" }
|
|
187
|
+
});
|
|
188
|
+
function Badge({ className, variant = "default", asChild = false, ...props }) {
|
|
189
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(asChild ? Slot : "span", {
|
|
190
|
+
"data-slot": "badge",
|
|
191
|
+
"data-variant": variant,
|
|
192
|
+
className: cn(badgeVariants({ variant }), className),
|
|
193
|
+
...props
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
function Label$1({ className, ...props }) {
|
|
197
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Root$1, {
|
|
198
|
+
"data-slot": "label",
|
|
199
|
+
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),
|
|
200
|
+
...props
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
function Slider$1({ className, defaultValue, value, min = 0, max = 100, ...props }) {
|
|
204
|
+
const _values = import_react.useMemo(() => Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [min, max], [
|
|
205
|
+
value,
|
|
206
|
+
defaultValue,
|
|
207
|
+
min,
|
|
208
|
+
max
|
|
209
|
+
]);
|
|
210
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Root$2, {
|
|
211
|
+
"data-slot": "slider",
|
|
212
|
+
defaultValue,
|
|
213
|
+
value,
|
|
214
|
+
min,
|
|
215
|
+
max,
|
|
216
|
+
className: cn("relative flex w-full touch-none select-none items-center", className),
|
|
217
|
+
...props,
|
|
218
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Track, {
|
|
219
|
+
"data-slot": "slider-track",
|
|
220
|
+
className: "relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20",
|
|
221
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Range, {
|
|
222
|
+
"data-slot": "slider-range",
|
|
223
|
+
className: "absolute h-full bg-primary"
|
|
224
|
+
})
|
|
225
|
+
}), Array.from({ length: _values.length }, (_, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Thumb, {
|
|
226
|
+
"data-slot": "slider-thumb",
|
|
227
|
+
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"
|
|
228
|
+
}, index))]
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
function Switch$1({ className, ...props }) {
|
|
232
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Root$3, {
|
|
233
|
+
"data-slot": "switch",
|
|
234
|
+
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),
|
|
235
|
+
...props,
|
|
236
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Thumb$1, {
|
|
237
|
+
"data-slot": "switch-thumb",
|
|
238
|
+
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"
|
|
239
|
+
})
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function Textarea({ className, ...props }) {
|
|
243
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", {
|
|
244
|
+
"data-slot": "textarea",
|
|
245
|
+
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),
|
|
246
|
+
...props
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
var createSsrRpc = (functionId, importer) => {
|
|
250
|
+
const url = "/_serverFn/" + functionId;
|
|
251
|
+
const serverFnMeta = { id: functionId };
|
|
252
|
+
const fn = async (...args) => {
|
|
253
|
+
return (importer ? await importer() : await getServerFnById(functionId))(...args);
|
|
254
|
+
};
|
|
255
|
+
return Object.assign(fn, {
|
|
256
|
+
url,
|
|
257
|
+
serverFnMeta,
|
|
258
|
+
[TSS_SERVER_FUNCTION]: true
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
var getConfig = createServerFn().handler(createSsrRpc("51a34c05479a893c7d320bd4cd1604427289d667698c48b9f30a01aabf8a5e68"));
|
|
262
|
+
var getChannels = createServerFn().handler(createSsrRpc("79ada05964de8f2123bfcd62b10097d59bf8fd689ef5c7161031e5871d8396c5"));
|
|
263
|
+
createServerFn().handler(createSsrRpc("f8bdca3d0579adb812d6404f55d9261a89cdb025e920b1fcad82883646a5fe9e"));
|
|
264
|
+
var getBundles = createServerFn({ method: "GET" }).inputValidator((input) => input).handler(createSsrRpc("00ccacb4a0212c83ec29f4d11719046ad91ea8291cfc557d514dbf00d3bd7f5f"));
|
|
265
|
+
var getBundle = createServerFn({ method: "GET" }).inputValidator((input) => input).handler(createSsrRpc("1bd85c2a50e24785cb6abb023a247a9f048f37ddfa85cbb1c57e579563bad013"));
|
|
266
|
+
var getBundleDownloadUrl = createServerFn({ method: "GET" }).inputValidator((input) => input).handler(createSsrRpc("e000081a14772a0496dfdf615232fbbb1a23b89191d0e32f89a05dd67a1916f7"));
|
|
267
|
+
var updateBundle = createServerFn({ method: "POST" }).inputValidator((input) => input).handler(createSsrRpc("67f90ce4cd10fd0226cd9d77cdcd8d0f25a59a6ac406360b655e44296bc4208b"));
|
|
268
|
+
var promoteBundle = createServerFn({ method: "POST" }).inputValidator((input) => input).handler(createSsrRpc("c4ef3bbb77ea8a4410623fd18a4741e2ff0668b1a97510bcdd032a8428eaa5da"));
|
|
269
|
+
createServerFn({ method: "POST" }).inputValidator((input) => input).handler(createSsrRpc("16cc7c2f080ea5b73e0c6bba815b110dbd7727796036d248ba0d8ae819ddef08"));
|
|
270
|
+
var deleteBundle = createServerFn({ method: "POST" }).inputValidator((input) => input).handler(createSsrRpc("3a27ff5679228b86a346b3fe42142e7a5bf0264f73173c0dbe5f07fb37c43cf7"));
|
|
271
|
+
var bundleListQueryKey = ["bundles"];
|
|
272
|
+
var queryKeys = {
|
|
273
|
+
config: ["config"],
|
|
274
|
+
channels: ["channels"],
|
|
275
|
+
configLoaded: ["config-loaded"],
|
|
276
|
+
bundles: {
|
|
277
|
+
all: bundleListQueryKey,
|
|
278
|
+
list: (filters) => [...bundleListQueryKey, filters ?? {}]
|
|
279
|
+
},
|
|
280
|
+
bundle: (bundleId) => ["bundle", bundleId]
|
|
281
|
+
};
|
|
282
|
+
function replaceBundleInQueryData(data, updatedBundle) {
|
|
283
|
+
if (!data) return data;
|
|
284
|
+
return {
|
|
285
|
+
...data,
|
|
286
|
+
data: data.data.map((bundle) => bundle.id === updatedBundle.id ? updatedBundle : bundle)
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function useConfigQuery() {
|
|
290
|
+
return useQuery({
|
|
291
|
+
queryKey: queryKeys.config,
|
|
292
|
+
queryFn: () => getConfig(),
|
|
293
|
+
staleTime: Infinity
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
function useChannelsQuery() {
|
|
297
|
+
return useQuery({
|
|
298
|
+
queryKey: queryKeys.channels,
|
|
299
|
+
queryFn: () => getChannels(),
|
|
300
|
+
staleTime: Infinity
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
function useBundlesQuery(filters) {
|
|
304
|
+
return useQuery({
|
|
305
|
+
queryKey: queryKeys.bundles.list(filters),
|
|
306
|
+
queryFn: () => getBundles({ data: filters }),
|
|
307
|
+
staleTime: Infinity,
|
|
308
|
+
placeholderData: (previousData) => previousData
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
function useBundleQuery(bundleId) {
|
|
312
|
+
return useQuery({
|
|
313
|
+
queryKey: queryKeys.bundle(bundleId),
|
|
314
|
+
queryFn: () => getBundle({ data: { bundleId } }),
|
|
315
|
+
staleTime: Infinity,
|
|
316
|
+
enabled: !!bundleId
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
function useBundleDownloadUrlMutation() {
|
|
320
|
+
return useMutation({ mutationFn: (params) => getBundleDownloadUrl({ data: params }) });
|
|
321
|
+
}
|
|
322
|
+
function useUpdateBundleMutation() {
|
|
323
|
+
const queryClient = useQueryClient();
|
|
324
|
+
return useMutation({
|
|
325
|
+
mutationFn: (params) => updateBundle({ data: params }),
|
|
326
|
+
onSuccess: async ({ bundle: updatedBundle }, vars) => {
|
|
327
|
+
queryClient.setQueryData(queryKeys.bundle(vars.bundleId), updatedBundle);
|
|
328
|
+
queryClient.setQueriesData({ queryKey: queryKeys.bundles.all }, (data) => replaceBundleInQueryData(data, updatedBundle));
|
|
329
|
+
await Promise.all([
|
|
330
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.bundles.all }),
|
|
331
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.bundle(vars.bundleId) }),
|
|
332
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.channels })
|
|
333
|
+
]);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
function usePromoteBundleMutation() {
|
|
338
|
+
const queryClient = useQueryClient();
|
|
339
|
+
return useMutation({
|
|
340
|
+
mutationFn: (params) => promoteBundle({ data: params }),
|
|
341
|
+
onSuccess: async ({ bundle }) => {
|
|
342
|
+
queryClient.setQueryData(queryKeys.bundle(bundle.id), bundle);
|
|
343
|
+
await Promise.all([
|
|
344
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.bundles.all }),
|
|
345
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.channels }),
|
|
346
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.bundle(bundle.id) })
|
|
347
|
+
]);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
function useDeleteBundleMutation() {
|
|
352
|
+
const queryClient = useQueryClient();
|
|
353
|
+
return useMutation({
|
|
354
|
+
mutationFn: (params) => deleteBundle({ data: params }),
|
|
355
|
+
onSuccess: async (_, vars) => {
|
|
356
|
+
queryClient.removeQueries({ queryKey: queryKeys.bundle(vars.bundleId) });
|
|
357
|
+
await Promise.all([queryClient.invalidateQueries({ queryKey: queryKeys.bundles.all }), queryClient.invalidateQueries({ queryKey: queryKeys.channels })]);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
function AlertDialog$1({ ...props }) {
|
|
362
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Root2, {
|
|
363
|
+
"data-slot": "alert-dialog",
|
|
364
|
+
...props
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
function AlertDialogPortal({ ...props }) {
|
|
368
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Portal2, {
|
|
369
|
+
"data-slot": "alert-dialog-portal",
|
|
370
|
+
...props
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
function AlertDialogOverlay({ className, ...props }) {
|
|
374
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Overlay2, {
|
|
375
|
+
"data-slot": "alert-dialog-overlay",
|
|
376
|
+
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),
|
|
377
|
+
...props
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
function AlertDialogContent({ className, size = "default", ...props }) {
|
|
381
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogPortal, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogOverlay, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Content2, {
|
|
382
|
+
"data-slot": "alert-dialog-content",
|
|
383
|
+
"data-size": size,
|
|
384
|
+
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),
|
|
385
|
+
...props
|
|
386
|
+
})] });
|
|
387
|
+
}
|
|
388
|
+
function AlertDialogHeader({ className, ...props }) {
|
|
389
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
390
|
+
"data-slot": "alert-dialog-header",
|
|
391
|
+
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),
|
|
392
|
+
...props
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
function AlertDialogFooter({ className, ...props }) {
|
|
396
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
397
|
+
"data-slot": "alert-dialog-footer",
|
|
398
|
+
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),
|
|
399
|
+
...props
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
function AlertDialogTitle({ className, ...props }) {
|
|
403
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Title2, {
|
|
404
|
+
"data-slot": "alert-dialog-title",
|
|
405
|
+
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),
|
|
406
|
+
...props
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
function AlertDialogDescription({ className, ...props }) {
|
|
410
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Description2, {
|
|
411
|
+
"data-slot": "alert-dialog-description",
|
|
412
|
+
className: cn("text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed text-balance md:text-pretty *:[a]:underline *:[a]:underline-offset-3", className),
|
|
413
|
+
...props
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
function AlertDialogAction({ className, variant = "default", size = "default", ...props }) {
|
|
417
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
418
|
+
variant,
|
|
419
|
+
size,
|
|
420
|
+
asChild: true,
|
|
421
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Action, {
|
|
422
|
+
"data-slot": "alert-dialog-action",
|
|
423
|
+
className: cn(className),
|
|
424
|
+
...props
|
|
425
|
+
})
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
function AlertDialogCancel({ className, variant = "outline", size = "default", ...props }) {
|
|
429
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
430
|
+
variant,
|
|
431
|
+
size,
|
|
432
|
+
asChild: true,
|
|
433
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Cancel, {
|
|
434
|
+
"data-slot": "alert-dialog-cancel",
|
|
435
|
+
className: cn(className),
|
|
436
|
+
...props
|
|
437
|
+
})
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
function DeleteBundleDialog({ bundle, open, onOpenChange, onSuccess }) {
|
|
441
|
+
const deleteBundleMutation = useDeleteBundleMutation();
|
|
442
|
+
const handleDelete = async () => {
|
|
443
|
+
try {
|
|
444
|
+
await deleteBundleMutation.mutateAsync({ bundleId: bundle.id });
|
|
445
|
+
toast.success("Bundle deleted successfully");
|
|
446
|
+
onOpenChange(false);
|
|
447
|
+
onSuccess();
|
|
448
|
+
} catch (error) {
|
|
449
|
+
toast.error("Failed to delete bundle");
|
|
450
|
+
console.error(error);
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialog$1, {
|
|
454
|
+
open,
|
|
455
|
+
onOpenChange,
|
|
456
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogContent, { children: [
|
|
457
|
+
/* @__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." })] }),
|
|
458
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
459
|
+
className: "my-4 p-4 bg-muted rounded-lg",
|
|
460
|
+
children: [
|
|
461
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
462
|
+
className: "text-sm font-medium mb-1",
|
|
463
|
+
children: "Bundle ID:"
|
|
464
|
+
}),
|
|
465
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
466
|
+
className: "text-xs font-mono text-muted-foreground break-all",
|
|
467
|
+
children: bundle.id
|
|
468
|
+
}),
|
|
469
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
470
|
+
className: "text-sm font-medium mt-3 mb-1",
|
|
471
|
+
children: "Channel:"
|
|
472
|
+
}),
|
|
473
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
474
|
+
className: "text-xs text-muted-foreground",
|
|
475
|
+
children: bundle.channel
|
|
476
|
+
})
|
|
477
|
+
]
|
|
478
|
+
}),
|
|
479
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(AlertDialogFooter, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogCancel, { children: "Cancel" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AlertDialogAction, {
|
|
480
|
+
onClick: handleDelete,
|
|
481
|
+
className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
482
|
+
disabled: deleteBundleMutation.isPending,
|
|
483
|
+
children: deleteBundleMutation.isPending ? "Deleting..." : "Delete"
|
|
484
|
+
})] })
|
|
485
|
+
] })
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
function Dialog$1({ ...props }) {
|
|
489
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Root, {
|
|
490
|
+
"data-slot": "dialog",
|
|
491
|
+
...props
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
function DialogTrigger({ ...props }) {
|
|
495
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Trigger, {
|
|
496
|
+
"data-slot": "dialog-trigger",
|
|
497
|
+
...props
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
function DialogPortal({ ...props }) {
|
|
501
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Portal, {
|
|
502
|
+
"data-slot": "dialog-portal",
|
|
503
|
+
...props
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
function DialogOverlay({ className, ...props }) {
|
|
507
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Overlay, {
|
|
508
|
+
"data-slot": "dialog-overlay",
|
|
509
|
+
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),
|
|
510
|
+
...props
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
function DialogContent({ className, children, showCloseButton = true, ...props }) {
|
|
514
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogPortal, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogOverlay, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Content, {
|
|
515
|
+
"data-slot": "dialog-content",
|
|
516
|
+
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),
|
|
517
|
+
...props,
|
|
518
|
+
children: [children, showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Close, {
|
|
519
|
+
"data-slot": "dialog-close",
|
|
520
|
+
asChild: true,
|
|
521
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
|
|
522
|
+
variant: "ghost",
|
|
523
|
+
className: "absolute top-2 right-2",
|
|
524
|
+
size: "icon-sm",
|
|
525
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
526
|
+
className: "sr-only",
|
|
527
|
+
children: "Close"
|
|
528
|
+
})]
|
|
529
|
+
})
|
|
530
|
+
})]
|
|
531
|
+
})] });
|
|
532
|
+
}
|
|
533
|
+
function DialogHeader({ className, ...props }) {
|
|
534
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
535
|
+
"data-slot": "dialog-header",
|
|
536
|
+
className: cn("gap-1 flex flex-col", className),
|
|
537
|
+
...props
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
function DialogFooter({ className, showCloseButton = false, children, ...props }) {
|
|
541
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
542
|
+
"data-slot": "dialog-footer",
|
|
543
|
+
className: cn("gap-2 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className),
|
|
544
|
+
...props,
|
|
545
|
+
children: [children, showCloseButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Close, {
|
|
546
|
+
asChild: true,
|
|
547
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
548
|
+
variant: "outline",
|
|
549
|
+
children: "Close"
|
|
550
|
+
})
|
|
551
|
+
})]
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
function DialogTitle({ className, ...props }) {
|
|
555
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Title, {
|
|
556
|
+
"data-slot": "dialog-title",
|
|
557
|
+
className: cn("text-sm font-medium", className),
|
|
558
|
+
...props
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
function DialogDescription({ className, ...props }) {
|
|
562
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Description, {
|
|
563
|
+
"data-slot": "dialog-description",
|
|
564
|
+
className: cn("text-muted-foreground *:[a]:hover:text-foreground text-xs/relaxed *:[a]:underline *:[a]:underline-offset-3", className),
|
|
565
|
+
...props
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
function Select$1({ ...props }) {
|
|
569
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Root2$1, {
|
|
570
|
+
"data-slot": "select",
|
|
571
|
+
...props
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
function SelectValue({ ...props }) {
|
|
575
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Value, {
|
|
576
|
+
"data-slot": "select-value",
|
|
577
|
+
...props
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
function SelectTrigger({ className, children, ...props }) {
|
|
581
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Trigger$1, {
|
|
582
|
+
"data-slot": "select-trigger",
|
|
583
|
+
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),
|
|
584
|
+
...props,
|
|
585
|
+
children: [children, /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, {
|
|
586
|
+
asChild: true,
|
|
587
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, { className: "h-4 w-4 opacity-50" })
|
|
588
|
+
})]
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
function SelectContent({ className, children, position = "item-aligned", align = "center", ...props }) {
|
|
592
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Portal$1, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Content2$1, {
|
|
593
|
+
"data-slot": "select-content",
|
|
594
|
+
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),
|
|
595
|
+
position,
|
|
596
|
+
align,
|
|
597
|
+
...props,
|
|
598
|
+
children: [
|
|
599
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectScrollUpButton, {}),
|
|
600
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Viewport, {
|
|
601
|
+
"data-position": position,
|
|
602
|
+
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" && ""),
|
|
603
|
+
children
|
|
604
|
+
}),
|
|
605
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectScrollDownButton, {})
|
|
606
|
+
]
|
|
607
|
+
}) });
|
|
608
|
+
}
|
|
609
|
+
function SelectItem({ className, children, ...props }) {
|
|
610
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Item, {
|
|
611
|
+
"data-slot": "select-item",
|
|
612
|
+
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),
|
|
613
|
+
...props,
|
|
614
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
615
|
+
className: "absolute right-2 flex h-3.5 w-3.5 items-center justify-center",
|
|
616
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ItemIndicator, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: "h-4 w-4" }) })
|
|
617
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ItemText, { children })]
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
function SelectScrollUpButton({ className, ...props }) {
|
|
621
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollUpButton, {
|
|
622
|
+
"data-slot": "select-scroll-up-button",
|
|
623
|
+
className: cn("bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-3.5", className),
|
|
624
|
+
...props,
|
|
625
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronUp, {})
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
function SelectScrollDownButton({ className, ...props }) {
|
|
629
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ScrollDownButton, {
|
|
630
|
+
"data-slot": "select-scroll-down-button",
|
|
631
|
+
className: cn("bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-3.5", className),
|
|
632
|
+
...props,
|
|
633
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronDown, {})
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
function useFilterParams() {
|
|
637
|
+
const search = useSearch({ from: "/" });
|
|
638
|
+
const navigate = useNavigate();
|
|
639
|
+
const filters = {
|
|
640
|
+
channel: search.channel,
|
|
641
|
+
platform: search.platform,
|
|
642
|
+
offset: search.offset
|
|
643
|
+
};
|
|
644
|
+
const bundleId = search.bundleId;
|
|
645
|
+
const navigateWithSearch = (nextSearch) => {
|
|
646
|
+
navigate({
|
|
647
|
+
to: "/",
|
|
648
|
+
search: nextSearch
|
|
649
|
+
});
|
|
650
|
+
};
|
|
651
|
+
const getNextFilters = (newFilters) => {
|
|
652
|
+
const hasChannel = Object.hasOwn(newFilters, "channel");
|
|
653
|
+
const hasPlatform = Object.hasOwn(newFilters, "platform");
|
|
654
|
+
const hasOffset = Object.hasOwn(newFilters, "offset");
|
|
655
|
+
return {
|
|
656
|
+
channel: hasChannel ? newFilters.channel : filters.channel,
|
|
657
|
+
platform: hasPlatform ? newFilters.platform : filters.platform,
|
|
658
|
+
offset: hasChannel || hasPlatform ? "0" : hasOffset ? newFilters.offset : filters.offset
|
|
659
|
+
};
|
|
660
|
+
};
|
|
661
|
+
const setFilters = (newFilters) => {
|
|
662
|
+
navigateWithSearch({
|
|
663
|
+
...getNextFilters(newFilters),
|
|
664
|
+
bundleId: void 0
|
|
665
|
+
});
|
|
666
|
+
};
|
|
667
|
+
const setBundleId = (nextBundleId, newFilters = {}) => {
|
|
668
|
+
navigateWithSearch({
|
|
669
|
+
...getNextFilters(newFilters),
|
|
670
|
+
bundleId: nextBundleId
|
|
671
|
+
});
|
|
672
|
+
};
|
|
673
|
+
const resetFilters = () => {
|
|
674
|
+
navigateWithSearch({
|
|
675
|
+
channel: void 0,
|
|
676
|
+
platform: void 0,
|
|
677
|
+
offset: void 0,
|
|
678
|
+
bundleId: void 0
|
|
679
|
+
});
|
|
680
|
+
};
|
|
681
|
+
return {
|
|
682
|
+
filters,
|
|
683
|
+
bundleId,
|
|
684
|
+
setFilters,
|
|
685
|
+
setBundleId,
|
|
686
|
+
resetFilters
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
function PromoteChannelDialog({ bundle, open, onOpenChange, onSuccess }) {
|
|
690
|
+
const [targetChannel, setTargetChannel] = (0, import_react.useState)("");
|
|
691
|
+
const [action, setAction] = (0, import_react.useState)("move");
|
|
692
|
+
const [copyBundleId, setCopyBundleId] = (0, import_react.useState)("");
|
|
693
|
+
const { setBundleId } = useFilterParams();
|
|
694
|
+
const { data: channels = [] } = useChannelsQuery();
|
|
695
|
+
const promoteBundleMutation = usePromoteBundleMutation();
|
|
696
|
+
const availableChannels = channels.filter((c) => c !== bundle.channel);
|
|
697
|
+
const isCopy = action === "copy";
|
|
698
|
+
const normalizedTargetChannel = targetChannel.trim();
|
|
699
|
+
const isSameChannel = normalizedTargetChannel === bundle.channel;
|
|
700
|
+
const displayedCopyBundleId = copyBundleId || "Generating bundle ID...";
|
|
701
|
+
const handleOpenChange = (nextOpen) => {
|
|
702
|
+
onOpenChange(nextOpen);
|
|
703
|
+
if (!nextOpen) {
|
|
704
|
+
setTargetChannel("");
|
|
705
|
+
setAction("move");
|
|
706
|
+
setCopyBundleId("");
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
const handleActionChange = (value) => {
|
|
710
|
+
const nextAction = value;
|
|
711
|
+
setAction(nextAction);
|
|
712
|
+
if (nextAction === "copy") setCopyBundleId((current) => current || createUUIDv7());
|
|
713
|
+
};
|
|
714
|
+
const openBundleDetail = (nextBundleId, nextChannel) => {
|
|
715
|
+
setBundleId(nextBundleId, {
|
|
716
|
+
channel: nextChannel,
|
|
717
|
+
offset: "0"
|
|
718
|
+
});
|
|
719
|
+
};
|
|
720
|
+
const handlePromote = async () => {
|
|
721
|
+
if (!normalizedTargetChannel) {
|
|
722
|
+
toast.error("Please select a target channel");
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (isSameChannel) {
|
|
726
|
+
toast.error("Target channel must be different from the current channel");
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
try {
|
|
730
|
+
const nextBundleId = isCopy ? copyBundleId || createUUIDv7() : void 0;
|
|
731
|
+
const { bundle: promotedBundle } = await promoteBundleMutation.mutateAsync({
|
|
732
|
+
action,
|
|
733
|
+
bundleId: bundle.id,
|
|
734
|
+
nextBundleId,
|
|
735
|
+
targetChannel: normalizedTargetChannel
|
|
736
|
+
});
|
|
737
|
+
const promotedBundleId = promotedBundle.id;
|
|
738
|
+
handleOpenChange(false);
|
|
739
|
+
onSuccess?.();
|
|
740
|
+
toast.success(isCopy ? `Bundle copied to ${normalizedTargetChannel}` : `Bundle moved to ${normalizedTargetChannel}`, {
|
|
741
|
+
description: `bundleId: ${promotedBundleId}`,
|
|
742
|
+
action: {
|
|
743
|
+
label: "Show Detail",
|
|
744
|
+
onClick: () => openBundleDetail(promotedBundleId, normalizedTargetChannel)
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
} catch (error) {
|
|
748
|
+
toast.error(error instanceof Error ? error.message : "Failed to promote bundle");
|
|
749
|
+
console.error(error);
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog$1, {
|
|
753
|
+
open,
|
|
754
|
+
onOpenChange: handleOpenChange,
|
|
755
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogContent, { children: [
|
|
756
|
+
/* @__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." })] }),
|
|
757
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
758
|
+
className: "space-y-4 py-4",
|
|
759
|
+
children: [
|
|
760
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
761
|
+
className: "space-y-2",
|
|
762
|
+
children: [
|
|
763
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, {
|
|
764
|
+
htmlFor: "promote-action",
|
|
765
|
+
children: "Action"
|
|
766
|
+
}),
|
|
767
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Select$1, {
|
|
768
|
+
value: action,
|
|
769
|
+
onValueChange: handleActionChange,
|
|
770
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, {
|
|
771
|
+
id: "promote-action",
|
|
772
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue, { placeholder: "Select an action" })
|
|
773
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectContent, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
|
|
774
|
+
value: "move",
|
|
775
|
+
children: "Move bundle"
|
|
776
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
|
|
777
|
+
value: "copy",
|
|
778
|
+
children: "Copy bundle"
|
|
779
|
+
})] })]
|
|
780
|
+
}),
|
|
781
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
782
|
+
className: "text-xs text-muted-foreground",
|
|
783
|
+
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."
|
|
784
|
+
})
|
|
785
|
+
]
|
|
786
|
+
}),
|
|
787
|
+
isCopy && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
788
|
+
className: "space-y-2",
|
|
789
|
+
children: [
|
|
790
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, {
|
|
791
|
+
htmlFor: "copy-bundle-id",
|
|
792
|
+
children: "New Bundle ID"
|
|
793
|
+
}),
|
|
794
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
|
|
795
|
+
id: "copy-bundle-id",
|
|
796
|
+
value: displayedCopyBundleId,
|
|
797
|
+
readOnly: true
|
|
798
|
+
}),
|
|
799
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
800
|
+
className: "font-mono text-xs text-muted-foreground",
|
|
801
|
+
children: displayedCopyBundleId
|
|
802
|
+
})
|
|
803
|
+
]
|
|
804
|
+
}),
|
|
805
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
806
|
+
className: "space-y-2",
|
|
807
|
+
children: [
|
|
808
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, {
|
|
809
|
+
htmlFor: "target-channel",
|
|
810
|
+
children: "Target Channel"
|
|
811
|
+
}),
|
|
812
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
|
|
813
|
+
id: "target-channel",
|
|
814
|
+
value: targetChannel,
|
|
815
|
+
onChange: (event) => setTargetChannel(event.target.value),
|
|
816
|
+
placeholder: "Enter a channel name",
|
|
817
|
+
list: "available-channels",
|
|
818
|
+
"aria-invalid": isSameChannel
|
|
819
|
+
}),
|
|
820
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("datalist", {
|
|
821
|
+
id: "available-channels",
|
|
822
|
+
children: availableChannels.map((channel) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: channel }, channel))
|
|
823
|
+
}),
|
|
824
|
+
availableChannels.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
825
|
+
className: "flex flex-wrap gap-2",
|
|
826
|
+
children: availableChannels.map((channel) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
827
|
+
type: "button",
|
|
828
|
+
variant: "outline",
|
|
829
|
+
size: "xs",
|
|
830
|
+
onClick: () => setTargetChannel(channel),
|
|
831
|
+
children: channel
|
|
832
|
+
}, channel))
|
|
833
|
+
}),
|
|
834
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
835
|
+
className: "text-xs text-muted-foreground",
|
|
836
|
+
children: "Choose an existing channel or enter a new one."
|
|
837
|
+
}),
|
|
838
|
+
isSameChannel && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
839
|
+
className: "text-xs text-destructive",
|
|
840
|
+
role: "alert",
|
|
841
|
+
children: "Target channel must be different from the current channel."
|
|
842
|
+
})
|
|
843
|
+
]
|
|
844
|
+
})
|
|
845
|
+
]
|
|
846
|
+
}),
|
|
847
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogFooter, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
848
|
+
variant: "outline",
|
|
849
|
+
onClick: () => handleOpenChange(false),
|
|
850
|
+
children: "Cancel"
|
|
851
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
852
|
+
onClick: handlePromote,
|
|
853
|
+
disabled: !normalizedTargetChannel || isCopy && !copyBundleId || isSameChannel || promoteBundleMutation.isPending,
|
|
854
|
+
children: isCopy ? "Copy" : "Move"
|
|
855
|
+
})] })
|
|
856
|
+
] })
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
function Card({ className, ...props }) {
|
|
860
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
861
|
+
"data-slot": "card",
|
|
862
|
+
className: cn("rounded-xl border bg-card text-card-foreground shadow", className),
|
|
863
|
+
...props
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
function CardHeader({ className, ...props }) {
|
|
867
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
868
|
+
"data-slot": "card-header",
|
|
869
|
+
className: cn("flex flex-col space-y-1.5 p-6", className),
|
|
870
|
+
...props
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
function CardTitle({ className, ...props }) {
|
|
874
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
875
|
+
"data-slot": "card-title",
|
|
876
|
+
className: cn("font-semibold leading-none tracking-tight", className),
|
|
877
|
+
...props
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
function CardDescription({ className, ...props }) {
|
|
881
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
882
|
+
"data-slot": "card-description",
|
|
883
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
884
|
+
...props
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
function CardContent({ className, ...props }) {
|
|
888
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
889
|
+
"data-slot": "card-content",
|
|
890
|
+
className: cn("p-6 pt-0", className),
|
|
891
|
+
...props
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
function RolloutCohortsDialog({ bundleId, rolloutCohortCount, targetCohorts, triggerLabel = "View Cohorts", triggerVariant = "outline", triggerSize = "sm", triggerClassName }) {
|
|
895
|
+
const normalizedRolloutCount = normalizeRolloutCohortCount(rolloutCohortCount);
|
|
896
|
+
const hasTargetCohortOverride = (targetCohorts?.length ?? 0) > 0;
|
|
897
|
+
if (!(normalizedRolloutCount > 0 && normalizedRolloutCount < 1e3 && !hasTargetCohortOverride)) return null;
|
|
898
|
+
const rolloutCohorts = Array.from({ length: NUMERIC_COHORT_SIZE }, (_, index) => index + 1).filter((cohortValue) => getNumericCohortRolloutPosition(bundleId, cohortValue) < normalizedRolloutCount);
|
|
899
|
+
const rolloutPercentage = (normalizedRolloutCount / 10).toFixed(1);
|
|
900
|
+
const excludedCount = NUMERIC_COHORT_SIZE - rolloutCohorts.length;
|
|
901
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Dialog$1, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTrigger, {
|
|
902
|
+
asChild: true,
|
|
903
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
|
|
904
|
+
type: "button",
|
|
905
|
+
variant: triggerVariant,
|
|
906
|
+
size: triggerSize,
|
|
907
|
+
className: triggerClassName,
|
|
908
|
+
onClick: (event) => event.stopPropagation(),
|
|
909
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(List, { className: "h-3.5 w-3.5" }), triggerLabel]
|
|
910
|
+
})
|
|
911
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogContent, {
|
|
912
|
+
className: "sm:max-w-3xl",
|
|
913
|
+
children: [
|
|
914
|
+
/* @__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: [
|
|
915
|
+
rolloutPercentage,
|
|
916
|
+
"% rollout currently targets",
|
|
917
|
+
" ",
|
|
918
|
+
rolloutCohorts.length,
|
|
919
|
+
" of ",
|
|
920
|
+
NUMERIC_COHORT_SIZE,
|
|
921
|
+
" numeric cohorts. The selected set stays stable for this bundle as you expand or shrink rollout."
|
|
922
|
+
] })] }),
|
|
923
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
924
|
+
className: "grid gap-3 sm:grid-cols-3",
|
|
925
|
+
children: [
|
|
926
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
|
|
927
|
+
className: "p-4",
|
|
928
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Selected Cohorts" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
|
|
929
|
+
className: "font-mono text-xl",
|
|
930
|
+
children: rolloutCohorts.length
|
|
931
|
+
})]
|
|
932
|
+
}) }),
|
|
933
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
|
|
934
|
+
className: "p-4",
|
|
935
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Excluded Cohorts" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
|
|
936
|
+
className: "font-mono text-xl",
|
|
937
|
+
children: excludedCount
|
|
938
|
+
})]
|
|
939
|
+
}) }),
|
|
940
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
|
|
941
|
+
className: "p-4",
|
|
942
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Bundle ID" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
|
|
943
|
+
className: "font-mono text-xs break-all leading-relaxed",
|
|
944
|
+
children: bundleId
|
|
945
|
+
})]
|
|
946
|
+
}) })
|
|
947
|
+
]
|
|
948
|
+
}),
|
|
949
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardHeader, {
|
|
950
|
+
className: "p-4 pb-3",
|
|
951
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
|
|
952
|
+
className: "text-sm",
|
|
953
|
+
children: "Numeric Cohorts"
|
|
954
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardDescription, { children: "Listed in ascending order for readability." })]
|
|
955
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardContent, {
|
|
956
|
+
className: "p-4 pt-0",
|
|
957
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
958
|
+
className: "max-h-[50vh] overflow-y-auto rounded-lg border bg-muted/20 p-3",
|
|
959
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
960
|
+
className: "grid grid-cols-4 gap-2 sm:grid-cols-6 lg:grid-cols-8",
|
|
961
|
+
children: rolloutCohorts.map((cohortValue) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
|
|
962
|
+
variant: "outline",
|
|
963
|
+
className: "justify-center font-mono",
|
|
964
|
+
children: cohortValue
|
|
965
|
+
}, cohortValue))
|
|
966
|
+
})
|
|
967
|
+
})
|
|
968
|
+
})] }),
|
|
969
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogFooter, { showCloseButton: true })
|
|
970
|
+
]
|
|
971
|
+
})] });
|
|
972
|
+
}
|
|
973
|
+
function getTargetAppVersionValidation(value) {
|
|
974
|
+
const normalizedValue = value.trim();
|
|
975
|
+
if (normalizedValue.length === 0) return {
|
|
976
|
+
error: "Invalid target app version",
|
|
977
|
+
normalizedRange: null
|
|
978
|
+
};
|
|
979
|
+
const normalizedRange = import_semver.default.validRange(normalizedValue);
|
|
980
|
+
if (!normalizedRange) return {
|
|
981
|
+
error: "Invalid target app version",
|
|
982
|
+
normalizedRange: null
|
|
983
|
+
};
|
|
984
|
+
return {
|
|
985
|
+
error: void 0,
|
|
986
|
+
normalizedRange
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
function getDefaultValues(bundle) {
|
|
990
|
+
return {
|
|
991
|
+
message: bundle.message || "",
|
|
992
|
+
targetAppVersion: bundle.targetAppVersion || "",
|
|
993
|
+
enabled: bundle.enabled,
|
|
994
|
+
shouldForceUpdate: bundle.shouldForceUpdate,
|
|
995
|
+
rolloutCohortCount: bundle.rolloutCohortCount ?? 1e3,
|
|
996
|
+
targetCohorts: bundle.targetCohorts ?? []
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
var formatRolloutPercentage = (rolloutCohortCount) => (rolloutCohortCount / 10).toFixed(1);
|
|
1000
|
+
function BundleEditorForm({ bundle, onClose }) {
|
|
1001
|
+
const bundleDownloadUrlMutation = useBundleDownloadUrlMutation();
|
|
1002
|
+
const updateBundleMutation = useUpdateBundleMutation();
|
|
1003
|
+
const [showPromoteDialog, setShowPromoteDialog] = (0, import_react.useState)(false);
|
|
1004
|
+
const [showDeleteDialog, setShowDeleteDialog] = (0, import_react.useState)(false);
|
|
1005
|
+
const [newCohort, setNewCohort] = (0, import_react.useState)("");
|
|
1006
|
+
const shouldEditTargetAppVersion = Boolean(bundle.targetAppVersion);
|
|
1007
|
+
const form = useForm({
|
|
1008
|
+
defaultValues: getDefaultValues(bundle),
|
|
1009
|
+
onSubmit: async ({ value }) => {
|
|
1010
|
+
const targetAppVersion = value.targetAppVersion.trim();
|
|
1011
|
+
const { error } = shouldEditTargetAppVersion ? getTargetAppVersionValidation(targetAppVersion) : { error: void 0 };
|
|
1012
|
+
if (error) {
|
|
1013
|
+
toast.error(error);
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
const normalizedTargetCohorts = Array.from(new Set(value.targetCohorts.map((cohort) => normalizeCohortValue(cohort))));
|
|
1017
|
+
if (normalizedTargetCohorts.find((cohort) => !isValidCohort(cohort))) {
|
|
1018
|
+
toast.error(INVALID_COHORT_ERROR_MESSAGE);
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
try {
|
|
1022
|
+
await updateBundleMutation.mutateAsync({
|
|
1023
|
+
bundleId: bundle.id,
|
|
1024
|
+
bundle: {
|
|
1025
|
+
message: value.message,
|
|
1026
|
+
targetAppVersion: targetAppVersion || void 0,
|
|
1027
|
+
enabled: value.enabled,
|
|
1028
|
+
shouldForceUpdate: value.shouldForceUpdate,
|
|
1029
|
+
rolloutCohortCount: value.rolloutCohortCount,
|
|
1030
|
+
targetCohorts: normalizedTargetCohorts.length > 0 ? normalizedTargetCohorts : null
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
toast.success("Bundle updated successfully");
|
|
1034
|
+
onClose();
|
|
1035
|
+
} catch (error) {
|
|
1036
|
+
toast.error("Failed to update bundle");
|
|
1037
|
+
console.error(error);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
1041
|
+
const hasChanges = useStore(form.store, (state) => !state.isDefaultValue);
|
|
1042
|
+
const isSubmitting = useStore(form.store, (state) => state.isSubmitting);
|
|
1043
|
+
const targetAppVersion = useStore(form.store, (state) => state.values.targetAppVersion);
|
|
1044
|
+
const targetCohorts = useStore(form.store, (state) => state.values.targetCohorts);
|
|
1045
|
+
const isSaving = isSubmitting || updateBundleMutation.isPending;
|
|
1046
|
+
const targetAppVersionValidation = shouldEditTargetAppVersion ? getTargetAppVersionValidation(targetAppVersion) : {
|
|
1047
|
+
error: void 0,
|
|
1048
|
+
normalizedRange: null
|
|
1049
|
+
};
|
|
1050
|
+
const hasTargetAppVersionError = Boolean(targetAppVersionValidation.error);
|
|
1051
|
+
const isDownloading = bundleDownloadUrlMutation.isPending;
|
|
1052
|
+
const handleAddCohort = () => {
|
|
1053
|
+
const normalizedCohort = normalizeCohortValue(newCohort);
|
|
1054
|
+
if (!normalizedCohort) return;
|
|
1055
|
+
if (!isValidCohort(normalizedCohort)) {
|
|
1056
|
+
toast.error(INVALID_COHORT_ERROR_MESSAGE);
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
const currentCohorts = form.getFieldValue("targetCohorts");
|
|
1060
|
+
if (currentCohorts.includes(normalizedCohort)) {
|
|
1061
|
+
toast.error("Cohort already exists");
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
form.setFieldValue("targetCohorts", [...currentCohorts, normalizedCohort]);
|
|
1065
|
+
setNewCohort("");
|
|
1066
|
+
};
|
|
1067
|
+
const handleRemoveCohort = (cohortToRemove) => {
|
|
1068
|
+
const currentCohorts = form.getFieldValue("targetCohorts");
|
|
1069
|
+
form.setFieldValue("targetCohorts", currentCohorts.filter((cohort) => cohort !== cohortToRemove));
|
|
1070
|
+
};
|
|
1071
|
+
const handleKeyDown = (e) => {
|
|
1072
|
+
if (e.key === "Enter") {
|
|
1073
|
+
e.preventDefault();
|
|
1074
|
+
handleAddCohort();
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
const handleDownloadBundle = async () => {
|
|
1078
|
+
const downloadWindow = window.open("", "_blank");
|
|
1079
|
+
try {
|
|
1080
|
+
const { fileUrl } = await bundleDownloadUrlMutation.mutateAsync({ bundleId: bundle.id });
|
|
1081
|
+
if (!fileUrl) throw new Error("Bundle download URL is empty");
|
|
1082
|
+
if (downloadWindow) {
|
|
1083
|
+
downloadWindow.opener = null;
|
|
1084
|
+
downloadWindow.location.href = fileUrl;
|
|
1085
|
+
} else window.open(fileUrl, "_blank", "noopener,noreferrer");
|
|
1086
|
+
toast.success("Bundle download started");
|
|
1087
|
+
} catch (error) {
|
|
1088
|
+
downloadWindow?.close();
|
|
1089
|
+
const message = error instanceof Error ? error.message : "Failed to download bundle";
|
|
1090
|
+
toast.error(message);
|
|
1091
|
+
console.error(error);
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1095
|
+
className: "space-y-6",
|
|
1096
|
+
children: [
|
|
1097
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", {
|
|
1098
|
+
onSubmit: (e) => {
|
|
1099
|
+
e.preventDefault();
|
|
1100
|
+
e.stopPropagation();
|
|
1101
|
+
if (form.state.isDefaultValue || isSaving || hasTargetAppVersionError) return;
|
|
1102
|
+
form.handleSubmit();
|
|
1103
|
+
},
|
|
1104
|
+
className: "space-y-6",
|
|
1105
|
+
children: [
|
|
1106
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(form.Field, {
|
|
1107
|
+
name: "message",
|
|
1108
|
+
children: (field) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1109
|
+
className: "space-y-2",
|
|
1110
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, {
|
|
1111
|
+
htmlFor: "message",
|
|
1112
|
+
children: "Message"
|
|
1113
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Textarea, {
|
|
1114
|
+
id: "message",
|
|
1115
|
+
value: field.state.value,
|
|
1116
|
+
onChange: (e) => field.handleChange(e.target.value),
|
|
1117
|
+
placeholder: "Update message...",
|
|
1118
|
+
rows: 3
|
|
1119
|
+
})]
|
|
1120
|
+
})
|
|
1121
|
+
}),
|
|
1122
|
+
shouldEditTargetAppVersion && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(form.Field, {
|
|
1123
|
+
name: "targetAppVersion",
|
|
1124
|
+
children: (field) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1125
|
+
className: "space-y-2",
|
|
1126
|
+
children: [
|
|
1127
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, {
|
|
1128
|
+
htmlFor: "targetAppVersion",
|
|
1129
|
+
children: "Target App Version"
|
|
1130
|
+
}),
|
|
1131
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
|
|
1132
|
+
id: "targetAppVersion",
|
|
1133
|
+
value: field.state.value,
|
|
1134
|
+
onChange: (e) => field.handleChange(e.target.value),
|
|
1135
|
+
placeholder: "1.0.0",
|
|
1136
|
+
"aria-invalid": hasTargetAppVersionError
|
|
1137
|
+
}),
|
|
1138
|
+
hasTargetAppVersionError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
1139
|
+
className: "text-xs text-destructive",
|
|
1140
|
+
role: "alert",
|
|
1141
|
+
children: targetAppVersionValidation.error
|
|
1142
|
+
}) : targetAppVersionValidation.normalizedRange && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
1143
|
+
className: "text-xs text-muted-foreground",
|
|
1144
|
+
children: targetAppVersionValidation.normalizedRange
|
|
1145
|
+
})
|
|
1146
|
+
]
|
|
1147
|
+
})
|
|
1148
|
+
}),
|
|
1149
|
+
bundle.fingerprintHash && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1150
|
+
className: "space-y-2",
|
|
1151
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, { children: "Fingerprint Hash" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
|
|
1152
|
+
value: bundle.fingerprintHash,
|
|
1153
|
+
disabled: true,
|
|
1154
|
+
className: "font-mono text-xs"
|
|
1155
|
+
})]
|
|
1156
|
+
}),
|
|
1157
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(form.Field, {
|
|
1158
|
+
name: "enabled",
|
|
1159
|
+
children: (field) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1160
|
+
className: "flex items-center justify-between",
|
|
1161
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, {
|
|
1162
|
+
htmlFor: "enabled",
|
|
1163
|
+
children: "Enabled"
|
|
1164
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Switch$1, {
|
|
1165
|
+
id: "enabled",
|
|
1166
|
+
checked: field.state.value,
|
|
1167
|
+
onCheckedChange: field.handleChange
|
|
1168
|
+
})]
|
|
1169
|
+
})
|
|
1170
|
+
}),
|
|
1171
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(form.Field, {
|
|
1172
|
+
name: "shouldForceUpdate",
|
|
1173
|
+
children: (field) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1174
|
+
className: "flex items-center justify-between",
|
|
1175
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, {
|
|
1176
|
+
htmlFor: "shouldForceUpdate",
|
|
1177
|
+
children: "Force Update"
|
|
1178
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Switch$1, {
|
|
1179
|
+
id: "shouldForceUpdate",
|
|
1180
|
+
checked: field.state.value,
|
|
1181
|
+
onCheckedChange: field.handleChange
|
|
1182
|
+
})]
|
|
1183
|
+
})
|
|
1184
|
+
}),
|
|
1185
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(form.Field, {
|
|
1186
|
+
name: "rolloutCohortCount",
|
|
1187
|
+
children: (field) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1188
|
+
className: "space-y-2",
|
|
1189
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1190
|
+
className: "flex items-center justify-between",
|
|
1191
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, {
|
|
1192
|
+
htmlFor: "rolloutCohortCount",
|
|
1193
|
+
children: "Rollout Percentage"
|
|
1194
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1195
|
+
className: "flex items-center gap-2",
|
|
1196
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1197
|
+
className: "text-sm font-medium",
|
|
1198
|
+
children: [formatRolloutPercentage(field.state.value), "%"]
|
|
1199
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RolloutCohortsDialog, {
|
|
1200
|
+
bundleId: bundle.id,
|
|
1201
|
+
rolloutCohortCount: field.state.value,
|
|
1202
|
+
targetCohorts,
|
|
1203
|
+
triggerLabel: "Preview Cohorts",
|
|
1204
|
+
triggerVariant: "outline",
|
|
1205
|
+
triggerSize: "sm"
|
|
1206
|
+
})]
|
|
1207
|
+
})]
|
|
1208
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Slider$1, {
|
|
1209
|
+
id: "rolloutCohortCount",
|
|
1210
|
+
value: [field.state.value],
|
|
1211
|
+
onValueChange: ([value]) => field.handleChange(value),
|
|
1212
|
+
min: 0,
|
|
1213
|
+
max: 1e3,
|
|
1214
|
+
step: 1,
|
|
1215
|
+
className: "mt-2"
|
|
1216
|
+
})]
|
|
1217
|
+
})
|
|
1218
|
+
}),
|
|
1219
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(form.Field, {
|
|
1220
|
+
name: "targetCohorts",
|
|
1221
|
+
children: (field) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1222
|
+
className: "space-y-2",
|
|
1223
|
+
children: [
|
|
1224
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label$1, { children: "Target Cohorts (optional)" }),
|
|
1225
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1226
|
+
className: "flex gap-2",
|
|
1227
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
|
|
1228
|
+
value: newCohort,
|
|
1229
|
+
onChange: (e) => setNewCohort(e.target.value),
|
|
1230
|
+
onKeyDown: handleKeyDown,
|
|
1231
|
+
placeholder: "Enter cohort...",
|
|
1232
|
+
className: "font-mono text-xs"
|
|
1233
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
1234
|
+
type: "button",
|
|
1235
|
+
variant: "outline",
|
|
1236
|
+
size: "icon",
|
|
1237
|
+
onClick: handleAddCohort,
|
|
1238
|
+
disabled: !newCohort.trim(),
|
|
1239
|
+
"aria-label": "Add cohort",
|
|
1240
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Plus, { className: "h-4 w-4" })
|
|
1241
|
+
})]
|
|
1242
|
+
}),
|
|
1243
|
+
field.state.value.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1244
|
+
className: "flex flex-wrap gap-2 mt-2",
|
|
1245
|
+
children: field.state.value.map((cohort) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
|
|
1246
|
+
variant: "secondary",
|
|
1247
|
+
className: "font-mono text-xs gap-1 pr-1",
|
|
1248
|
+
children: [cohort.length > 20 ? `${cohort.slice(0, 20)}...` : cohort, /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
1249
|
+
type: "button",
|
|
1250
|
+
onClick: () => handleRemoveCohort(cohort),
|
|
1251
|
+
"aria-label": `Remove cohort ${cohort}`,
|
|
1252
|
+
className: "ml-1 rounded-full hover:bg-muted-foreground/20 p-0.5",
|
|
1253
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "h-3 w-3" })
|
|
1254
|
+
})]
|
|
1255
|
+
}, cohort))
|
|
1256
|
+
}),
|
|
1257
|
+
field.state.value.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
|
|
1258
|
+
className: "text-xs text-muted-foreground mt-2",
|
|
1259
|
+
children: [
|
|
1260
|
+
field.state.value.length,
|
|
1261
|
+
" cohort",
|
|
1262
|
+
field.state.value.length !== 1 ? "s" : "",
|
|
1263
|
+
" targeted"
|
|
1264
|
+
]
|
|
1265
|
+
})
|
|
1266
|
+
]
|
|
1267
|
+
})
|
|
1268
|
+
}),
|
|
1269
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1270
|
+
className: "pt-2",
|
|
1271
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
1272
|
+
type: "submit",
|
|
1273
|
+
size: "lg",
|
|
1274
|
+
className: "w-full",
|
|
1275
|
+
disabled: !hasChanges || isSaving || hasTargetAppVersionError,
|
|
1276
|
+
children: isSaving ? "Saving..." : "Save Changes"
|
|
1277
|
+
})
|
|
1278
|
+
})
|
|
1279
|
+
]
|
|
1280
|
+
}),
|
|
1281
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Separator$1, { className: "my-8" }),
|
|
1282
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1283
|
+
className: "space-y-4",
|
|
1284
|
+
children: [
|
|
1285
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", {
|
|
1286
|
+
className: "text-sm font-medium",
|
|
1287
|
+
children: "Actions"
|
|
1288
|
+
}),
|
|
1289
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
1290
|
+
variant: "outline",
|
|
1291
|
+
size: "sm",
|
|
1292
|
+
className: "w-full",
|
|
1293
|
+
onClick: () => setShowPromoteDialog(true),
|
|
1294
|
+
children: "Promote to Channel"
|
|
1295
|
+
}),
|
|
1296
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
|
|
1297
|
+
variant: "outline",
|
|
1298
|
+
size: "sm",
|
|
1299
|
+
className: "w-full",
|
|
1300
|
+
onClick: handleDownloadBundle,
|
|
1301
|
+
disabled: isDownloading,
|
|
1302
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Download, { className: "h-4 w-4" }), isDownloading ? "Preparing Download..." : "Download Bundle"]
|
|
1303
|
+
}),
|
|
1304
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
1305
|
+
variant: "destructive",
|
|
1306
|
+
size: "sm",
|
|
1307
|
+
className: "w-full",
|
|
1308
|
+
onClick: () => setShowDeleteDialog(true),
|
|
1309
|
+
children: "Delete Bundle"
|
|
1310
|
+
})
|
|
1311
|
+
]
|
|
1312
|
+
}),
|
|
1313
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PromoteChannelDialog, {
|
|
1314
|
+
bundle,
|
|
1315
|
+
open: showPromoteDialog,
|
|
1316
|
+
onOpenChange: setShowPromoteDialog,
|
|
1317
|
+
onSuccess: onClose
|
|
1318
|
+
}),
|
|
1319
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DeleteBundleDialog, {
|
|
1320
|
+
bundle,
|
|
1321
|
+
open: showDeleteDialog,
|
|
1322
|
+
onOpenChange: setShowDeleteDialog,
|
|
1323
|
+
onSuccess: onClose
|
|
1324
|
+
})
|
|
1325
|
+
]
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
var normalizeGitUrl = (gitUrl) => {
|
|
1329
|
+
const trimmedGitUrl = gitUrl.trim();
|
|
1330
|
+
if (trimmedGitUrl.startsWith("git@")) {
|
|
1331
|
+
const sshMatch = trimmedGitUrl.match(/^git@([^:]+):(.+?)(?:\.git)?$/);
|
|
1332
|
+
if (sshMatch) return `https://${sshMatch[1]}/${sshMatch[2]}`;
|
|
1333
|
+
}
|
|
1334
|
+
if (trimmedGitUrl.startsWith("ssh://")) try {
|
|
1335
|
+
const url = new URL(trimmedGitUrl);
|
|
1336
|
+
return `https://${url.hostname}${url.pathname}`.replace(/\/+$/, "").replace(/\.git$/, "");
|
|
1337
|
+
} catch {
|
|
1338
|
+
return trimmedGitUrl.replace(/\/+$/, "").replace(/\.git$/, "");
|
|
1339
|
+
}
|
|
1340
|
+
return trimmedGitUrl.replace(/\/+$/, "").replace(/\.git$/, "");
|
|
1341
|
+
};
|
|
1342
|
+
var getCommitUrl = (gitUrl, commitHash) => {
|
|
1343
|
+
if (!gitUrl?.trim()) return null;
|
|
1344
|
+
const normalizedGitUrl = normalizeGitUrl(gitUrl);
|
|
1345
|
+
try {
|
|
1346
|
+
const { hostname } = new URL(normalizedGitUrl);
|
|
1347
|
+
if (hostname.includes("gitlab")) return `${normalizedGitUrl}/-/commit/${commitHash}`;
|
|
1348
|
+
if (hostname.includes("bitbucket")) return `${normalizedGitUrl}/commits/${commitHash}`;
|
|
1349
|
+
} catch {}
|
|
1350
|
+
return `${normalizedGitUrl}/commit/${commitHash}`;
|
|
1351
|
+
};
|
|
1352
|
+
function BundleMetadata({ bundle }) {
|
|
1353
|
+
const { data: configData, isFetched } = useConfigQuery();
|
|
1354
|
+
const hasMetadata = bundle.targetAppVersion || bundle.fingerprintHash || bundle.gitCommitHash || bundle.fileHash;
|
|
1355
|
+
const gitCommitUrl = bundle.gitCommitHash && isFetched ? getCommitUrl(configData?.console.gitUrl, bundle.gitCommitHash) : null;
|
|
1356
|
+
if (!hasMetadata) return null;
|
|
1357
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Card, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardTitle, {
|
|
1358
|
+
className: "text-sm font-medium",
|
|
1359
|
+
children: "Metadata"
|
|
1360
|
+
}) }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CardContent, {
|
|
1361
|
+
className: "space-y-3 text-sm",
|
|
1362
|
+
children: [
|
|
1363
|
+
bundle.targetAppVersion && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1364
|
+
className: "flex items-center justify-between",
|
|
1365
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1366
|
+
className: "text-muted-foreground",
|
|
1367
|
+
children: "App Version"
|
|
1368
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1369
|
+
className: "font-mono",
|
|
1370
|
+
children: bundle.targetAppVersion
|
|
1371
|
+
})]
|
|
1372
|
+
}),
|
|
1373
|
+
bundle.fingerprintHash && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1374
|
+
className: "flex items-center justify-between",
|
|
1375
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1376
|
+
className: "text-muted-foreground",
|
|
1377
|
+
children: "Fingerprint"
|
|
1378
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1379
|
+
className: "font-mono text-xs",
|
|
1380
|
+
children: [bundle.fingerprintHash.slice(0, 16), "..."]
|
|
1381
|
+
})]
|
|
1382
|
+
}),
|
|
1383
|
+
bundle.gitCommitHash && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1384
|
+
className: "flex items-center justify-between",
|
|
1385
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1386
|
+
className: "text-muted-foreground",
|
|
1387
|
+
children: "Git Commit"
|
|
1388
|
+
}), gitCommitUrl ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("a", {
|
|
1389
|
+
href: gitCommitUrl,
|
|
1390
|
+
target: "_blank",
|
|
1391
|
+
rel: "noopener noreferrer",
|
|
1392
|
+
className: "flex items-center gap-1 text-primary hover:underline font-mono text-xs",
|
|
1393
|
+
children: [bundle.gitCommitHash.slice(0, 8), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ExternalLink, { className: "h-3 w-3" })]
|
|
1394
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1395
|
+
className: "font-mono text-xs",
|
|
1396
|
+
children: bundle.gitCommitHash.slice(0, 8)
|
|
1397
|
+
})]
|
|
1398
|
+
}),
|
|
1399
|
+
bundle.fileHash && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1400
|
+
className: "flex items-center justify-between",
|
|
1401
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1402
|
+
className: "text-muted-foreground",
|
|
1403
|
+
children: "File Hash"
|
|
1404
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1405
|
+
className: "font-mono text-xs",
|
|
1406
|
+
children: [bundle.fileHash.slice(0, 16), "..."]
|
|
1407
|
+
})]
|
|
1408
|
+
})
|
|
1409
|
+
]
|
|
1410
|
+
})] });
|
|
1411
|
+
}
|
|
1412
|
+
function BundleEditorSheet({ bundleId, bundle, loading = false, open, onOpenChange }) {
|
|
1413
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sheet, {
|
|
1414
|
+
open,
|
|
1415
|
+
onOpenChange,
|
|
1416
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SheetContent, {
|
|
1417
|
+
className: "w-[600px] sm:max-w-[600px] overflow-y-auto",
|
|
1418
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SheetHeader, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SheetTitle, { children: bundle ? "Edit Bundle" : "Bundle Details" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SheetDescription, { children: bundle ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleBasicInfo, { bundle }) : loading ? bundleId ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1419
|
+
className: "font-mono text-xs",
|
|
1420
|
+
children: ["Loading ", bundleId]
|
|
1421
|
+
}) : "Loading bundle details" : bundleId ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", {
|
|
1422
|
+
className: "font-mono text-xs",
|
|
1423
|
+
children: ["Bundle not found: ", bundleId]
|
|
1424
|
+
}) : "Bundle details unavailable" })] }), bundle ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1425
|
+
className: "px-6 pb-6 space-y-6",
|
|
1426
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleEditorForm, {
|
|
1427
|
+
bundle,
|
|
1428
|
+
onClose: () => onOpenChange(false)
|
|
1429
|
+
}, bundle.id), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleMetadata, { bundle })]
|
|
1430
|
+
}) : loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1431
|
+
className: "px-6 pb-6 space-y-4",
|
|
1432
|
+
children: [
|
|
1433
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" }),
|
|
1434
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-28 w-full" }),
|
|
1435
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" }),
|
|
1436
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-10 w-full" })
|
|
1437
|
+
]
|
|
1438
|
+
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1439
|
+
className: "px-6 pb-6 text-sm text-muted-foreground",
|
|
1440
|
+
children: "The requested bundle could not be loaded."
|
|
1441
|
+
})]
|
|
1442
|
+
})
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
function Table({ className, ...props }) {
|
|
1446
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1447
|
+
"data-slot": "table-container",
|
|
1448
|
+
className: "relative w-full overflow-x-auto",
|
|
1449
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("table", {
|
|
1450
|
+
"data-slot": "table",
|
|
1451
|
+
className: cn("w-full caption-bottom text-xs", className),
|
|
1452
|
+
...props
|
|
1453
|
+
})
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
function TableHeader({ className, ...props }) {
|
|
1457
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", {
|
|
1458
|
+
"data-slot": "table-header",
|
|
1459
|
+
className: cn("[&_tr]:border-b", className),
|
|
1460
|
+
...props
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
function TableBody({ className, ...props }) {
|
|
1464
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", {
|
|
1465
|
+
"data-slot": "table-body",
|
|
1466
|
+
className: cn("[&_tr:last-child]:border-0", className),
|
|
1467
|
+
...props
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
function TableRow({ className, ...props }) {
|
|
1471
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", {
|
|
1472
|
+
"data-slot": "table-row",
|
|
1473
|
+
className: cn("hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", className),
|
|
1474
|
+
...props
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
function TableHead({ className, ...props }) {
|
|
1478
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {
|
|
1479
|
+
"data-slot": "table-head",
|
|
1480
|
+
className: cn("text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0", className),
|
|
1481
|
+
...props
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
function TableCell({ className, ...props }) {
|
|
1485
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", {
|
|
1486
|
+
"data-slot": "table-cell",
|
|
1487
|
+
className: cn("p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0", className),
|
|
1488
|
+
...props
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
function BundleIdDisplay({ bundleId, maxLength = 12 }) {
|
|
1492
|
+
const truncated = bundleId.length > maxLength ? bundleId.slice(-maxLength) : bundleId;
|
|
1493
|
+
if (bundleId.length <= maxLength) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1494
|
+
className: "font-mono text-xs",
|
|
1495
|
+
children: bundleId
|
|
1496
|
+
});
|
|
1497
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Tooltip$1, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipTrigger, {
|
|
1498
|
+
asChild: true,
|
|
1499
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1500
|
+
className: "font-mono text-xs cursor-help",
|
|
1501
|
+
children: truncated
|
|
1502
|
+
})
|
|
1503
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
1504
|
+
className: "font-mono text-xs",
|
|
1505
|
+
children: bundleId
|
|
1506
|
+
}) })] }) });
|
|
1507
|
+
}
|
|
1508
|
+
var channelColors = {
|
|
1509
|
+
production: "bg-green-500/10 text-green-700 dark:text-green-400 border-green-500/20",
|
|
1510
|
+
dev: "bg-blue-500/10 text-blue-700 dark:text-blue-400 border-blue-500/20",
|
|
1511
|
+
staging: "bg-yellow-500/10 text-yellow-700 dark:text-yellow-400 border-yellow-500/20"
|
|
1512
|
+
};
|
|
1513
|
+
function ChannelBadge({ channel, className }) {
|
|
1514
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Badge, {
|
|
1515
|
+
variant: "outline",
|
|
1516
|
+
className: cn(channelColors[channel.toLowerCase()] || "bg-gray-500/10 text-gray-700 dark:text-gray-400 border-gray-500/20", className),
|
|
1517
|
+
children: channel
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
function EnabledStatusIcon({ enabled, className, falseIcon = "x" }) {
|
|
1521
|
+
if (enabled) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Check, { className: cn("h-4 w-4 text-green-600 dark:text-green-400", className) });
|
|
1522
|
+
if (falseIcon === "minus") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Minus, { className: cn("h-4 w-4 text-muted-foreground", className) });
|
|
1523
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: cn("h-4 w-4 text-red-600 dark:text-red-400", className) });
|
|
1524
|
+
}
|
|
1525
|
+
function RolloutPercentageBadge({ percentage, className }) {
|
|
1526
|
+
const isPartialRollout = percentage < 100;
|
|
1527
|
+
const formattedPercentage = percentage.toFixed(1);
|
|
1528
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Badge, {
|
|
1529
|
+
variant: isPartialRollout ? "secondary" : "default",
|
|
1530
|
+
className: cn("gap-1", className),
|
|
1531
|
+
children: [
|
|
1532
|
+
isPartialRollout && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TriangleAlert, { className: "h-3 w-3" }),
|
|
1533
|
+
formattedPercentage,
|
|
1534
|
+
"%"
|
|
1535
|
+
]
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
import_dayjs_min.default.extend(import_relativeTime.default);
|
|
1539
|
+
function TimestampDisplay({ uuid, format = "relative" }) {
|
|
1540
|
+
const date = (0, import_dayjs_min.default)(extractTimestampFromUUIDv7(uuid));
|
|
1541
|
+
if (format === "relative") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1542
|
+
className: "text-sm text-muted-foreground",
|
|
1543
|
+
children: date.fromNow()
|
|
1544
|
+
});
|
|
1545
|
+
if (format === "absolute") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1546
|
+
className: "text-sm text-muted-foreground",
|
|
1547
|
+
children: date.format("YYYY-MM-DD HH:mm:ss")
|
|
1548
|
+
});
|
|
1549
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1550
|
+
className: "flex flex-col",
|
|
1551
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1552
|
+
className: "text-sm",
|
|
1553
|
+
children: date.fromNow()
|
|
1554
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1555
|
+
className: "text-xs text-muted-foreground",
|
|
1556
|
+
children: date.format("YYYY-MM-DD HH:mm:ss")
|
|
1557
|
+
})]
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
var columnHelper = createColumnHelper();
|
|
1561
|
+
var bundleColumns = [
|
|
1562
|
+
columnHelper.accessor("id", {
|
|
1563
|
+
header: "Bundle ID",
|
|
1564
|
+
cell: (info) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleIdDisplay, { bundleId: info.getValue() })
|
|
1565
|
+
}),
|
|
1566
|
+
columnHelper.accessor("channel", {
|
|
1567
|
+
header: "Channel",
|
|
1568
|
+
cell: (info) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChannelBadge, { channel: info.getValue() })
|
|
1569
|
+
}),
|
|
1570
|
+
columnHelper.accessor("platform", {
|
|
1571
|
+
header: "Platform",
|
|
1572
|
+
cell: (info) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1573
|
+
className: "flex items-center gap-2",
|
|
1574
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlatformIcon, {
|
|
1575
|
+
platform: info.getValue(),
|
|
1576
|
+
className: "h-4 w-4"
|
|
1577
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: info.getValue() === "ios" ? "iOS" : "Android" })]
|
|
1578
|
+
})
|
|
1579
|
+
}),
|
|
1580
|
+
columnHelper.display({
|
|
1581
|
+
id: "target",
|
|
1582
|
+
header: "Target",
|
|
1583
|
+
cell: (info) => {
|
|
1584
|
+
const row = info.row.original;
|
|
1585
|
+
if (row.fingerprintHash) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Tooltip$1, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipTrigger, {
|
|
1586
|
+
asChild: true,
|
|
1587
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1588
|
+
className: "flex items-center gap-2 cursor-help",
|
|
1589
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FingerprintPattern, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1590
|
+
className: "font-mono text-xs",
|
|
1591
|
+
children: row.fingerprintHash.slice(0, 8)
|
|
1592
|
+
})]
|
|
1593
|
+
})
|
|
1594
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipContent, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
1595
|
+
className: "font-mono text-xs",
|
|
1596
|
+
children: row.fingerprintHash
|
|
1597
|
+
}) })] });
|
|
1598
|
+
if (row.targetAppVersion) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1599
|
+
className: "flex items-center gap-2",
|
|
1600
|
+
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", {
|
|
1601
|
+
className: "text-sm",
|
|
1602
|
+
children: row.targetAppVersion
|
|
1603
|
+
})]
|
|
1604
|
+
});
|
|
1605
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1606
|
+
className: "text-sm text-muted-foreground",
|
|
1607
|
+
children: "-"
|
|
1608
|
+
});
|
|
1609
|
+
}
|
|
1610
|
+
}),
|
|
1611
|
+
columnHelper.accessor("enabled", {
|
|
1612
|
+
header: "Enabled",
|
|
1613
|
+
cell: (info) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnabledStatusIcon, { enabled: info.getValue() })
|
|
1614
|
+
}),
|
|
1615
|
+
columnHelper.accessor("shouldForceUpdate", {
|
|
1616
|
+
header: "Force Update",
|
|
1617
|
+
cell: (info) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EnabledStatusIcon, {
|
|
1618
|
+
enabled: info.getValue(),
|
|
1619
|
+
falseIcon: "minus"
|
|
1620
|
+
})
|
|
1621
|
+
}),
|
|
1622
|
+
columnHelper.accessor("rolloutCohortCount", {
|
|
1623
|
+
header: "Rollout",
|
|
1624
|
+
cell: (info) => {
|
|
1625
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RolloutPercentageBadge, { percentage: (info.getValue() ?? 1e3) / 10 });
|
|
1626
|
+
}
|
|
1627
|
+
}),
|
|
1628
|
+
columnHelper.accessor("message", {
|
|
1629
|
+
header: "Message",
|
|
1630
|
+
cell: (info) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1631
|
+
className: "text-sm text-muted-foreground",
|
|
1632
|
+
children: info.getValue() || "-"
|
|
1633
|
+
})
|
|
1634
|
+
}),
|
|
1635
|
+
columnHelper.accessor("id", {
|
|
1636
|
+
id: "created",
|
|
1637
|
+
header: "Created",
|
|
1638
|
+
cell: (info) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TimestampDisplay, { uuid: info.getValue() })
|
|
1639
|
+
})
|
|
1640
|
+
];
|
|
1641
|
+
function BundlesTable({ bundles, selectedBundleId, onRowClick }) {
|
|
1642
|
+
const { filters, setFilters } = useFilterParams();
|
|
1643
|
+
const currentOffset = Number(filters.offset || 0);
|
|
1644
|
+
const table = useReactTable({
|
|
1645
|
+
data: bundles,
|
|
1646
|
+
columns: bundleColumns,
|
|
1647
|
+
getCoreRowModel: getCoreRowModel()
|
|
1648
|
+
});
|
|
1649
|
+
const handlePreviousPage = () => {
|
|
1650
|
+
setFilters({ offset: Math.max(0, currentOffset - 20).toString() });
|
|
1651
|
+
};
|
|
1652
|
+
const handleNextPage = () => {
|
|
1653
|
+
setFilters({ offset: (currentOffset + 20).toString() });
|
|
1654
|
+
};
|
|
1655
|
+
const hasNextPage = bundles.length === 20;
|
|
1656
|
+
const hasPreviousPage = currentOffset > 0;
|
|
1657
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1658
|
+
className: "space-y-4",
|
|
1659
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1660
|
+
className: "rounded-lg border bg-card text-card-foreground shadow-sm overflow-hidden",
|
|
1661
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Table, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHeader, {
|
|
1662
|
+
className: "bg-muted/40",
|
|
1663
|
+
children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableRow, {
|
|
1664
|
+
className: "hover:bg-transparent border-b border-border/60",
|
|
1665
|
+
children: headerGroup.headers.map((header) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableHead, {
|
|
1666
|
+
className: "h-10 text-xs font-semibold uppercase text-muted-foreground/70",
|
|
1667
|
+
children: header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())
|
|
1668
|
+
}, header.id))
|
|
1669
|
+
}, headerGroup.id))
|
|
1670
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableBody, { children: table.getRowModel().rows?.length ? table.getRowModel().rows.map((row) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableRow, {
|
|
1671
|
+
"data-state": row.original.id === selectedBundleId ? "selected" : void 0,
|
|
1672
|
+
onClick: () => onRowClick(row.original),
|
|
1673
|
+
className: "cursor-pointer hover:bg-muted/30 transition-colors data-[state=selected]:bg-muted",
|
|
1674
|
+
children: row.getVisibleCells().map((cell) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
|
|
1675
|
+
className: "py-3",
|
|
1676
|
+
children: flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
1677
|
+
}, cell.id))
|
|
1678
|
+
}, row.id)) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableRow, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TableCell, {
|
|
1679
|
+
colSpan: bundleColumns.length,
|
|
1680
|
+
className: "h-32 text-center text-muted-foreground",
|
|
1681
|
+
children: "No bundles found matching your filters."
|
|
1682
|
+
}) }) })] })
|
|
1683
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1684
|
+
className: "flex items-center justify-between px-2",
|
|
1685
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1686
|
+
className: "text-xs text-muted-foreground font-medium",
|
|
1687
|
+
children: [
|
|
1688
|
+
"Showing ",
|
|
1689
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1690
|
+
className: "text-foreground",
|
|
1691
|
+
children: currentOffset + 1
|
|
1692
|
+
}),
|
|
1693
|
+
" ",
|
|
1694
|
+
"to",
|
|
1695
|
+
" ",
|
|
1696
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1697
|
+
className: "text-foreground",
|
|
1698
|
+
children: currentOffset + bundles.length
|
|
1699
|
+
}),
|
|
1700
|
+
" ",
|
|
1701
|
+
"entries"
|
|
1702
|
+
]
|
|
1703
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1704
|
+
className: "flex items-center gap-2",
|
|
1705
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
|
|
1706
|
+
variant: "outline",
|
|
1707
|
+
size: "sm",
|
|
1708
|
+
onClick: handlePreviousPage,
|
|
1709
|
+
disabled: !hasPreviousPage,
|
|
1710
|
+
className: "h-8 px-3 text-xs",
|
|
1711
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronLeft, { className: "h-3.5 w-3.5 mr-1" }), "Previous"]
|
|
1712
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
|
|
1713
|
+
variant: "outline",
|
|
1714
|
+
size: "sm",
|
|
1715
|
+
onClick: handleNextPage,
|
|
1716
|
+
disabled: !hasNextPage,
|
|
1717
|
+
className: "h-8 px-3 text-xs",
|
|
1718
|
+
children: ["Next", /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { className: "h-3.5 w-3.5 ml-1" })]
|
|
1719
|
+
})]
|
|
1720
|
+
})]
|
|
1721
|
+
})]
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1724
|
+
function FilterToolbar() {
|
|
1725
|
+
const { filters, setFilters, resetFilters } = useFilterParams();
|
|
1726
|
+
const { data: channels = [] } = useChannelsQuery();
|
|
1727
|
+
const hasActiveFilters = filters.channel || filters.platform;
|
|
1728
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", {
|
|
1729
|
+
className: "flex h-12 shrink-0 items-center gap-2 border-b px-4 bg-card/50 backdrop-blur-sm sticky top-0 z-10",
|
|
1730
|
+
children: [
|
|
1731
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SidebarTrigger, { className: "-ml-1" }),
|
|
1732
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1733
|
+
className: "flex items-center gap-1.5 text-muted-foreground ml-2",
|
|
1734
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Funnel, { className: "h-3.5 w-3.5" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
1735
|
+
className: "text-xs font-medium",
|
|
1736
|
+
children: "Filters"
|
|
1737
|
+
})]
|
|
1738
|
+
}),
|
|
1739
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Select$1, {
|
|
1740
|
+
value: filters.platform || "all",
|
|
1741
|
+
onValueChange: (value) => setFilters({ platform: value === "all" ? void 0 : value }),
|
|
1742
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, {
|
|
1743
|
+
className: "w-[140px] h-8 text-xs",
|
|
1744
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue, { placeholder: "All Platforms" })
|
|
1745
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectContent, { children: [
|
|
1746
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
|
|
1747
|
+
value: "all",
|
|
1748
|
+
children: "All Platforms"
|
|
1749
|
+
}),
|
|
1750
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
|
|
1751
|
+
value: "ios",
|
|
1752
|
+
children: "iOS"
|
|
1753
|
+
}),
|
|
1754
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
|
|
1755
|
+
value: "android",
|
|
1756
|
+
children: "Android"
|
|
1757
|
+
})
|
|
1758
|
+
] })]
|
|
1759
|
+
}),
|
|
1760
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Select$1, {
|
|
1761
|
+
value: filters.channel || "all",
|
|
1762
|
+
onValueChange: (value) => setFilters({ channel: value === "all" ? void 0 : value }),
|
|
1763
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectTrigger, {
|
|
1764
|
+
className: "w-[140px] h-8 text-xs",
|
|
1765
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectValue, { placeholder: "All Channels" })
|
|
1766
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(SelectContent, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
|
|
1767
|
+
value: "all",
|
|
1768
|
+
children: "All Channels"
|
|
1769
|
+
}), channels.map((channel) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SelectItem, {
|
|
1770
|
+
value: channel,
|
|
1771
|
+
children: channel
|
|
1772
|
+
}, channel))] })]
|
|
1773
|
+
}),
|
|
1774
|
+
hasActiveFilters && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
|
|
1775
|
+
variant: "ghost",
|
|
1776
|
+
size: "sm",
|
|
1777
|
+
onClick: resetFilters,
|
|
1778
|
+
className: "h-8 px-2 text-xs text-muted-foreground hover:text-foreground",
|
|
1779
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(X, { className: "h-3.5 w-3.5 mr-1" }), "Clear"]
|
|
1780
|
+
})
|
|
1781
|
+
]
|
|
1782
|
+
});
|
|
1783
|
+
}
|
|
1784
|
+
function BundlesPage() {
|
|
1785
|
+
const { filters, bundleId, setBundleId } = useFilterParams();
|
|
1786
|
+
const activeBundleId = bundleId ?? "";
|
|
1787
|
+
const { data: bundlesData, isLoading } = useBundlesQuery({
|
|
1788
|
+
channel: filters.channel,
|
|
1789
|
+
platform: filters.platform,
|
|
1790
|
+
offset: filters.offset,
|
|
1791
|
+
limit: "20"
|
|
1792
|
+
});
|
|
1793
|
+
const bundles = bundlesData?.data ?? [];
|
|
1794
|
+
const selectedBundleFromList = activeBundleId ? bundles.find((bundle) => bundle.id === activeBundleId) ?? null : null;
|
|
1795
|
+
const { data: selectedBundleFromQuery, isPending: isSelectedBundlePending } = useBundleQuery(activeBundleId);
|
|
1796
|
+
const selectedBundle = selectedBundleFromQuery ?? selectedBundleFromList;
|
|
1797
|
+
const isSelectedBundleLoading = Boolean(activeBundleId) && !selectedBundle && isSelectedBundlePending;
|
|
1798
|
+
if (isLoading) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1799
|
+
className: "flex flex-col h-full",
|
|
1800
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FilterToolbar, {}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1801
|
+
className: "flex-1 p-6 space-y-4 bg-muted/5",
|
|
1802
|
+
children: [
|
|
1803
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-12 w-full" }),
|
|
1804
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-12 w-full" }),
|
|
1805
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-12 w-full" }),
|
|
1806
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Skeleton, { className: "h-12 w-full" })
|
|
1807
|
+
]
|
|
1808
|
+
})]
|
|
1809
|
+
});
|
|
1810
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
1811
|
+
className: "flex flex-col h-full",
|
|
1812
|
+
children: [
|
|
1813
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FilterToolbar, {}),
|
|
1814
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
1815
|
+
className: "flex-1 p-6 space-y-6 bg-muted/5",
|
|
1816
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundlesTable, {
|
|
1817
|
+
bundles,
|
|
1818
|
+
selectedBundleId: bundleId,
|
|
1819
|
+
onRowClick: (bundle) => setBundleId(bundle.id)
|
|
1820
|
+
})
|
|
1821
|
+
}),
|
|
1822
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(BundleEditorSheet, {
|
|
1823
|
+
bundleId,
|
|
1824
|
+
bundle: selectedBundle,
|
|
1825
|
+
loading: isSelectedBundleLoading,
|
|
1826
|
+
open: Boolean(bundleId),
|
|
1827
|
+
onOpenChange: (open) => !open && setBundleId(void 0)
|
|
1828
|
+
})
|
|
1829
|
+
]
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
//#endregion
|
|
1833
|
+
export { BundlesPage as component };
|