@tapflowio/relay 0.26.1 → 0.27.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/dist/RelayServer.d.ts +72 -0
- package/dist/RelayServer.d.ts.map +1 -1
- package/dist/RelayServer.js +254 -7
- package/dist/RelayServer.js.map +1 -1
- package/dist/SessionManager.d.ts +5 -3
- package/dist/SessionManager.d.ts.map +1 -1
- package/dist/SessionManager.js +3 -1
- package/dist/SessionManager.js.map +1 -1
- package/dist/lib/webhookTransport.d.ts +29 -0
- package/dist/lib/webhookTransport.d.ts.map +1 -0
- package/dist/lib/webhookTransport.js +86 -0
- package/dist/lib/webhookTransport.js.map +1 -0
- package/dist/lib/webhookUrl.d.ts +6 -0
- package/dist/lib/webhookUrl.d.ts.map +1 -1
- package/dist/lib/webhookUrl.js +50 -14
- package/dist/lib/webhookUrl.js.map +1 -1
- package/dist/lib/webhooks.d.ts +2 -10
- package/dist/lib/webhooks.d.ts.map +1 -1
- package/dist/lib/webhooks.js +8 -3
- package/dist/lib/webhooks.js.map +1 -1
- package/package.json +6 -6
- package/public/assets/{AppCenter-DZCsC2wa.js → AppCenter-BMdYtQbV.js} +7 -7
- package/public/assets/AppCenter-BMdYtQbV.js.br +0 -0
- package/public/assets/AppCenter-BMdYtQbV.js.gz +0 -0
- package/public/assets/Default-0DHHVGaN.js +1 -0
- package/public/assets/Default-0DHHVGaN.js.br +0 -0
- package/public/assets/Default-0DHHVGaN.js.gz +0 -0
- package/public/assets/Invite-C0ZB_HiU.js +1 -0
- package/public/assets/Invite-C0ZB_HiU.js.br +0 -0
- package/public/assets/Invite-C0ZB_HiU.js.gz +0 -0
- package/public/assets/{MacResources-B0DQ9ISE.js → MacResources-DmY5pRV2.js} +1 -1
- package/public/assets/MacResources-DmY5pRV2.js.br +0 -0
- package/public/assets/MacResources-DmY5pRV2.js.gz +0 -0
- package/public/assets/QASession-2QogWEX7.js +150 -0
- package/public/assets/QASession-2QogWEX7.js.br +0 -0
- package/public/assets/QASession-2QogWEX7.js.gz +0 -0
- package/public/assets/ResetPassword-jNRuertx.js +1 -0
- package/public/assets/ResetPassword-jNRuertx.js.br +0 -0
- package/public/assets/ResetPassword-jNRuertx.js.gz +0 -0
- package/public/assets/Setup-CF_FN9Dt.js +1 -0
- package/public/assets/Setup-CF_FN9Dt.js.br +0 -0
- package/public/assets/Setup-CF_FN9Dt.js.gz +0 -0
- package/public/assets/Team-BbiyOGau.js +6 -0
- package/public/assets/Team-BbiyOGau.js.br +0 -0
- package/public/assets/Team-BbiyOGau.js.gz +0 -0
- package/public/assets/Tokens-CZo4sPTx.js +1 -0
- package/public/assets/Tokens-CZo4sPTx.js.br +0 -0
- package/public/assets/Tokens-CZo4sPTx.js.gz +0 -0
- package/public/assets/alert-dialog-DFz8G_zu.js +1 -0
- package/public/assets/alert-dialog-DFz8G_zu.js.br +0 -0
- package/public/assets/alert-dialog-DFz8G_zu.js.gz +0 -0
- package/public/assets/badge-B3vaBVIQ.js +1 -0
- package/public/assets/badge-B3vaBVIQ.js.br +0 -0
- package/public/assets/badge-B3vaBVIQ.js.gz +0 -0
- package/public/assets/{build-format-fzt-JrUX.js → build-format-dbQPU3A4.js} +3 -3
- package/public/assets/build-format-dbQPU3A4.js.br +0 -0
- package/public/assets/build-format-dbQPU3A4.js.gz +0 -0
- package/public/assets/{dialog-DQkbaw8h.js → dialog-DeBPk-I3.js} +5 -5
- package/public/assets/dialog-DeBPk-I3.js.br +0 -0
- package/public/assets/dialog-DeBPk-I3.js.gz +0 -0
- package/public/assets/field-hint-CMKmbVpy.js +1 -0
- package/public/assets/field-hint-CMKmbVpy.js.br +0 -0
- package/public/assets/field-hint-CMKmbVpy.js.gz +0 -0
- package/public/assets/index-B4w4Lh3e.js +218 -0
- package/public/assets/index-B4w4Lh3e.js.br +0 -0
- package/public/assets/index-B4w4Lh3e.js.gz +0 -0
- package/public/assets/index-CDWu0GQL.css +1 -0
- package/public/assets/index-CDWu0GQL.css.br +0 -0
- package/public/assets/index-CDWu0GQL.css.gz +0 -0
- package/public/assets/list-view-BvINA5t4.js +1 -0
- package/public/assets/list-view-BvINA5t4.js.br +0 -0
- package/public/assets/list-view-BvINA5t4.js.gz +0 -0
- package/public/assets/{pencil-BJcOp3oN.js → pencil-CycAOoC_.js} +2 -2
- package/public/assets/pencil-CycAOoC_.js.br +0 -0
- package/public/assets/pencil-CycAOoC_.js.gz +0 -0
- package/public/assets/resource-health-C1qSG3MK.js +1 -0
- package/public/assets/resource-health-C1qSG3MK.js.br +0 -0
- package/public/assets/resource-health-C1qSG3MK.js.gz +0 -0
- package/public/assets/{trash-DhBxh6H7.js → trash-BXQnOI5U.js} +3 -3
- package/public/assets/trash-BXQnOI5U.js.br +0 -0
- package/public/assets/trash-BXQnOI5U.js.gz +0 -0
- package/public/index.html +2 -2
- package/public/index.html.br +0 -0
- package/public/index.html.gz +0 -0
- package/public/assets/AppCenter-DZCsC2wa.js.br +0 -0
- package/public/assets/AppCenter-DZCsC2wa.js.gz +0 -0
- package/public/assets/Default-xD9Fmd5X.js +0 -1
- package/public/assets/Default-xD9Fmd5X.js.br +0 -0
- package/public/assets/Default-xD9Fmd5X.js.gz +0 -0
- package/public/assets/Invite-29Erwv8j.js +0 -1
- package/public/assets/Invite-29Erwv8j.js.br +0 -0
- package/public/assets/Invite-29Erwv8j.js.gz +0 -0
- package/public/assets/MacResources-B0DQ9ISE.js.br +0 -0
- package/public/assets/MacResources-B0DQ9ISE.js.gz +0 -0
- package/public/assets/QASession-D-n6PXuD.js +0 -150
- package/public/assets/QASession-D-n6PXuD.js.br +0 -0
- package/public/assets/QASession-D-n6PXuD.js.gz +0 -0
- package/public/assets/ResetPassword-BnPIwTYH.js +0 -1
- package/public/assets/ResetPassword-BnPIwTYH.js.br +0 -0
- package/public/assets/ResetPassword-BnPIwTYH.js.gz +0 -0
- package/public/assets/Setup-DmUGFblg.js +0 -1
- package/public/assets/Setup-DmUGFblg.js.br +0 -0
- package/public/assets/Setup-DmUGFblg.js.gz +0 -0
- package/public/assets/Team-BpvRgFfZ.js +0 -6
- package/public/assets/Team-BpvRgFfZ.js.br +0 -0
- package/public/assets/Team-BpvRgFfZ.js.gz +0 -0
- package/public/assets/Tokens-x6BtCcL7.js +0 -1
- package/public/assets/Tokens-x6BtCcL7.js.br +0 -0
- package/public/assets/Tokens-x6BtCcL7.js.gz +0 -0
- package/public/assets/alert-dialog-C8wDimW-.js +0 -1
- package/public/assets/alert-dialog-C8wDimW-.js.br +0 -0
- package/public/assets/alert-dialog-C8wDimW-.js.gz +0 -0
- package/public/assets/badge-CWVbeuH3.js +0 -1
- package/public/assets/badge-CWVbeuH3.js.br +0 -0
- package/public/assets/badge-CWVbeuH3.js.gz +0 -0
- package/public/assets/build-format-fzt-JrUX.js.br +0 -0
- package/public/assets/build-format-fzt-JrUX.js.gz +0 -0
- package/public/assets/dialog-DQkbaw8h.js.br +0 -0
- package/public/assets/dialog-DQkbaw8h.js.gz +0 -0
- package/public/assets/index-C1Fo8g_c.js +0 -218
- package/public/assets/index-C1Fo8g_c.js.br +0 -0
- package/public/assets/index-C1Fo8g_c.js.gz +0 -0
- package/public/assets/index-C2F1AgwB.css +0 -1
- package/public/assets/index-C2F1AgwB.css.br +0 -0
- package/public/assets/index-C2F1AgwB.css.gz +0 -0
- package/public/assets/list-view-DxI397-H.js +0 -1
- package/public/assets/list-view-DxI397-H.js.br +0 -0
- package/public/assets/list-view-DxI397-H.js.gz +0 -0
- package/public/assets/pencil-BJcOp3oN.js.br +0 -0
- package/public/assets/pencil-BJcOp3oN.js.gz +0 -0
- package/public/assets/resource-health-Cq3gqqVd.js +0 -1
- package/public/assets/resource-health-Cq3gqqVd.js.br +0 -0
- package/public/assets/resource-health-Cq3gqqVd.js.gz +0 -0
- package/public/assets/trash-DhBxh6H7.js.br +0 -0
- package/public/assets/trash-DhBxh6H7.js.gz +0 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import dns from 'dns';
|
|
2
|
+
import http from 'http';
|
|
3
|
+
import https from 'https';
|
|
4
|
+
import net from 'net';
|
|
5
|
+
import { isBlockedAddress } from './webhookUrl.js';
|
|
6
|
+
const systemLookup = (hostname) => dns.promises.lookup(hostname, { all: true });
|
|
7
|
+
// Only drained, never read for meaning, so a receiver can't make the relay buffer a large reply.
|
|
8
|
+
const MAX_RESPONSE_BYTES = 64 * 1024;
|
|
9
|
+
export class BlockedDestinationError extends Error {
|
|
10
|
+
constructor(host, address) {
|
|
11
|
+
super(`${host} resolves to ${address}, a loopback or metadata address`);
|
|
12
|
+
this.name = 'BlockedDestinationError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Webhook delivery transport. Unlike global fetch it judges the addresses a hostname resolves to, and
|
|
17
|
+
* the socket connects only to addresses that passed: the check runs inside the connection's own
|
|
18
|
+
* `lookup`, so there is no second resolution for DNS rebinding to answer differently. It never
|
|
19
|
+
* follows redirects — node's http client doesn't — so a 3xx comes back as a non-ok response instead of
|
|
20
|
+
* a hop to wherever the receiver points.
|
|
21
|
+
*/
|
|
22
|
+
export function createSafeFetch(opts = {}) {
|
|
23
|
+
const resolve = opts.lookup ?? systemLookup;
|
|
24
|
+
const isBlocked = opts.isBlocked ?? isBlockedAddress;
|
|
25
|
+
const gatedLookup = (hostname, options, callback) => {
|
|
26
|
+
resolve(hostname).then((answers) => {
|
|
27
|
+
if (answers.length === 0)
|
|
28
|
+
return callback(Object.assign(new Error(`getaddrinfo ENOTFOUND ${hostname}`), { code: 'ENOTFOUND' }), '');
|
|
29
|
+
// Blocked answers are dropped rather than failing the name: macOS answers a LAN host's `.local`
|
|
30
|
+
// name with its fe80:: address next to its 192.168.x one, and that host is a webhook receiver this
|
|
31
|
+
// feature exists for. Dropping is safe because only what survives is handed to the socket.
|
|
32
|
+
const permitted = answers.filter((a) => !isBlocked(a.address));
|
|
33
|
+
if (permitted.length === 0)
|
|
34
|
+
return callback(new BlockedDestinationError(hostname, answers[0].address), '');
|
|
35
|
+
const wanted = options.family === 4 || options.family === 6 ? permitted.filter((a) => a.family === options.family) : permitted;
|
|
36
|
+
if (wanted.length === 0)
|
|
37
|
+
return callback(Object.assign(new Error(`getaddrinfo ENOTFOUND ${hostname}`), { code: 'ENOTFOUND' }), '');
|
|
38
|
+
if (options.all)
|
|
39
|
+
return callback(null, wanted);
|
|
40
|
+
callback(null, wanted[0].address, wanted[0].family);
|
|
41
|
+
}, (err) => callback(err, ''));
|
|
42
|
+
};
|
|
43
|
+
return (url, init) => new Promise((resolvePromise, reject) => {
|
|
44
|
+
const u = new URL(url);
|
|
45
|
+
const host = u.hostname.replace(/^\[|\]$/g, '');
|
|
46
|
+
// net.connect skips `lookup` for an IP literal, so a literal is judged here.
|
|
47
|
+
if (net.isIP(host) && isBlocked(host))
|
|
48
|
+
return reject(new BlockedDestinationError(host, host));
|
|
49
|
+
// A row written before registration refused these must not leak them as an Authorization header.
|
|
50
|
+
if (u.username || u.password)
|
|
51
|
+
return reject(new Error('webhook URL must not contain credentials'));
|
|
52
|
+
const client = u.protocol === 'https:' ? https : http;
|
|
53
|
+
const req = client.request(u, {
|
|
54
|
+
method: init.method,
|
|
55
|
+
// fetch sent a User-Agent and Accept on its own, and edge WAFs commonly refuse a request with no
|
|
56
|
+
// User-Agent at all.
|
|
57
|
+
headers: {
|
|
58
|
+
'User-Agent': 'tapflow-webhook',
|
|
59
|
+
Accept: '*/*',
|
|
60
|
+
...init.headers,
|
|
61
|
+
'Content-Length': String(Buffer.byteLength(init.body)),
|
|
62
|
+
},
|
|
63
|
+
lookup: gatedLookup,
|
|
64
|
+
signal: init.signal,
|
|
65
|
+
// A pooled socket would skip the lookup; every delivery resolves and is judged afresh.
|
|
66
|
+
agent: false,
|
|
67
|
+
}, (res) => {
|
|
68
|
+
const chunks = [];
|
|
69
|
+
let size = 0;
|
|
70
|
+
res.on('data', (c) => {
|
|
71
|
+
if (size < MAX_RESPONSE_BYTES)
|
|
72
|
+
chunks.push(c);
|
|
73
|
+
size += c.length;
|
|
74
|
+
});
|
|
75
|
+
res.on('error', reject);
|
|
76
|
+
res.on('end', () => {
|
|
77
|
+
const status = res.statusCode ?? 0;
|
|
78
|
+
const body = Buffer.concat(chunks).subarray(0, MAX_RESPONSE_BYTES).toString();
|
|
79
|
+
resolvePromise({ ok: status >= 200 && status < 300, status, text: async () => body });
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
req.on('error', reject);
|
|
83
|
+
req.end(init.body);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=webhookTransport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhookTransport.js","sourceRoot":"","sources":["../../src/lib/webhookTransport.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/webhookUrl.d.ts
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
/** True when an IP address is one a webhook must never be delivered to. */
|
|
2
|
+
export declare function isBlockedAddress(ip: string): boolean;
|
|
1
3
|
/**
|
|
2
4
|
* Validate a webhook destination URL. Returns an error string, or null when allowed.
|
|
3
5
|
* Blocks loopback and cloud-metadata addresses; private LAN ranges (10/172.16/192.168)
|
|
4
6
|
* are intentionally allowed because self-hosted CI often lives there.
|
|
5
7
|
*
|
|
8
|
+
* This is the registration-time check and sees only the URL, so a hostname is judged by its name.
|
|
9
|
+
* The address it resolves to is judged at delivery, by webhookTransport.ts — DNS can point a
|
|
10
|
+
* harmless-looking name at loopback, and can change after registration.
|
|
11
|
+
*
|
|
6
12
|
* Lives in its own module so both webhooks.ts (delivery) and config.ts (config-file
|
|
7
13
|
* endpoints) can validate without a circular import — webhooks.ts imports config.ts,
|
|
8
14
|
* so config.ts can't import back into webhooks.ts.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhookUrl.d.ts","sourceRoot":"","sources":["../../src/lib/webhookUrl.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"webhookUrl.d.ts","sourceRoot":"","sources":["../../src/lib/webhookUrl.ts"],"names":[],"mappings":"AAgCA,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAIpD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAwB7D"}
|
package/dist/lib/webhookUrl.js
CHANGED
|
@@ -1,8 +1,48 @@
|
|
|
1
|
+
import net from 'net';
|
|
2
|
+
// Loopback, unspecified, link-local (which holds the 169.254.169.254 metadata service) and AWS's IPv6
|
|
3
|
+
// metadata address. Private LAN ranges (10/172.16/192.168, fc00::/7) are intentionally absent:
|
|
4
|
+
// self-hosted CI usually lives there, and webhooks reaching it is the feature.
|
|
5
|
+
const blocked = new net.BlockList();
|
|
6
|
+
// BlockList matches the IPv4-mapped form (::ffff:127.0.0.1, ::ffff:7f00:1) of an IPv4 subnet by itself,
|
|
7
|
+
// but no other IPv6 spelling that carries an IPv4 address. Those are added as subnets of their own:
|
|
8
|
+
// IPv4-compatible ::a.b.c.d, IPv4-translated ::ffff:0:a.b.c.d (RFC 6052), the NAT64 well-known
|
|
9
|
+
// prefix 64:ff9b::/96, and 6to4 2002:aabb:ccdd::/48 — a NAT64 or 6to4 gateway on the relay's network
|
|
10
|
+
// hands such an address to the embedded IPv4 host. Not covered: the local-use NAT64 prefix
|
|
11
|
+
// 64:ff9b:1::/48 (RFC 8215) and any prefix an operator picks. Only a gateway that operator configured
|
|
12
|
+
// translates those, and an operator's own prefix cannot be known from here.
|
|
13
|
+
function blockIPv4Subnet(address, prefix) {
|
|
14
|
+
blocked.addSubnet(address, prefix, 'ipv4');
|
|
15
|
+
const [a, b, c, d] = address.split('.').map(Number);
|
|
16
|
+
const hi = ((a << 8) | b).toString(16);
|
|
17
|
+
const lo = ((c << 8) | d).toString(16);
|
|
18
|
+
blocked.addSubnet(`::${hi}:${lo}`, 96 + prefix, 'ipv6');
|
|
19
|
+
blocked.addSubnet(`::ffff:0:${hi}:${lo}`, 96 + prefix, 'ipv6');
|
|
20
|
+
blocked.addSubnet(`64:ff9b::${hi}:${lo}`, 96 + prefix, 'ipv6');
|
|
21
|
+
blocked.addSubnet(`2002:${hi}:${lo}::`, 16 + prefix, 'ipv6');
|
|
22
|
+
}
|
|
23
|
+
blockIPv4Subnet('127.0.0.0', 8);
|
|
24
|
+
blockIPv4Subnet('0.0.0.0', 8);
|
|
25
|
+
blockIPv4Subnet('169.254.0.0', 16);
|
|
26
|
+
blocked.addAddress('::1', 'ipv6');
|
|
27
|
+
blocked.addAddress('::', 'ipv6');
|
|
28
|
+
blocked.addSubnet('fe80::', 10, 'ipv6');
|
|
29
|
+
blocked.addAddress('fd00:ec2::254', 'ipv6');
|
|
30
|
+
/** True when an IP address is one a webhook must never be delivered to. */
|
|
31
|
+
export function isBlockedAddress(ip) {
|
|
32
|
+
const family = net.isIP(ip);
|
|
33
|
+
if (family === 0)
|
|
34
|
+
return false;
|
|
35
|
+
return blocked.check(ip, family === 4 ? 'ipv4' : 'ipv6');
|
|
36
|
+
}
|
|
1
37
|
/**
|
|
2
38
|
* Validate a webhook destination URL. Returns an error string, or null when allowed.
|
|
3
39
|
* Blocks loopback and cloud-metadata addresses; private LAN ranges (10/172.16/192.168)
|
|
4
40
|
* are intentionally allowed because self-hosted CI often lives there.
|
|
5
41
|
*
|
|
42
|
+
* This is the registration-time check and sees only the URL, so a hostname is judged by its name.
|
|
43
|
+
* The address it resolves to is judged at delivery, by webhookTransport.ts — DNS can point a
|
|
44
|
+
* harmless-looking name at loopback, and can change after registration.
|
|
45
|
+
*
|
|
6
46
|
* Lives in its own module so both webhooks.ts (delivery) and config.ts (config-file
|
|
7
47
|
* endpoints) can validate without a circular import — webhooks.ts imports config.ts,
|
|
8
48
|
* so config.ts can't import back into webhooks.ts.
|
|
@@ -18,21 +58,17 @@ export function validateWebhookUrl(raw) {
|
|
|
18
58
|
if (u.protocol !== 'http:' && u.protocol !== 'https:') {
|
|
19
59
|
return 'URL must use http or https';
|
|
20
60
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// to the embedded IPv4 — an SSRF bypass. Unwrap it back to dotted IPv4 first.
|
|
25
|
-
const mapped = host.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
|
|
26
|
-
if (mapped) {
|
|
27
|
-
const hi = parseInt(mapped[1], 16);
|
|
28
|
-
const lo = parseInt(mapped[2], 16);
|
|
29
|
-
host = [hi >> 8, hi & 0xff, lo >> 8, lo & 0xff].join('.');
|
|
61
|
+
// fetch refused these outright; node's http client would turn them into an Authorization header.
|
|
62
|
+
if (u.username || u.password) {
|
|
63
|
+
return 'URL must not contain credentials';
|
|
30
64
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
65
|
+
// WHATWG URL has already canonicalized numeric IPv4 spellings (2130706433, 0x7f.1 → 127.0.0.1).
|
|
66
|
+
const host = u.hostname
|
|
67
|
+
.toLowerCase()
|
|
68
|
+
.replace(/^\[|\]$/g, '') // strip IPv6 brackets
|
|
69
|
+
.replace(/\.$/, ''); // a trailing dot names the same host
|
|
70
|
+
// RFC 6761 reserves localhost and every name under it for loopback, with no DNS involved.
|
|
71
|
+
if (host === 'localhost' || host.endsWith('.localhost') || isBlockedAddress(host)) {
|
|
36
72
|
return 'URL host is not allowed (loopback or metadata address)';
|
|
37
73
|
}
|
|
38
74
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhookUrl.js","sourceRoot":"","sources":["../../src/lib/webhookUrl.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"webhookUrl.js","sourceRoot":"","sources":["../../src/lib/webhookUrl.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/webhooks.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { type FetchLike } from './webhookTransport.js';
|
|
1
2
|
export { validateWebhookUrl } from './webhookUrl.js';
|
|
3
|
+
export type { FetchLike } from './webhookTransport.js';
|
|
2
4
|
export declare const WEBHOOK_TIMEOUT_MS = 5000;
|
|
3
5
|
export interface WebhookPayload {
|
|
4
6
|
event: string;
|
|
@@ -10,16 +12,6 @@ export interface WebhookPayload {
|
|
|
10
12
|
};
|
|
11
13
|
changedAt: string;
|
|
12
14
|
}
|
|
13
|
-
export type FetchLike = (url: string, init: {
|
|
14
|
-
method: string;
|
|
15
|
-
headers: Record<string, string>;
|
|
16
|
-
body: string;
|
|
17
|
-
signal: AbortSignal;
|
|
18
|
-
}) => Promise<{
|
|
19
|
-
ok: boolean;
|
|
20
|
-
status: number;
|
|
21
|
-
text: () => Promise<string>;
|
|
22
|
-
}>;
|
|
23
15
|
/** HMAC-SHA256 signature of the raw request body, formatted like EAS's expo-signature. */
|
|
24
16
|
export declare function signPayload(secret: string, body: string): string;
|
|
25
17
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../src/lib/webhooks.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../src/lib/webhooks.ts"],"names":[],"mappings":"AAIA,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAIvE,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACpD,YAAY,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAKtD,eAAO,MAAM,kBAAkB,OAAO,CAAA;AAEtC,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE;QACL,EAAE,EAAE,MAAM,CAAA;QACV,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;QACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;QACzB,MAAM,EAAE,MAAM,CAAA;KACf,CAAA;IACD,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,0FAA0F;AAC1F,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEhE;AAOD;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,cAAc,EACvB,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,SAAS,CAAA;CAAO,GACjC,OAAO,CAAC,IAAI,CAAC,CAqCf"}
|
package/dist/lib/webhooks.js
CHANGED
|
@@ -2,6 +2,7 @@ import crypto from 'crypto';
|
|
|
2
2
|
import { createLogger } from '@tapflowio/agent-core';
|
|
3
3
|
import { getDb } from '../db.js';
|
|
4
4
|
import { config } from './config.js';
|
|
5
|
+
import { createSafeFetch } from './webhookTransport.js';
|
|
5
6
|
// Re-exported so existing importers keep using webhooks.ts; the implementation lives
|
|
6
7
|
// in webhookUrl.ts to avoid a circular import with config.ts (which also validates).
|
|
7
8
|
export { validateWebhookUrl } from './webhookUrl.js';
|
|
@@ -29,7 +30,7 @@ export async function deliverWebhooks(payload, opts = {}) {
|
|
|
29
30
|
if (rows.length === 0)
|
|
30
31
|
return;
|
|
31
32
|
const body = JSON.stringify(payload);
|
|
32
|
-
const fetchFn = opts.fetchFn ??
|
|
33
|
+
const fetchFn = opts.fetchFn ?? createSafeFetch();
|
|
33
34
|
await Promise.allSettled(rows.map(async (ep) => {
|
|
34
35
|
const headers = { 'Content-Type': 'application/json' };
|
|
35
36
|
if (ep.secret)
|
|
@@ -41,10 +42,14 @@ export async function deliverWebhooks(payload, opts = {}) {
|
|
|
41
42
|
body,
|
|
42
43
|
signal: AbortSignal.timeout(WEBHOOK_TIMEOUT_MS),
|
|
43
44
|
});
|
|
44
|
-
// Drain the body so
|
|
45
|
+
// Drain the body so the connection is released.
|
|
45
46
|
await res.text();
|
|
46
|
-
if (
|
|
47
|
+
if (res.status >= 300 && res.status < 400) {
|
|
48
|
+
logger.warn(`webhook POST ${ep.url} returned ${res.status} (redirects are not followed; register the final URL)`);
|
|
49
|
+
}
|
|
50
|
+
else if (!res.ok) {
|
|
47
51
|
logger.warn(`webhook POST ${ep.url} returned ${res.status}`);
|
|
52
|
+
}
|
|
48
53
|
}
|
|
49
54
|
catch (err) {
|
|
50
55
|
logger.warn(`webhook POST ${ep.url} failed: ${String(err)}`);
|
package/dist/lib/webhooks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../../src/lib/webhooks.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAA;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../../src/lib/webhooks.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tapflowio/relay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "WebSocket relay server for tapflow — NAT traversal, session routing, JWT auth, bundled dashboard",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tapflow",
|
|
@@ -49,18 +49,18 @@
|
|
|
49
49
|
"better-sqlite3": "^12.11.1",
|
|
50
50
|
"busboy": "^1.6.0",
|
|
51
51
|
"jsonwebtoken": "^9.0.3",
|
|
52
|
-
"nodemailer": "^
|
|
53
|
-
"ws": "^8.
|
|
52
|
+
"nodemailer": "^10.0.11",
|
|
53
|
+
"ws": "^8.22.0",
|
|
54
54
|
"zod": "^4.6.5",
|
|
55
|
-
"@tapflowio/agent-core": "0.
|
|
56
|
-
"@tapflowio/protocol": "0.
|
|
55
|
+
"@tapflowio/agent-core": "0.27.0",
|
|
56
|
+
"@tapflowio/protocol": "0.27.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@types/better-sqlite3": "^7.6.13",
|
|
60
60
|
"@types/busboy": "^1.5.4",
|
|
61
61
|
"@types/jsonwebtoken": "^9.0.10",
|
|
62
62
|
"@types/node": "^22.20.4",
|
|
63
|
-
"@types/nodemailer": "^8.0.
|
|
63
|
+
"@types/nodemailer": "^8.0.2",
|
|
64
64
|
"@types/ws": "^8.0.0",
|
|
65
65
|
"typescript": "^5.0.0",
|
|
66
66
|
"vitest": "^4.1.11",
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
var Ue=l=>{throw TypeError(l)};var
|
|
2
|
-
* @license lucide-react v1.
|
|
1
|
+
var Ue=l=>{throw TypeError(l)};var Oe=(l,e,s)=>e.has(l)||Ue("Cannot "+s);var C=(l,e,s)=>(Oe(l,e,"read from private field"),s?s.call(l):e.get(l)),fe=(l,e,s)=>e.has(l)?Ue("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(l):e.set(l,s),me=(l,e,s,a)=>(Oe(l,e,"write to private field"),a?a.call(l,s):e.set(l,s),s),he=(l,e,s)=>(Oe(l,e,"access private method"),s);import{k as c,f as pe,j as t}from"./vendor-react-BlPSzrEe.js";import{J as xt,ax as gt,ap as Ve,ah as bt,at as et,aO as tt,as as jt,ay as yt,a6 as ve,aA as W,B as re,L as ye,a0 as vt,I as He,F as St,a2 as Nt,S as wt,N as Dt,T as Ct,O as kt,K as _t,f as At,aD as It,aN as Je,ao as Ot,$ as Et,aw as Rt,aC as Ft,r as Tt,av as $t,ac as Mt,ar as Pt,af as Bt}from"./index-B4w4Lh3e.js";import{f as zt,d as Fe,S as Lt,P as qt,b as Kt,e as Qt}from"./build-format-dbQPU3A4.js";import{f as st,d as at,e as lt,i as Ce,j as ke,g as _e,h as F,S as Ae,a as nt,D as rt,C as Ut}from"./dialog-DeBPk-I3.js";import{P as Vt,T as Ht}from"./trash-BXQnOI5U.js";import{B as Ee}from"./badge-B3vaBVIQ.js";import{f as Jt,g as Wt,d as Gt,b as Xt,c as Yt,e as Zt,a as es,A as ts}from"./alert-dialog-DFz8G_zu.js";import{u as ss}from"./useFocusAfterSwap-BY3N9f9v.js";import{b as as,c as ls}from"./vendor-router-B_lVHrKl.js";var le,de,M,ne,se,je,Te,Ze,ns=(Ze=class extends xt{constructor(e,s){super();fe(this,se);fe(this,le);fe(this,de);fe(this,M);fe(this,ne);me(this,le,e),this.setOptions(s),this.bindMethods(),he(this,se,je).call(this)}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){var a;const s=this.options;this.options=C(this,le).defaultMutationOptions(e),gt(this.options,s)||C(this,le).getMutationCache().notify({type:"observerOptionsUpdated",mutation:C(this,M),observer:this}),s!=null&&s.mutationKey&&this.options.mutationKey&&Ve(s.mutationKey)!==Ve(this.options.mutationKey)?this.reset():((a=C(this,M))==null?void 0:a.state.status)==="pending"&&C(this,M).setOptions(this.options)}onSubscribe(){this.listeners.size===1&&C(this,M)&&(C(this,M).addObserver(this),he(this,se,je).call(this))}onUnsubscribe(){var e;this.hasListeners()||(e=C(this,M))==null||e.removeObserver(this)}onMutationUpdate(e){he(this,se,je).call(this),he(this,se,Te).call(this,e)}getCurrentResult(){return C(this,de)}reset(){var e;(e=C(this,M))==null||e.removeObserver(this),me(this,M,void 0),he(this,se,je).call(this),he(this,se,Te).call(this)}mutate(e,s){var a;return me(this,ne,s),(a=C(this,M))==null||a.removeObserver(this),me(this,M,C(this,le).getMutationCache().build(C(this,le),this.options)),C(this,M).addObserver(this),C(this,M).execute(e)}},le=new WeakMap,de=new WeakMap,M=new WeakMap,ne=new WeakMap,se=new WeakSet,je=function(){var s;const e=((s=C(this,M))==null?void 0:s.state)??bt();me(this,de,{...e,isPending:e.status==="pending",isSuccess:e.status==="success",isError:e.status==="error",isIdle:e.status==="idle",mutate:this.mutate,reset:this.reset})},Te=function(e){et.batch(()=>{var s,a,i,u,f,j,b,m;if(C(this,ne)&&this.hasListeners()){const d=C(this,de).variables,y=C(this,de).context,p={client:C(this,le),meta:this.options.meta,mutationKey:this.options.mutationKey};if((e==null?void 0:e.type)==="success"){try{(a=(s=C(this,ne)).onSuccess)==null||a.call(s,e.data,d,y,p)}catch(r){Promise.reject(r)}try{(u=(i=C(this,ne)).onSettled)==null||u.call(i,e.data,null,d,y,p)}catch(r){Promise.reject(r)}}else if((e==null?void 0:e.type)==="error"){try{(j=(f=C(this,ne)).onError)==null||j.call(f,e.error,d,y,p)}catch(r){Promise.reject(r)}try{(m=(b=C(this,ne)).onSettled)==null||m.call(b,void 0,e.error,d,y,p)}catch(r){Promise.reject(r)}}}this.listeners.forEach(d=>{d(C(this,de))})})},Ze);function Re(l,e){const s=tt(),[a]=c.useState(()=>new ns(s,l));c.useEffect(()=>{a.setOptions(l)},[a,l]);const i=c.useSyncExternalStore(c.useCallback(f=>a.subscribe(et.batchCalls(f)),[a]),()=>a.getCurrentResult(),()=>a.getCurrentResult()),u=c.useCallback((...f)=>{a.mutate(f[0],f[1]).catch(jt)},[a]);if(i.error&&yt(a.options.throwOnError,[i.error]))throw i.error;return{...i,mutate:u,mutateAsync:i.mutate}}/**
|
|
2
|
+
* @license lucide-react v1.48.0 - ISC
|
|
3
3
|
*
|
|
4
4
|
* This source code is licensed under the ISC license.
|
|
5
5
|
* See the LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/const it={name:"ban",size:24,node:[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M4.929 4.929 19.07 19.071",key:"196cmz"}]]};it.node;const rs=ve(it);/**
|
|
7
|
-
* @license lucide-react v1.
|
|
7
|
+
* @license lucide-react v1.48.0 - ISC
|
|
8
8
|
*
|
|
9
9
|
* This source code is licensed under the ISC license.
|
|
10
10
|
* See the LICENSE file in the root directory of this source tree.
|
|
11
11
|
*/const ot={name:"layers",size:24,node:[["path",{d:"M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z",key:"zw3jo"}],["path",{d:"M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12",key:"1wduqc"}],["path",{d:"M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17",key:"kqbvx6"}]],aliases:["layers-3"]};ot.node;const is=ve(ot);/**
|
|
12
|
-
* @license lucide-react v1.
|
|
12
|
+
* @license lucide-react v1.48.0 - ISC
|
|
13
13
|
*
|
|
14
14
|
* This source code is licensed under the ISC license.
|
|
15
15
|
* See the LICENSE file in the root directory of this source tree.
|
|
16
16
|
*/const ct={name:"package",size:24,node:[["path",{d:"M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z",key:"1a0edw"}],["path",{d:"M12 22V12",key:"d0xqtd"}],["polyline",{points:"3.29 7 12 12 20.71 7",key:"ousv84"}],["path",{d:"m7.5 4.27 9 5.15",key:"1c824w"}]]};ct.node;const os=ve(ct);/**
|
|
17
|
-
* @license lucide-react v1.
|
|
17
|
+
* @license lucide-react v1.48.0 - ISC
|
|
18
18
|
*
|
|
19
19
|
* This source code is licensed under the ISC license.
|
|
20
20
|
* See the LICENSE file in the root directory of this source tree.
|
|
21
21
|
*/const dt={name:"timer-off",size:24,node:[["path",{d:"M10 2h4",key:"n1abiw"}],["path",{d:"M4.6 11a8 8 0 0 0 1.7 8.7 8 8 0 0 0 8.7 1.7",key:"10he05"}],["path",{d:"M7.4 7.4a8 8 0 0 1 10.3 1 8 8 0 0 1 .9 10.2",key:"15f7sh"}],["path",{d:"m2 2 20 20",key:"1ooewy"}],["path",{d:"M12 12v-2",key:"fwoke6"}]]};dt.node;const cs=ve(dt);/**
|
|
22
|
-
* @license lucide-react v1.
|
|
22
|
+
* @license lucide-react v1.48.0 - ISC
|
|
23
23
|
*
|
|
24
24
|
* This source code is licensed under the ISC license.
|
|
25
25
|
* See the LICENSE file in the root directory of this source tree.
|
|
26
|
-
*/const ut={name:"upload",size:24,node:[["path",{d:"M12 3v12",key:"1x0j5s"}],["path",{d:"m17 8-5-5-5 5",key:"7q97r8"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}]]};ut.node;const Je=ve(ut);function ds(l){const e=pe.c(37),{onSuccess:s,appId:a,canWrite:i}=l,[u,f]=c.useState(!1),[j,b]=c.useState(null),[m,d]=c.useState("none"),y=c.useRef(null);let p;e[0]!==a||e[1]!==j||e[2]!==s||e[3]!==m?(p=function(D){if(D.preventDefault(),!j)return;const O=new FormData;O.append("file",j),m!=="none"&&O.append("status",m),a&&O.append("app_id",String(a));const Z=fetch("/api/v1/builds",{method:"POST",credentials:"include",body:O}).then(async V=>{if(!V.ok){const ue=await V.json().catch(ms);throw new Error(ue.error??"Upload failed. Check the file format.")}s()});J.promise(Z,{loading:"Uploading build…",success:"Build uploaded",error:fs}),f(!1),b(null),d("none")},e[0]=a,e[1]=j,e[2]=s,e[3]=m,e[4]=p):p=e[4];const r=p;let N;e[5]===Symbol.for("react.memo_cache_sentinel")?(N=function(D){f(D),D||(b(null),d("none"))},e[5]=N):N=e[5];const v=N;if(!i){let w;return e[6]===Symbol.for("react.memo_cache_sentinel")?(w=t.jsxs(re,{size:"sm",onClick:hs,children:[t.jsx(Je,{className:"mr-2 h-4 w-4"}),"Upload build"]}),e[6]=w):w=e[6],w}let g;e[7]===Symbol.for("react.memo_cache_sentinel")?(g=t.jsx(st,{asChild:!0,children:t.jsxs(re,{size:"sm",children:[t.jsx(Je,{className:"mr-2 h-4 w-4"}),"Upload build"]})}),e[7]=g):g=e[7];let C;e[8]===Symbol.for("react.memo_cache_sentinel")?(C=t.jsx(at,{children:t.jsx(lt,{children:"Upload build"})}),e[8]=C):C=e[8];let E;e[9]===Symbol.for("react.memo_cache_sentinel")?(E=t.jsx(ye,{children:"File"}),e[9]=E):E=e[9];let x;e[10]===Symbol.for("react.memo_cache_sentinel")?(x=()=>{var w;return(w=y.current)==null?void 0:w.click()},e[10]=x):x=e[10];let _;e[11]===Symbol.for("react.memo_cache_sentinel")?(_=w=>{w.preventDefault();const D=w.dataTransfer.files[0];D&&b(D)},e[11]=_):_=e[11];const P=j?j.name:"Click or drag to upload";let T;e[12]!==P?(T=t.jsx("div",{className:"flex h-24 cursor-pointer items-center justify-center rounded-md border-2 border-dashed text-sm text-muted-foreground hover:border-primary",onClick:x,onDragOver:us,onDrop:_,children:P}),e[12]=P,e[13]=T):T=e[13];let B;e[14]===Symbol.for("react.memo_cache_sentinel")?(B=t.jsx("input",{ref:y,type:"file",accept:".zip,.apk,.tgz,.tar.gz,application/gzip,application/x-gzip",className:"hidden",onChange:w=>{var D;return b(((D=w.target.files)==null?void 0:D[0])??null)}}),e[14]=B):B=e[14];let L;e[15]===Symbol.for("react.memo_cache_sentinel")?(L=t.jsx("code",{children:".app.zip"}),e[15]=L):L=e[15];let G;e[16]===Symbol.for("react.memo_cache_sentinel")?(G=t.jsx("code",{children:".tar.gz"}),e[16]=G):G=e[16];let X;e[17]===Symbol.for("react.memo_cache_sentinel")?(X=t.jsx("code",{children:".tgz"}),e[17]=X):X=e[17];let H;e[18]===Symbol.for("react.memo_cache_sentinel")?(H=t.jsxs("p",{className:"text-xs text-muted-foreground",children:["iOS: ",L," or ",G," / ",X," (e.g. an EAS simulator build) · Android: ",t.jsx("code",{children:".apk"})]}),e[18]=H):H=e[18];let q;e[19]!==T?(q=t.jsxs("div",{className:"grid gap-2",children:[E,T,B,H]}),e[19]=T,e[20]=q):q=e[20];let Q;e[21]===Symbol.for("react.memo_cache_sentinel")?(Q=t.jsxs(ye,{children:["Status ",t.jsx("span",{className:"text-muted-foreground",children:"(optional)"})]}),e[21]=Q):Q=e[21];let Y;e[22]===Symbol.for("react.memo_cache_sentinel")?(Y=t.jsx(ke,{children:t.jsx(Ce,{})}),e[22]=Y):Y=e[22];let K;e[23]===Symbol.for("react.memo_cache_sentinel")?(K=t.jsxs(_e,{children:[t.jsx(F,{value:"none",children:"None"}),t.jsx(F,{value:"Backlog",children:"Backlog"}),t.jsx(F,{value:"In Progress",children:"In Progress"}),t.jsx(F,{value:"Done",children:"Done"}),t.jsx(F,{value:"Rejected",children:"Rejected"})]}),e[23]=K):K=e[23];let z;e[24]!==m?(z=t.jsxs("div",{className:"grid gap-2",children:[Q,t.jsxs(Ae,{value:m,onValueChange:d,children:[Y,K]})]}),e[24]=m,e[25]=z):z=e[25];let U;e[26]===Symbol.for("react.memo_cache_sentinel")?(U=t.jsx(re,{type:"button",variant:"outline",className:"w-24",onClick:()=>f(!1),children:"Cancel"}),e[26]=U):U=e[26];const W=!j;let A;e[27]!==W?(A=t.jsxs("div",{className:"flex justify-end gap-2",children:[U,t.jsx(re,{type:"submit",className:"w-24",disabled:W,children:"Upload"})]}),e[27]=W,e[28]=A):A=e[28];let $;e[29]!==r||e[30]!==q||e[31]!==z||e[32]!==A?($=t.jsxs(nt,{className:"sm:max-w-md",children:[C,t.jsxs("form",{onSubmit:r,className:"flex flex-col gap-4 pt-2",children:[q,z,A]})]}),e[29]=r,e[30]=q,e[31]=z,e[32]=A,e[33]=$):$=e[33];let R;return e[34]!==u||e[35]!==$?(R=t.jsxs(rt,{open:u,onOpenChange:v,children:[g,$]}),e[34]=u,e[35]=$,e[36]=R):R=e[36],R}function us(l){return l.preventDefault()}function hs(){return J.error("Viewers can't upload builds. Ask an Admin for QA or Developer access.")}function fs(l){return l instanceof Error?l.message:"Failed to upload build"}function ms(){return{}}function De(l){const e=pe.c(8);let s,a;e[0]!==l?({className:s,...a}=l,e[0]=l,e[1]=s,e[2]=a):(s=e[1],a=e[2]);let i;e[3]!==s?(i=vt("font-mono text-xs tabular-nums tracking-tight",s),e[3]=s,e[4]=i):i=e[4];let u;return e[5]!==a||e[6]!==i?(u=t.jsx("span",{className:i,...a}),e[5]=a,e[6]=i,e[7]=u):u=e[7],u}function ps({onSuccess:l,canWrite:e}){const[s,a]=c.useState(!1),[i,u]=c.useState(""),[f,j]=c.useState(""),[b,m]=c.useState("ios"),[d,y]=c.useState(""),[p,r]=c.useState(!1);async function N(g){g.preventDefault(),y(""),r(!0);try{const C=await Nt({name:i.trim(),bundle_id_key:f.trim(),platform:b});if(!C){y("Failed to create app");return}if("error"in C){y(C.error);return}a(!1),u(""),j(""),m("ios"),J.success("App created"),l()}catch{y("Failed to create app — check your network"),J.error("Failed to create app — check your network")}finally{r(!1)}}const v=g=>t.jsxs("button",{type:"button",onClick:g,className:"flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-left text-sm text-muted-foreground hover:bg-accent hover:text-foreground transition-colors",children:[t.jsx(Vt,{className:"size-3.5 shrink-0"}),t.jsx("span",{children:"Add App"})]});return e?t.jsxs(rt,{open:s,onOpenChange:a,children:[t.jsx(st,{asChild:!0,children:v()}),t.jsxs(nt,{className:"sm:max-w-sm",children:[t.jsx(at,{children:t.jsx(lt,{children:"Add App"})}),t.jsxs("form",{onSubmit:N,className:"flex flex-col gap-4 pt-2",children:[t.jsxs("div",{className:"flex flex-col gap-1.5",children:[t.jsx(ye,{htmlFor:"app-name",children:"Name"}),t.jsx(He,{id:"app-name",value:i,onChange:g=>u(g.target.value),placeholder:"My App",required:!0})]}),t.jsxs("div",{className:"flex flex-col gap-1.5",children:[t.jsx(ye,{htmlFor:"bundle-id",children:"Bundle ID"}),t.jsx(He,{id:"bundle-id",value:f,onChange:g=>j(g.target.value),placeholder:"com.example.myapp",required:!0})]}),t.jsxs("div",{className:"flex flex-col gap-1.5",children:[t.jsx(ye,{htmlFor:"platform",children:"Platform"}),t.jsxs(Ae,{value:b,onValueChange:g=>m(g),children:[t.jsx(ke,{id:"platform",children:t.jsx(Ce,{})}),t.jsxs(_e,{children:[t.jsx(F,{value:"ios",children:"iOS"}),t.jsx(F,{value:"android",children:"Android"}),t.jsx(F,{value:"both",children:"Both"})]})]})]}),t.jsx(St,{assertive:!0,id:"add-app-error",message:d||void 0}),t.jsx(re,{type:"submit",disabled:p,className:"w-full",children:p?"Creating…":"Create App"})]})]})]}):v(()=>J.error("Viewers can't add apps. Ask an Admin for QA or Developer access."))}function xs(l){const e=pe.c(17),{apps:s,selectedAppId:a,onSelect:i,onAdd:u,canWrite:f}=l;let j;e[0]===Symbol.for("react.memo_cache_sentinel")?(j=t.jsx("span",{className:"px-2 pb-1 font-mono text-xs font-medium text-muted-foreground uppercase tracking-wider",children:"Apps"}),e[0]=j):j=e[0];let b;e[1]!==s.length?(b=s.length===0&&t.jsx("span",{className:"px-2 text-sm text-muted-foreground",children:"No apps yet"}),e[1]=s.length,e[2]=b):b=e[2];let m;if(e[3]!==s||e[4]!==i||e[5]!==a){let p;e[7]!==i||e[8]!==a?(p=r=>t.jsxs("button",{onClick:()=>i(r.id),className:["flex flex-col gap-0.5 rounded-md px-2 py-2 text-left text-sm hover:bg-accent overflow-hidden",r.id===a?"bg-accent font-medium":""].join(" "),children:[t.jsx("span",{className:"truncate",children:r.name}),t.jsx(De,{className:"text-muted-foreground truncate",children:r.bundle_id_key})]},r.id),e[7]=i,e[8]=a,e[9]=p):p=e[9],m=s.map(p),e[3]=s,e[4]=i,e[5]=a,e[6]=m}else m=e[6];let d;e[10]!==f||e[11]!==u?(d=t.jsx("div",{className:"mt-1 border-t pt-1",children:t.jsx(ps,{onSuccess:u,canWrite:f})}),e[10]=f,e[11]=u,e[12]=d):d=e[12];let y;return e[13]!==b||e[14]!==m||e[15]!==d?(y=t.jsxs("aside",{className:"w-64 shrink-0 border-r flex flex-col gap-1 p-3 overflow-y-auto",children:[j,b,m,d]}),e[13]=b,e[14]=m,e[15]=d,e[16]=y):y=e[16],y}function gs(l){const e=pe.c(70),{build:s,isLast:a,onNavigate:i,onStatusChange:u,onScheduleDeletion:f,onCancelDeletion:j,canWrite:b,statusDescribedBy:m}=l,[d,y]=c.useState(!1),p=c.useRef(null),r=s.status_label==="Done";let N;e[0]!==s.delete_after?(N=s.delete_after?zt(s.delete_after):null,e[0]=s.delete_after,e[1]=N):N=e[1];const v=N;let g;e[2]!==s?(g=Fe(s),e[2]=s,e[3]=g):g=e[3];const C=g;let E;e[4]!==s.id||e[5]!==u?(E=function(ce){u(s.id,ce==="none"?null:ce)},e[4]=s.id,e[5]=u,e[6]=E):E=e[6];const x=E;let _;e[7]===Symbol.for("react.memo_cache_sentinel")?(_=xe=>{var ce;xe.preventDefault(),(ce=p.current)==null||ce.focus()},e[7]=_):_=e[7];let P;e[8]===Symbol.for("react.memo_cache_sentinel")?(P=t.jsxs(Wt,{children:[t.jsx(Jt,{children:"Schedule deletion?"}),t.jsx(Gt,{children:"Build files will be deleted after 7 days. You can cancel the scheduled deletion anytime before then."})]}),e[8]=P):P=e[8];let T;e[9]===Symbol.for("react.memo_cache_sentinel")?(T=t.jsx(Xt,{children:"Cancel"}),e[9]=T):T=e[9];let B;e[10]!==s.id||e[11]!==f?(B=t.jsxs(Yt,{onCloseAutoFocus:_,children:[P,t.jsxs(Zt,{children:[T,t.jsx(es,{onClick:()=>{f(s.id),y(!1)},children:"Schedule deletion"})]})]}),e[10]=s.id,e[11]=f,e[12]=B):B=e[12];let L;e[13]!==d||e[14]!==B?(L=t.jsx(ts,{open:d,onOpenChange:y,children:B}),e[13]=d,e[14]=B,e[15]=L):L=e[15];const G=a?"":"border-b";let X;e[16]!==G?(X=["flex items-center gap-3 px-4 py-3",G],e[16]=G,e[17]=X):X=e[17];const H=X.join(" "),q=s.build_number??"—";let Q;e[18]!==q?(Q=t.jsxs("span",{className:"text-sm font-medium",children:["build ",t.jsx(De,{children:q})]}),e[18]=q,e[19]=Q):Q=e[19];const Y=s.platform==="ios"?"ios":"android";let K;e[20]!==s.platform||e[21]!==Y?(K=t.jsx(Oe,{tone:Y,className:"text-xs capitalize",children:s.platform}),e[20]=s.platform,e[21]=Y,e[22]=K):K=e[22];let z;e[23]!==s.status_label?(z=s.status_label&&t.jsx(Oe,{tone:Lt[s.status_label],className:"text-xs",children:s.status_label}),e[23]=s.status_label,e[24]=z):z=e[24];let U;e[25]!==v?(U=v&&t.jsx(Oe,{tone:v.urgent?"rejected":"backlog",className:"text-xs",children:v.label}),e[25]=v,e[26]=U):U=e[26];let W;e[27]!==Q||e[28]!==K||e[29]!==z||e[30]!==U?(W=t.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[Q,K,z,U]}),e[27]=Q,e[28]=K,e[29]=z,e[30]=U,e[31]=W):W=e[31];let A;e[32]===Symbol.for("react.memo_cache_sentinel")?(A={display:"flex",flexDirection:"row",gap:"14px",marginTop:"0.7rem",alignItems:"center"},e[32]=A):A=e[32];let $;e[33]!==s.uploader?($=s.uploader&&t.jsxs(t.Fragment,{children:[t.jsx("span",{children:s.uploader}),t.jsx(wt,{orientation:"vertical",className:"h-3"})]}),e[33]=s.uploader,e[34]=$):$=e[34];let R;e[35]!==s.uploaded_at?(R=new Date(s.uploaded_at).toLocaleDateString(),e[35]=s.uploaded_at,e[36]=R):R=e[36];let w;e[37]!==R?(w=t.jsx(De,{children:R}),e[37]=R,e[38]=w):w=e[38];let D;e[39]!==$||e[40]!==w?(D=t.jsxs("div",{style:A,className:"text-xs text-muted-foreground",children:[$,w]}),e[39]=$,e[40]=w,e[41]=D):D=e[41];let O;e[42]!==W||e[43]!==D?(O=t.jsxs("div",{className:"flex-1 min-w-0",children:[W,D]}),e[42]=W,e[43]=D,e[44]=O):O=e[44];let Z;e[45]!==s.id||e[46]!==s.status_label||e[47]!==b||e[48]!==v||e[49]!==x||e[50]!==j||e[51]!==C||e[52]!==m?(Z=b&&t.jsxs(t.Fragment,{children:[t.jsxs(Ae,{value:s.status_label??"none",onValueChange:x,children:[t.jsx(ke,{className:"h-9 w-32 text-xs","aria-label":`Status for ${C}`,"aria-describedby":m,"data-status-trigger":s.id,children:t.jsx(Ce,{})}),t.jsxs(_e,{children:[t.jsx(F,{value:"none",children:t.jsx("span",{className:"text-muted-foreground",children:"—"})}),t.jsx(F,{value:"Backlog",children:"Backlog"}),t.jsx(F,{value:"In Progress",children:"In Progress"}),t.jsx(F,{value:"Done",children:"Done"}),t.jsx(F,{value:"Rejected",children:"Rejected"})]})]}),t.jsx(Dt,{delayDuration:400,children:t.jsxs(kt,{children:[t.jsx(Ct,{asChild:!0,children:v?t.jsx(re,{ref:p,size:"icon-sm",variant:"outline",onClick:()=>j(s.id),"aria-label":`Cancel scheduled deletion of ${C}`,children:t.jsx(cs,{className:"h-3.5 w-3.5","aria-hidden":!0})}):t.jsx(re,{ref:p,size:"icon-sm",variant:"destructive",onClick:()=>y(!0),"aria-label":`Schedule deletion of ${C}`,children:t.jsx(Ht,{className:"h-3.5 w-3.5","aria-hidden":!0})})}),t.jsx(_t,{children:v?"Cancel scheduled deletion":"Schedule deletion"})]})})]}),e[45]=s.id,e[46]=s.status_label,e[47]=b,e[48]=v,e[49]=x,e[50]=j,e[51]=C,e[52]=m,e[53]=Z):Z=e[53];let V;e[54]!==s.id||e[55]!==i?(V=()=>i(s.id),e[54]=s.id,e[55]=i,e[56]=V):V=e[56];const ue=`Start QA on ${C}`;let ie;e[57]===Symbol.for("react.memo_cache_sentinel")?(ie=t.jsx(qt,{className:"mr-1.5 h-3.5 w-3.5"}),e[57]=ie):ie=e[57];let oe;e[58]!==r||e[59]!==V||e[60]!==ue?(oe=t.jsxs(re,{size:"sm",onClick:V,disabled:r,"aria-label":ue,children:[ie,"Start QA"]}),e[58]=r,e[59]=V,e[60]=ue,e[61]=oe):oe=e[61];let ae;e[62]!==H||e[63]!==O||e[64]!==Z||e[65]!==oe?(ae=t.jsxs("div",{className:H,children:[O,Z,oe]}),e[62]=H,e[63]=O,e[64]=Z,e[65]=oe,e[66]=ae):ae=e[66];let te;return e[67]!==ae||e[68]!==L?(te=t.jsxs(t.Fragment,{children:[L,ae]}),e[67]=ae,e[68]=L,e[69]=te):te=e[69],te}function bs(l){const e=pe.c(29),{versionName:s,builds:a,isOpen:i,onToggle:u,onNavigate:f,onStatusChange:j,onScheduleDeletion:b,onCancelDeletion:m,canWrite:d,describedBy:y,rowNote:p}=l,r=c.useId(),N=i?r:void 0;let v;e[0]!==i?(v=i?t.jsx(Ut,{className:"h-4 w-4 shrink-0 text-muted-foreground"}):t.jsx(At,{className:"h-4 w-4 shrink-0 text-muted-foreground"}),e[0]=i,e[1]=v):v=e[1];let g;e[2]!==s?(g=t.jsx(De,{className:"font-medium text-sm text-foreground",children:s}),e[2]=s,e[3]=g):g=e[3];const C=a.length>1?"s":"";let E;e[4]!==a.length||e[5]!==C?(E=t.jsxs("span",{className:"text-xs text-muted-foreground",children:["(",a.length," build",C,")"]}),e[4]=a.length,e[5]=C,e[6]=E):E=e[6];let x;e[7]!==y||e[8]!==i||e[9]!==u||e[10]!==N||e[11]!==v||e[12]!==g||e[13]!==E||e[14]!==s?(x=t.jsx("h2",{children:t.jsxs("button",{className:"flex w-full items-center gap-2 px-4 py-3 text-left hover:bg-accent/50 bg-card focus-visible:outline-none focus-visible:outline-offset-[-2px] focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",onClick:u,"aria-expanded":i,"aria-controls":N,"aria-describedby":y,"data-release-header":s,children:[v,g,E]})}),e[7]=y,e[8]=i,e[9]=u,e[10]=N,e[11]=v,e[12]=g,e[13]=E,e[14]=s,e[15]=x):x=e[15];let _;e[16]!==a||e[17]!==d||e[18]!==i||e[19]!==m||e[20]!==f||e[21]!==b||e[22]!==j||e[23]!==r||e[24]!==p?(_=i&&t.jsx("div",{id:r,className:"border-t bg-card",children:a.map((T,B)=>t.jsx(gs,{build:T,isLast:B===a.length-1,onNavigate:f,onStatusChange:j,onScheduleDeletion:b,onCancelDeletion:m,canWrite:d,statusDescribedBy:(p==null?void 0:p.buildId)===T.id?p.id:void 0},T.id))}),e[16]=a,e[17]=d,e[18]=i,e[19]=m,e[20]=f,e[21]=b,e[22]=j,e[23]=r,e[24]=p,e[25]=_):_=e[25];let P;return e[26]!==x||e[27]!==_?(P=t.jsxs("div",{className:"rounded-lg shadow-card-2 overflow-hidden",children:[x,_]}),e[26]=x,e[27]=_,e[28]=P):P=e[28],P}const ht="tapflow-app-center-releases:";function js(l){try{const e=JSON.parse(localStorage.getItem(ht+l)??"[]");return Array.isArray(e)?e.filter(s=>Array.isArray(s)&&s.length===2&&typeof s[0]=="string"&&typeof s[1]=="boolean"):[]}catch{return[]}}function ys(l,e){try{localStorage.setItem(ht+l,JSON.stringify(e))}catch{}}function vs(l,e){const s=pe.c(13),[a,i]=c.useState(Ss);let u;s[0]!==l||s[1]!==a?(u=l===null?[]:a.get(l)??js(l),s[0]=l,s[1]=a,s[2]=u):u=s[2];const f=u,j=e[0];let b;s[3]!==j||s[4]!==f?(b=r=>{var N;return((N=f.find(v=>{const[g]=v;return g===r}))==null?void 0:N[1])??r===j},s[3]=j,s[4]=f,s[5]=b):b=s[5];const m=b;let d;s[6]!==l||s[7]!==m||s[8]!==f?(d=r=>{if(l===null)return;const N=[...f.filter(v=>{const[g]=v;return g!==r}),[r,!m(r)]].slice(-200);ys(l,N),i(v=>new Map(v).set(l,N))},s[6]=l,s[7]=m,s[8]=f,s[9]=d):d=s[9];const y=d;let p;return s[10]!==m||s[11]!==y?(p={isOpen:m,toggle:y},s[10]=m,s[11]=y,s[12]=p):p=s[12],p}function Ss(){return new Map}const Ne=l=>`row:${l}`,we=l=>`release:${l}`;function Ge(l){return l instanceof HTMLElement?l.dataset.statusTrigger!==void 0?Ne(Number(l.dataset.statusTrigger)):l.dataset.releaseHeader!==void 0?we(l.dataset.releaseHeader):null:null}function Xe(l,e){const s=new Set(JSON.parse(e));return l.find(a=>s.has(a))??null}function Ye(l,e){return l.map((s,a)=>({item:s,d:a>e?2*(a-e)-1:2*(e-a)})).filter(({d:s})=>s>0).sort((s,a)=>s.d-a.d).map(({item:s})=>s)}function Rs(){var Qe;const l=as(),[e,s]=ls(),a=tt(),{user:i}=It(),u=(i==null?void 0:i.role)!=="Viewer",[f,j]=c.useState(""),[b,m]=c.useState(""),[d,y]=c.useState("all");c.useEffect(()=>{const n=setTimeout(()=>m(f),250);return()=>clearTimeout(n)},[f]);const p=e.get("appId"),r=p!==null&&/^[1-9][0-9]*$/.test(p)?Number(p):null,N=We({queryKey:["apps"],queryFn:Mt}),v=N.data??[],g=v.find(n=>n.id===r)??null,C=["builds",r,b,d],E=JSON.stringify(C),x=We({queryKey:C,queryFn:()=>Bt({appId:r,search:b,statusFilter:d}),enabled:r!==null,placeholderData:Pt}),_=x.data??[];c.useEffect(()=>{!N.data||e.get("appId")||N.data.length===0||s({appId:String(N.data[0].id)},{replace:!0})},[N.data,e,s]);const[P,T]=c.useState(null),[B,L]=c.useState(!1);c.useEffect(()=>{x.isRefetchError&&J.error("Couldn't refresh builds — showing the last list",{id:"builds:refresh"})},[x.isRefetchError]);const G=c.useRef(null);c.useEffect(()=>{r===null||x.isPlaceholderData||!x.data||G.current!==r&&(G.current=r,T(r))},[r,x.isPlaceholderData,x.data]);function X(n){s({appId:String(n)})}function H(n,h){return{onMutate:async o=>{const S=C;await a.cancelQueries({queryKey:S});const I=a.getQueryData(S);return a.setQueryData(S,ee=>ee&&n(ee,o)),{previous:I,key:S}},onError:(o,S,I)=>{if(I!=null&&I.previous&&a.setQueryData(I.key,I.previous),o instanceof Tt){a.invalidateQueries({queryKey:$t.me}),J.error(o.message);return}J.error(h)},onSettled:()=>{a.invalidateQueries({queryKey:["builds"]})}}}const q=H((n,h)=>n.map(o=>o.id===h.buildId?{...o,status_label:h.status}:o),"Failed to update status"),Q=Re({mutationFn:({buildId:n,status:h})=>Ft(n,h),...q,onError:(n,h,o)=>{var S;q.onError(n,h,o),((S=R.current)==null?void 0:S.leaving)===h.buildId&&(R.current=null,O(null))}}),Y=(n,h)=>{var S;const o=(S=a.getQueryData(n))==null?void 0:S.find(I=>I.id===h);return o?Fe(o):"the build"},K=Re({mutationFn:n=>Rt(n),...H((n,h)=>n.map(o=>o.id===h?{...o,delete_after:new Date().toISOString()}:o),"Failed to schedule deletion"),onSuccess:(n,h,o)=>{o&&(a.setQueryData(o.key,S=>S==null?void 0:S.map(I=>I.id===h?{...I,delete_after:n}:I)),J.success(`Deletion scheduled for ${Y(o.key,h)} — it will be deleted ${Kt(n)}`))}}),z=Re({mutationFn:n=>Ot(n),...H((n,h)=>n.map(o=>o.id===h?{...o,delete_after:null}:o),"Failed to cancel scheduled deletion"),onSuccess:(n,h,o)=>{J.success(`Scheduled deletion cancelled for ${o?Y(o.key,h):"the build"}`)}}),U=n=>K.mutate(n),W=n=>z.mutate(n),A=Et(_),$=vs(((Qe=_[0])==null?void 0:Qe.app_id)??null,A.map(n=>n.versionName)),R=c.useRef(null),w=c.useRef(null),[D,O]=c.useState(null),Z=c.useRef(null),V=c.useId();function ue(n){const h=A.findIndex(S=>S.builds.some(I=>I.id===n));if(h<0)return[];const o=A[h].builds;return[...Ye(o,o.findIndex(S=>S.id===n)).map(S=>Ne(S.id)),...Ye(A,h).map(S=>we(S.versionName))]}const ie=JSON.stringify(A.flatMap(n=>[we(n.versionName),...$.isOpen(n.versionName)?n.builds.map(h=>Ne(h.id)):[]])),oe=(n,h)=>{const o=_.find(ee=>ee.id===n),I=d!=="all"&&h!==d?ue(n):[];if(o&&I.length>0){const ee=`${Fe(o)} was set to ${h??"no status"}, so the ${d} filter no longer shows it.`,Ke={leaving:n,candidates:I,text:ee,key:E};R.current=Ke,w.current=null,O(Ke)}Q.mutate({buildId:n,status:h})},ae=D!==null&&!_.some(n=>n.id===D.leaving),te=ae?Xe(D.candidates,ie):null,xe=x.data,ce=x.isRefetchError;c.useLayoutEffect(()=>{var I;const n=R.current;if(!n)return;if(n.key!==E||ce){R.current=null,O(null);return}if((xe??[]).some(ee=>ee.id===n.leaving))return;R.current=null;const h=Xe(n.candidates,ie);if(!h||document.activeElement!==null&&document.activeElement!==document.body){J.success(n.text),O(null);return}const o=((I=Z.current)==null?void 0:I.querySelectorAll("[data-status-trigger], [data-release-header]"))??[],S=Array.from(o).find(ee=>Ge(ee)===h);S instanceof HTMLElement?(w.current=h,S.focus()):(J.success(n.text),O(null))},[E,ce,xe,ie]),c.useEffect(()=>{if(!D)return;const n=h=>{const o=Ge(h.target);if(w.current!==null){o!==w.current&&O(null);return}o!==Ne(D.leaving)&&(o===null||!D.candidates.includes(o))&&O(null)};return document.addEventListener("focusin",n),()=>document.removeEventListener("focusin",n)},[D]);const ge=(g==null?void 0:g.name)??"this app",Ie=b!==""||d!=="all",$e=x.isPlaceholderData&&P!==r,ft=x.isFetchedAfterMount&&(x.isPlaceholderData||x.isLoading),be=B||ft,Se=x.isLoadingError||be?"error":x.isLoading||A.length===0&&$e?"loading":A.length===0?"empty":"list",Me=c.useRef(null),mt=ss(Se,Me),Pe=c.useId(),Be=c.useId(),ze=c.useId(),Le=c.useId(),qe=c.useId(),pt=r===null?"":x.isLoadingError?`Couldn't load builds for ${ge}`:x.isLoading||$e||be?`Loading builds for ${ge}`:A.length>0?`Showing ${_.length} build${_.length===1?"":"s"} for ${ge}`:Ie?`No builds match the current filters for ${ge}`:`No builds for ${ge}`;return t.jsxs("div",{className:"flex h-full gap-0",children:[t.jsx(xs,{apps:v,selectedAppId:r,onSelect:X,onAdd:()=>a.invalidateQueries({queryKey:["apps"]}),canWrite:u}),t.jsxs("div",{className:"flex-1 flex flex-col gap-4 p-4 overflow-y-auto min-w-0",children:[t.jsxs("div",{className:"flex items-center justify-between flex-wrap gap-2",children:[t.jsx("h1",{className:"text-xl font-semibold tracking-display-sm",children:g?g.name:"App Center"}),t.jsx(ds,{onSuccess:()=>{a.invalidateQueries({queryKey:["apps"]}),a.invalidateQueries({queryKey:["builds"]})},appId:r,canWrite:u})]}),t.jsx("p",{id:qe,role:"status",className:"sr-only",children:pt}),r===null?t.jsxs("div",{className:"flex-1 flex flex-col items-center justify-center gap-2 text-center",children:[t.jsx(is,{className:"w-8 h-8 text-muted-foreground/40"}),t.jsx("p",{className:"text-sm font-medium",children:"No app selected"}),t.jsx("p",{className:"text-sm text-muted-foreground",children:"Choose an app from the sidebar to view its builds."})]}):t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"flex flex-wrap gap-2",children:[t.jsx(Qt,{ref:Me,"aria-describedby":`${Le} ${ze}`,"aria-label":"Search versions",placeholder:"Search version…",value:f,onChange:j}),t.jsxs(Ae,{value:d,onValueChange:y,children:[t.jsx(ke,{className:"h-8 w-36","aria-label":"Filter by status",children:t.jsx(Ce,{placeholder:"Status"})}),t.jsxs(_e,{children:[t.jsx(F,{value:"all",children:"All statuses"}),t.jsx(F,{value:"Backlog",children:"Backlog"}),t.jsx(F,{value:"In Progress",children:"In Progress"}),t.jsx(F,{value:"Done",children:"Done"}),t.jsx(F,{value:"Rejected",children:"Rejected"})]})]}),x.isRefetchError&&t.jsx("p",{className:"text-sm text-muted-foreground self-center",children:"Showing the last list — refresh failed."})]}),t.jsx("div",{...mt,className:"contents",children:Se==="error"?t.jsxs("div",{className:"flex-1 flex flex-col items-center justify-center gap-2 text-center",children:[t.jsx(rs,{className:"w-8 h-8 text-muted-foreground/40"}),t.jsx("p",{id:Pe,className:"text-sm font-medium",children:"Couldn't load builds"}),t.jsx("p",{id:Be,className:"text-sm text-muted-foreground",children:"Check that the relay is reachable."}),t.jsx(re,{variant:"outline",size:"sm",className:"mt-1 aria-disabled:opacity-50","aria-describedby":`${Pe} ${Be}`,"aria-disabled":be,onClick:()=>{be||(L(!0),x.refetch().finally(()=>L(!1)))},children:be?"Trying…":"Try again"})]}):Se==="loading"?t.jsx("p",{id:Le,className:"text-sm text-muted-foreground",children:"Loading…"}):Se==="empty"?t.jsxs("div",{className:"flex-1 flex flex-col items-center justify-center gap-2 text-center",children:[t.jsx(os,{className:"w-8 h-8 text-muted-foreground/40"}),t.jsx("p",{id:ze,className:"text-sm font-medium",children:Ie?"No matching builds":"No builds yet"}),t.jsx("p",{className:"text-sm text-muted-foreground",children:Ie?"No build matches the search and status filters.":u?"Upload the first build to get started.":"Builds appear here once a teammate uploads one."})]}):t.jsxs("div",{ref:Z,className:"flex flex-col gap-2","aria-busy":x.isPlaceholderData,children:[ae&&t.jsx("p",{id:V,hidden:!0,children:D.text}),A.map(({versionName:n,builds:h},o)=>t.jsx(bs,{describedBy:[o===0&&!x.isPlaceholderData?qe:null,te===we(n)?V:null].filter(Boolean).join(" ")||void 0,rowNote:te!=null&&te.startsWith("row:")?{buildId:Number(te.slice(4)),id:V}:void 0,versionName:n,builds:h,isOpen:$.isOpen(n),onToggle:()=>$.toggle(n),onNavigate:S=>l(`/app-center/build?id=${S}`),onStatusChange:oe,onScheduleDeletion:U,onCancelDeletion:W,canWrite:u},n))]})})]})]})]})}export{Rs as AppCenter};
|
|
26
|
+
*/const ut={name:"upload",size:24,node:[["path",{d:"M12 3v12",key:"1x0j5s"}],["path",{d:"m17 8-5-5-5 5",key:"7q97r8"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}]]};ut.node;const We=ve(ut);function ds(l){const e=pe.c(37),{onSuccess:s,appId:a,canWrite:i}=l,[u,f]=c.useState(!1),[j,b]=c.useState(null),[m,d]=c.useState("none"),y=c.useRef(null);let p;e[0]!==a||e[1]!==j||e[2]!==s||e[3]!==m?(p=function(D){if(D.preventDefault(),!j)return;const E=new FormData;E.append("file",j),m!=="none"&&E.append("status",m),a&&E.append("app_id",String(a));const Z=fetch("/api/v1/builds",{method:"POST",credentials:"include",body:E}).then(async V=>{if(!V.ok){const ue=await V.json().catch(ms);throw new Error(ue.error??"Upload failed. Check the file format.")}s()});W.promise(Z,{loading:"Uploading build…",success:"Build uploaded",error:fs}),f(!1),b(null),d("none")},e[0]=a,e[1]=j,e[2]=s,e[3]=m,e[4]=p):p=e[4];const r=p;let N;e[5]===Symbol.for("react.memo_cache_sentinel")?(N=function(D){f(D),D||(b(null),d("none"))},e[5]=N):N=e[5];const v=N;if(!i){let w;return e[6]===Symbol.for("react.memo_cache_sentinel")?(w=t.jsxs(re,{size:"sm",onClick:hs,children:[t.jsx(We,{className:"mr-2 h-4 w-4"}),"Upload build"]}),e[6]=w):w=e[6],w}let g;e[7]===Symbol.for("react.memo_cache_sentinel")?(g=t.jsx(st,{asChild:!0,children:t.jsxs(re,{size:"sm",children:[t.jsx(We,{className:"mr-2 h-4 w-4"}),"Upload build"]})}),e[7]=g):g=e[7];let k;e[8]===Symbol.for("react.memo_cache_sentinel")?(k=t.jsx(at,{children:t.jsx(lt,{children:"Upload build"})}),e[8]=k):k=e[8];let O;e[9]===Symbol.for("react.memo_cache_sentinel")?(O=t.jsx(ye,{children:"File"}),e[9]=O):O=e[9];let x;e[10]===Symbol.for("react.memo_cache_sentinel")?(x=()=>{var w;return(w=y.current)==null?void 0:w.click()},e[10]=x):x=e[10];let _;e[11]===Symbol.for("react.memo_cache_sentinel")?(_=w=>{w.preventDefault();const D=w.dataTransfer.files[0];D&&b(D)},e[11]=_):_=e[11];const P=j?j.name:"Click or drag to upload";let T;e[12]!==P?(T=t.jsx("div",{className:"flex h-24 cursor-pointer items-center justify-center rounded-md border-2 border-dashed text-sm text-muted-foreground hover:border-primary",onClick:x,onDragOver:us,onDrop:_,children:P}),e[12]=P,e[13]=T):T=e[13];let B;e[14]===Symbol.for("react.memo_cache_sentinel")?(B=t.jsx("input",{ref:y,type:"file",accept:".zip,.apk,.tgz,.tar.gz,application/gzip,application/x-gzip",className:"hidden",onChange:w=>{var D;return b(((D=w.target.files)==null?void 0:D[0])??null)}}),e[14]=B):B=e[14];let L;e[15]===Symbol.for("react.memo_cache_sentinel")?(L=t.jsx("code",{children:".app.zip"}),e[15]=L):L=e[15];let G;e[16]===Symbol.for("react.memo_cache_sentinel")?(G=t.jsx("code",{children:".tar.gz"}),e[16]=G):G=e[16];let X;e[17]===Symbol.for("react.memo_cache_sentinel")?(X=t.jsx("code",{children:".tgz"}),e[17]=X):X=e[17];let H;e[18]===Symbol.for("react.memo_cache_sentinel")?(H=t.jsxs("p",{className:"text-xs text-muted-foreground",children:["iOS: ",L," or ",G," / ",X," (e.g. an EAS simulator build) · Android: ",t.jsx("code",{children:".apk"})]}),e[18]=H):H=e[18];let q;e[19]!==T?(q=t.jsxs("div",{className:"grid gap-2",children:[O,T,B,H]}),e[19]=T,e[20]=q):q=e[20];let K;e[21]===Symbol.for("react.memo_cache_sentinel")?(K=t.jsxs(ye,{children:["Status ",t.jsx("span",{className:"text-muted-foreground",children:"(optional)"})]}),e[21]=K):K=e[21];let Y;e[22]===Symbol.for("react.memo_cache_sentinel")?(Y=t.jsx(Ce,{children:t.jsx(ke,{})}),e[22]=Y):Y=e[22];let Q;e[23]===Symbol.for("react.memo_cache_sentinel")?(Q=t.jsxs(_e,{children:[t.jsx(F,{value:"none",children:"None"}),t.jsx(F,{value:"Backlog",children:"Backlog"}),t.jsx(F,{value:"In Progress",children:"In Progress"}),t.jsx(F,{value:"Done",children:"Done"}),t.jsx(F,{value:"Rejected",children:"Rejected"})]}),e[23]=Q):Q=e[23];let z;e[24]!==m?(z=t.jsxs("div",{className:"grid gap-2",children:[K,t.jsxs(Ae,{value:m,onValueChange:d,children:[Y,Q]})]}),e[24]=m,e[25]=z):z=e[25];let U;e[26]===Symbol.for("react.memo_cache_sentinel")?(U=t.jsx(re,{type:"button",variant:"outline",className:"w-24",onClick:()=>f(!1),children:"Cancel"}),e[26]=U):U=e[26];const J=!j;let A;e[27]!==J?(A=t.jsxs("div",{className:"flex justify-end gap-2",children:[U,t.jsx(re,{type:"submit",className:"w-24",disabled:J,children:"Upload"})]}),e[27]=J,e[28]=A):A=e[28];let $;e[29]!==r||e[30]!==q||e[31]!==z||e[32]!==A?($=t.jsxs(nt,{className:"sm:max-w-md",children:[k,t.jsxs("form",{onSubmit:r,className:"flex flex-col gap-4 pt-2",children:[q,z,A]})]}),e[29]=r,e[30]=q,e[31]=z,e[32]=A,e[33]=$):$=e[33];let R;return e[34]!==u||e[35]!==$?(R=t.jsxs(rt,{open:u,onOpenChange:v,children:[g,$]}),e[34]=u,e[35]=$,e[36]=R):R=e[36],R}function us(l){return l.preventDefault()}function hs(){return W.error("Viewers can't upload builds. Ask an Admin for QA or Developer access.")}function fs(l){return l instanceof Error?l.message:"Failed to upload build"}function ms(){return{}}function De(l){const e=pe.c(8);let s,a;e[0]!==l?({className:s,...a}=l,e[0]=l,e[1]=s,e[2]=a):(s=e[1],a=e[2]);let i;e[3]!==s?(i=vt("font-mono text-xs tabular-nums tracking-tight",s),e[3]=s,e[4]=i):i=e[4];let u;return e[5]!==a||e[6]!==i?(u=t.jsx("span",{className:i,...a}),e[5]=a,e[6]=i,e[7]=u):u=e[7],u}function ps({onSuccess:l,canWrite:e}){const[s,a]=c.useState(!1),[i,u]=c.useState(""),[f,j]=c.useState(""),[b,m]=c.useState("ios"),[d,y]=c.useState(""),[p,r]=c.useState(!1);async function N(g){g.preventDefault(),y(""),r(!0);try{const k=await Nt({name:i.trim(),bundle_id_key:f.trim(),platform:b});if(!k){y("Failed to create app");return}if("error"in k){y(k.error);return}a(!1),u(""),j(""),m("ios"),W.success("App created"),l()}catch{y("Failed to create app — check your network"),W.error("Failed to create app — check your network")}finally{r(!1)}}const v=g=>t.jsxs("button",{type:"button",onClick:g,className:"flex w-full items-center gap-1.5 rounded-md px-2 py-1.5 text-left text-sm text-muted-foreground hover:bg-accent hover:text-foreground transition-colors",children:[t.jsx(Vt,{className:"size-3.5 shrink-0"}),t.jsx("span",{children:"Add App"})]});return e?t.jsxs(rt,{open:s,onOpenChange:a,children:[t.jsx(st,{asChild:!0,children:v()}),t.jsxs(nt,{className:"sm:max-w-sm",children:[t.jsx(at,{children:t.jsx(lt,{children:"Add App"})}),t.jsxs("form",{onSubmit:N,className:"flex flex-col gap-4 pt-2",children:[t.jsxs("div",{className:"flex flex-col gap-1.5",children:[t.jsx(ye,{htmlFor:"app-name",children:"Name"}),t.jsx(He,{id:"app-name",value:i,onChange:g=>u(g.target.value),placeholder:"My App",required:!0})]}),t.jsxs("div",{className:"flex flex-col gap-1.5",children:[t.jsx(ye,{htmlFor:"bundle-id",children:"Bundle ID"}),t.jsx(He,{id:"bundle-id",value:f,onChange:g=>j(g.target.value),placeholder:"com.example.myapp",required:!0})]}),t.jsxs("div",{className:"flex flex-col gap-1.5",children:[t.jsx(ye,{htmlFor:"platform",children:"Platform"}),t.jsxs(Ae,{value:b,onValueChange:g=>m(g),children:[t.jsx(Ce,{id:"platform",children:t.jsx(ke,{})}),t.jsxs(_e,{children:[t.jsx(F,{value:"ios",children:"iOS"}),t.jsx(F,{value:"android",children:"Android"}),t.jsx(F,{value:"both",children:"Both"})]})]})]}),t.jsx(St,{assertive:!0,id:"add-app-error",message:d||void 0}),t.jsx(re,{type:"submit",disabled:p,className:"w-full",children:p?"Creating…":"Create App"})]})]})]}):v(()=>W.error("Viewers can't add apps. Ask an Admin for QA or Developer access."))}function xs(l){const e=pe.c(17),{apps:s,selectedAppId:a,onSelect:i,onAdd:u,canWrite:f}=l;let j;e[0]===Symbol.for("react.memo_cache_sentinel")?(j=t.jsx("span",{className:"px-2 pb-1 font-mono text-xs font-medium text-muted-foreground uppercase tracking-wider",children:"Apps"}),e[0]=j):j=e[0];let b;e[1]!==s.length?(b=s.length===0&&t.jsx("span",{className:"px-2 text-sm text-muted-foreground",children:"No apps yet"}),e[1]=s.length,e[2]=b):b=e[2];let m;if(e[3]!==s||e[4]!==i||e[5]!==a){let p;e[7]!==i||e[8]!==a?(p=r=>t.jsxs("button",{onClick:()=>i(r.id),className:["flex flex-col gap-0.5 rounded-md px-2 py-2 text-left text-sm hover:bg-accent overflow-hidden",r.id===a?"bg-accent font-medium":""].join(" "),children:[t.jsx("span",{className:"truncate",children:r.name}),t.jsx(De,{className:"text-muted-foreground truncate",children:r.bundle_id_key})]},r.id),e[7]=i,e[8]=a,e[9]=p):p=e[9],m=s.map(p),e[3]=s,e[4]=i,e[5]=a,e[6]=m}else m=e[6];let d;e[10]!==f||e[11]!==u?(d=t.jsx("div",{className:"mt-1 border-t pt-1",children:t.jsx(ps,{onSuccess:u,canWrite:f})}),e[10]=f,e[11]=u,e[12]=d):d=e[12];let y;return e[13]!==b||e[14]!==m||e[15]!==d?(y=t.jsxs("aside",{className:"w-64 shrink-0 border-r flex flex-col gap-1 p-3 overflow-y-auto",children:[j,b,m,d]}),e[13]=b,e[14]=m,e[15]=d,e[16]=y):y=e[16],y}function gs(l){const e=pe.c(70),{build:s,isLast:a,onNavigate:i,onStatusChange:u,onScheduleDeletion:f,onCancelDeletion:j,canWrite:b,statusDescribedBy:m}=l,[d,y]=c.useState(!1),p=c.useRef(null),r=s.status_label==="Done";let N;e[0]!==s.delete_after?(N=s.delete_after?zt(s.delete_after):null,e[0]=s.delete_after,e[1]=N):N=e[1];const v=N;let g;e[2]!==s?(g=Fe(s),e[2]=s,e[3]=g):g=e[3];const k=g;let O;e[4]!==s.id||e[5]!==u?(O=function(ce){u(s.id,ce==="none"?null:ce)},e[4]=s.id,e[5]=u,e[6]=O):O=e[6];const x=O;let _;e[7]===Symbol.for("react.memo_cache_sentinel")?(_=xe=>{var ce;xe.preventDefault(),(ce=p.current)==null||ce.focus()},e[7]=_):_=e[7];let P;e[8]===Symbol.for("react.memo_cache_sentinel")?(P=t.jsxs(Jt,{children:[t.jsx(Wt,{children:"Schedule deletion?"}),t.jsx(Gt,{children:"Build files will be deleted after 7 days. You can cancel the scheduled deletion anytime before then."})]}),e[8]=P):P=e[8];let T;e[9]===Symbol.for("react.memo_cache_sentinel")?(T=t.jsx(Xt,{children:"Cancel"}),e[9]=T):T=e[9];let B;e[10]!==s.id||e[11]!==f?(B=t.jsxs(Yt,{onCloseAutoFocus:_,children:[P,t.jsxs(Zt,{children:[T,t.jsx(es,{onClick:()=>{f(s.id),y(!1)},children:"Schedule deletion"})]})]}),e[10]=s.id,e[11]=f,e[12]=B):B=e[12];let L;e[13]!==d||e[14]!==B?(L=t.jsx(ts,{open:d,onOpenChange:y,children:B}),e[13]=d,e[14]=B,e[15]=L):L=e[15];const G=a?"":"border-b";let X;e[16]!==G?(X=["flex items-center gap-3 px-4 py-3",G],e[16]=G,e[17]=X):X=e[17];const H=X.join(" "),q=s.build_number??"—";let K;e[18]!==q?(K=t.jsxs("span",{className:"text-sm font-medium",children:["build ",t.jsx(De,{children:q})]}),e[18]=q,e[19]=K):K=e[19];const Y=s.platform==="ios"?"ios":"android";let Q;e[20]!==s.platform||e[21]!==Y?(Q=t.jsx(Ee,{tone:Y,className:"text-xs capitalize",children:s.platform}),e[20]=s.platform,e[21]=Y,e[22]=Q):Q=e[22];let z;e[23]!==s.status_label?(z=s.status_label&&t.jsx(Ee,{tone:Lt[s.status_label],className:"text-xs",children:s.status_label}),e[23]=s.status_label,e[24]=z):z=e[24];let U;e[25]!==v?(U=v&&t.jsx(Ee,{tone:v.urgent?"rejected":"backlog",className:"text-xs",children:v.label}),e[25]=v,e[26]=U):U=e[26];let J;e[27]!==K||e[28]!==Q||e[29]!==z||e[30]!==U?(J=t.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[K,Q,z,U]}),e[27]=K,e[28]=Q,e[29]=z,e[30]=U,e[31]=J):J=e[31];let A;e[32]===Symbol.for("react.memo_cache_sentinel")?(A={display:"flex",flexDirection:"row",gap:"14px",marginTop:"0.7rem",alignItems:"center"},e[32]=A):A=e[32];let $;e[33]!==s.uploader?($=s.uploader&&t.jsxs(t.Fragment,{children:[t.jsx("span",{children:s.uploader}),t.jsx(wt,{orientation:"vertical",className:"h-3"})]}),e[33]=s.uploader,e[34]=$):$=e[34];let R;e[35]!==s.uploaded_at?(R=new Date(s.uploaded_at).toLocaleDateString(),e[35]=s.uploaded_at,e[36]=R):R=e[36];let w;e[37]!==R?(w=t.jsx(De,{children:R}),e[37]=R,e[38]=w):w=e[38];let D;e[39]!==$||e[40]!==w?(D=t.jsxs("div",{style:A,className:"text-xs text-muted-foreground",children:[$,w]}),e[39]=$,e[40]=w,e[41]=D):D=e[41];let E;e[42]!==J||e[43]!==D?(E=t.jsxs("div",{className:"flex-1 min-w-0",children:[J,D]}),e[42]=J,e[43]=D,e[44]=E):E=e[44];let Z;e[45]!==s.id||e[46]!==s.status_label||e[47]!==b||e[48]!==v||e[49]!==x||e[50]!==j||e[51]!==k||e[52]!==m?(Z=b&&t.jsxs(t.Fragment,{children:[t.jsxs(Ae,{value:s.status_label??"none",onValueChange:x,children:[t.jsx(Ce,{className:"h-9 w-32 text-xs","aria-label":`Status for ${k}`,"aria-describedby":m,"data-status-trigger":s.id,children:t.jsx(ke,{})}),t.jsxs(_e,{children:[t.jsx(F,{value:"none",children:t.jsx("span",{className:"text-muted-foreground",children:"—"})}),t.jsx(F,{value:"Backlog",children:"Backlog"}),t.jsx(F,{value:"In Progress",children:"In Progress"}),t.jsx(F,{value:"Done",children:"Done"}),t.jsx(F,{value:"Rejected",children:"Rejected"})]})]}),t.jsx(Dt,{delayDuration:400,children:t.jsxs(Ct,{children:[t.jsx(kt,{asChild:!0,children:v?t.jsx(re,{ref:p,size:"icon-sm",variant:"outline",onClick:()=>j(s.id),"aria-label":`Cancel scheduled deletion of ${k}`,children:t.jsx(cs,{className:"h-3.5 w-3.5","aria-hidden":!0})}):t.jsx(re,{ref:p,size:"icon-sm",variant:"destructive",onClick:()=>y(!0),"aria-label":`Schedule deletion of ${k}`,children:t.jsx(Ht,{className:"h-3.5 w-3.5","aria-hidden":!0})})}),t.jsx(_t,{children:v?"Cancel scheduled deletion":"Schedule deletion"})]})})]}),e[45]=s.id,e[46]=s.status_label,e[47]=b,e[48]=v,e[49]=x,e[50]=j,e[51]=k,e[52]=m,e[53]=Z):Z=e[53];let V;e[54]!==s.id||e[55]!==i?(V=()=>i(s.id),e[54]=s.id,e[55]=i,e[56]=V):V=e[56];const ue=`Start QA on ${k}`;let ie;e[57]===Symbol.for("react.memo_cache_sentinel")?(ie=t.jsx(qt,{className:"mr-1.5 h-3.5 w-3.5"}),e[57]=ie):ie=e[57];let oe;e[58]!==r||e[59]!==V||e[60]!==ue?(oe=t.jsxs(re,{size:"sm",onClick:V,disabled:r,"aria-label":ue,children:[ie,"Start QA"]}),e[58]=r,e[59]=V,e[60]=ue,e[61]=oe):oe=e[61];let ae;e[62]!==H||e[63]!==E||e[64]!==Z||e[65]!==oe?(ae=t.jsxs("div",{className:H,children:[E,Z,oe]}),e[62]=H,e[63]=E,e[64]=Z,e[65]=oe,e[66]=ae):ae=e[66];let te;return e[67]!==ae||e[68]!==L?(te=t.jsxs(t.Fragment,{children:[L,ae]}),e[67]=ae,e[68]=L,e[69]=te):te=e[69],te}function bs(l){const e=pe.c(29),{versionName:s,builds:a,isOpen:i,onToggle:u,onNavigate:f,onStatusChange:j,onScheduleDeletion:b,onCancelDeletion:m,canWrite:d,describedBy:y,rowNote:p}=l,r=c.useId(),N=i?r:void 0;let v;e[0]!==i?(v=i?t.jsx(Ut,{className:"h-4 w-4 shrink-0 text-muted-foreground"}):t.jsx(At,{className:"h-4 w-4 shrink-0 text-muted-foreground"}),e[0]=i,e[1]=v):v=e[1];let g;e[2]!==s?(g=t.jsx(De,{className:"font-medium text-sm text-foreground",children:s}),e[2]=s,e[3]=g):g=e[3];const k=a.length>1?"s":"";let O;e[4]!==a.length||e[5]!==k?(O=t.jsxs("span",{className:"text-xs text-muted-foreground",children:["(",a.length," build",k,")"]}),e[4]=a.length,e[5]=k,e[6]=O):O=e[6];let x;e[7]!==y||e[8]!==i||e[9]!==u||e[10]!==N||e[11]!==v||e[12]!==g||e[13]!==O||e[14]!==s?(x=t.jsx("h2",{children:t.jsxs("button",{className:"flex w-full items-center gap-2 px-4 py-3 text-left hover:bg-accent/50 bg-card focus-visible:outline-none focus-visible:outline-offset-[-2px] focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring",onClick:u,"aria-expanded":i,"aria-controls":N,"aria-describedby":y,"data-release-header":s,children:[v,g,O]})}),e[7]=y,e[8]=i,e[9]=u,e[10]=N,e[11]=v,e[12]=g,e[13]=O,e[14]=s,e[15]=x):x=e[15];let _;e[16]!==a||e[17]!==d||e[18]!==i||e[19]!==m||e[20]!==f||e[21]!==b||e[22]!==j||e[23]!==r||e[24]!==p?(_=i&&t.jsx("div",{id:r,className:"border-t bg-card",children:a.map((T,B)=>t.jsx(gs,{build:T,isLast:B===a.length-1,onNavigate:f,onStatusChange:j,onScheduleDeletion:b,onCancelDeletion:m,canWrite:d,statusDescribedBy:(p==null?void 0:p.buildId)===T.id?p.id:void 0},T.id))}),e[16]=a,e[17]=d,e[18]=i,e[19]=m,e[20]=f,e[21]=b,e[22]=j,e[23]=r,e[24]=p,e[25]=_):_=e[25];let P;return e[26]!==x||e[27]!==_?(P=t.jsxs("div",{className:"rounded-lg shadow-card-2 overflow-hidden",children:[x,_]}),e[26]=x,e[27]=_,e[28]=P):P=e[28],P}const ht="tapflow-app-center-releases:";function js(l){try{const e=JSON.parse(localStorage.getItem(ht+l)??"[]");return Array.isArray(e)?e.filter(s=>Array.isArray(s)&&s.length===2&&typeof s[0]=="string"&&typeof s[1]=="boolean"):[]}catch{return[]}}function ys(l,e){try{localStorage.setItem(ht+l,JSON.stringify(e))}catch{}}function vs(l,e){const s=pe.c(13),[a,i]=c.useState(Ss);let u;s[0]!==l||s[1]!==a?(u=l===null?[]:a.get(l)??js(l),s[0]=l,s[1]=a,s[2]=u):u=s[2];const f=u,j=e[0];let b;s[3]!==j||s[4]!==f?(b=r=>{var N;return((N=f.find(v=>{const[g]=v;return g===r}))==null?void 0:N[1])??r===j},s[3]=j,s[4]=f,s[5]=b):b=s[5];const m=b;let d;s[6]!==l||s[7]!==m||s[8]!==f?(d=r=>{if(l===null)return;const N=[...f.filter(v=>{const[g]=v;return g!==r}),[r,!m(r)]].slice(-200);ys(l,N),i(v=>new Map(v).set(l,N))},s[6]=l,s[7]=m,s[8]=f,s[9]=d):d=s[9];const y=d;let p;return s[10]!==m||s[11]!==y?(p={isOpen:m,toggle:y},s[10]=m,s[11]=y,s[12]=p):p=s[12],p}function Ss(){return new Map}const Ne=l=>`row:${l}`,we=l=>`release:${l}`;function Ge(l){return l instanceof HTMLElement?l.dataset.statusTrigger!==void 0?Ne(Number(l.dataset.statusTrigger)):l.dataset.releaseHeader!==void 0?we(l.dataset.releaseHeader):null:null}function Xe(l,e){const s=new Set(JSON.parse(e));return l.find(a=>s.has(a))??null}function Ye(l,e){return l.map((s,a)=>({item:s,d:a>e?2*(a-e)-1:2*(e-a)})).filter(({d:s})=>s>0).sort((s,a)=>s.d-a.d).map(({item:s})=>s)}function Rs(){var Ke;const l=as(),[e,s]=ls(),a=tt(),{user:i}=It(),u=(i==null?void 0:i.role)!=="Viewer",[f,j]=c.useState(""),[b,m]=c.useState(""),[d,y]=c.useState("all");c.useEffect(()=>{const n=setTimeout(()=>m(f),250);return()=>clearTimeout(n)},[f]);const p=e.get("appId"),r=p!==null&&/^[1-9][0-9]*$/.test(p)?Number(p):null,N=Je({queryKey:["apps"],queryFn:Mt}),v=N.data??[],g=v.find(n=>n.id===r)??null,k=["builds",r,b,d],O=JSON.stringify(k),x=Je({queryKey:k,queryFn:()=>Bt({appId:r,search:b,statusFilter:d}),enabled:r!==null,placeholderData:Pt}),_=x.data??[];c.useEffect(()=>{!N.data||e.get("appId")||N.data.length===0||s({appId:String(N.data[0].id)},{replace:!0})},[N.data,e,s]);const[P,T]=c.useState(null),[B,L]=c.useState(!1);c.useEffect(()=>{x.isRefetchError&&W.error("Couldn't refresh builds — showing the last list",{id:"builds:refresh"})},[x.isRefetchError]);const G=c.useRef(null);c.useEffect(()=>{r===null||x.isPlaceholderData||!x.data||G.current!==r&&(G.current=r,T(r))},[r,x.isPlaceholderData,x.data]);function X(n){s({appId:String(n)})}function H(n,h){return{onMutate:async o=>{const S=k;await a.cancelQueries({queryKey:S});const I=a.getQueryData(S);return a.setQueryData(S,ee=>ee&&n(ee,o)),{previous:I,key:S}},onError:(o,S,I)=>{if(I!=null&&I.previous&&a.setQueryData(I.key,I.previous),o instanceof Tt){a.invalidateQueries({queryKey:$t.me}),W.error(o.message);return}W.error(h)},onSettled:()=>{a.invalidateQueries({queryKey:["builds"]})}}}const q=H((n,h)=>n.map(o=>o.id===h.buildId?{...o,status_label:h.status}:o),"Failed to update status"),K=Re({mutationFn:({buildId:n,status:h})=>Ft(n,h),...q,onError:(n,h,o)=>{var S;q.onError(n,h,o),((S=R.current)==null?void 0:S.leaving)===h.buildId&&(R.current=null,E(null))}}),Y=(n,h)=>{var S;const o=(S=a.getQueryData(n))==null?void 0:S.find(I=>I.id===h);return o?Fe(o):"the build"},Q=Re({mutationFn:n=>Rt(n),...H((n,h)=>n.map(o=>o.id===h?{...o,delete_after:new Date().toISOString()}:o),"Failed to schedule deletion"),onSuccess:(n,h,o)=>{o&&(a.setQueryData(o.key,S=>S==null?void 0:S.map(I=>I.id===h?{...I,delete_after:n}:I)),W.success(`Deletion scheduled for ${Y(o.key,h)} — it will be deleted ${Qt(n)}`))}}),z=Re({mutationFn:n=>Et(n),...H((n,h)=>n.map(o=>o.id===h?{...o,delete_after:null}:o),"Failed to cancel scheduled deletion"),onSuccess:(n,h,o)=>{W.success(`Scheduled deletion cancelled for ${o?Y(o.key,h):"the build"}`)}}),U=n=>Q.mutate(n),J=n=>z.mutate(n),A=Ot(_),$=vs(((Ke=_[0])==null?void 0:Ke.app_id)??null,A.map(n=>n.versionName)),R=c.useRef(null),w=c.useRef(null),[D,E]=c.useState(null),Z=c.useRef(null),V=c.useId();function ue(n){const h=A.findIndex(S=>S.builds.some(I=>I.id===n));if(h<0)return[];const o=A[h].builds;return[...Ye(o,o.findIndex(S=>S.id===n)).map(S=>Ne(S.id)),...Ye(A,h).map(S=>we(S.versionName))]}const ie=JSON.stringify(A.flatMap(n=>[we(n.versionName),...$.isOpen(n.versionName)?n.builds.map(h=>Ne(h.id)):[]])),oe=(n,h)=>{const o=_.find(ee=>ee.id===n),I=d!=="all"&&h!==d?ue(n):[];if(o&&I.length>0){const ee=`${Fe(o)} was set to ${h??"no status"}, so the ${d} filter no longer shows it.`,Qe={leaving:n,candidates:I,text:ee,key:O};R.current=Qe,w.current=null,E(Qe)}K.mutate({buildId:n,status:h})},ae=D!==null&&!_.some(n=>n.id===D.leaving),te=ae?Xe(D.candidates,ie):null,xe=x.data,ce=x.isRefetchError;c.useLayoutEffect(()=>{var I;const n=R.current;if(!n)return;if(n.key!==O||ce){R.current=null,E(null);return}if((xe??[]).some(ee=>ee.id===n.leaving))return;R.current=null;const h=Xe(n.candidates,ie);if(!h||document.activeElement!==null&&document.activeElement!==document.body){W.success(n.text),E(null);return}const o=((I=Z.current)==null?void 0:I.querySelectorAll("[data-status-trigger], [data-release-header]"))??[],S=Array.from(o).find(ee=>Ge(ee)===h);S instanceof HTMLElement?(w.current=h,S.focus()):(W.success(n.text),E(null))},[O,ce,xe,ie]),c.useEffect(()=>{if(!D)return;const n=h=>{const o=Ge(h.target);if(w.current!==null){o!==w.current&&E(null);return}o!==Ne(D.leaving)&&(o===null||!D.candidates.includes(o))&&E(null)};return document.addEventListener("focusin",n),()=>document.removeEventListener("focusin",n)},[D]);const ge=(g==null?void 0:g.name)??"this app",Ie=b!==""||d!=="all",$e=x.isPlaceholderData&&P!==r,ft=x.isFetchedAfterMount&&(x.isPlaceholderData||x.isLoading),be=B||ft,Se=x.isLoadingError||be?"error":x.isLoading||A.length===0&&$e?"loading":A.length===0?"empty":"list",Me=c.useRef(null),mt=ss(Se,Me),Pe=c.useId(),Be=c.useId(),ze=c.useId(),Le=c.useId(),qe=c.useId(),pt=r===null?"":x.isLoadingError?`Couldn't load builds for ${ge}`:x.isLoading||$e||be?`Loading builds for ${ge}`:A.length>0?`Showing ${_.length} build${_.length===1?"":"s"} for ${ge}`:Ie?`No builds match the current filters for ${ge}`:`No builds for ${ge}`;return t.jsxs("div",{className:"flex h-full gap-0",children:[t.jsx(xs,{apps:v,selectedAppId:r,onSelect:X,onAdd:()=>a.invalidateQueries({queryKey:["apps"]}),canWrite:u}),t.jsxs("div",{className:"flex-1 flex flex-col gap-4 p-4 overflow-y-auto min-w-0",children:[t.jsxs("div",{className:"flex items-center justify-between flex-wrap gap-2",children:[t.jsx("h1",{className:"text-xl font-semibold tracking-display-sm",children:g?g.name:"App Center"}),t.jsx(ds,{onSuccess:()=>{a.invalidateQueries({queryKey:["apps"]}),a.invalidateQueries({queryKey:["builds"]})},appId:r,canWrite:u})]}),t.jsx("p",{id:qe,role:"status",className:"sr-only",children:pt}),r===null?t.jsxs("div",{className:"flex-1 flex flex-col items-center justify-center gap-2 text-center",children:[t.jsx(is,{className:"w-8 h-8 text-muted-foreground/40"}),t.jsx("p",{className:"text-sm font-medium",children:"No app selected"}),t.jsx("p",{className:"text-sm text-muted-foreground",children:"Choose an app from the sidebar to view its builds."})]}):t.jsxs(t.Fragment,{children:[t.jsxs("div",{className:"flex flex-wrap gap-2",children:[t.jsx(Kt,{ref:Me,"aria-describedby":`${Le} ${ze}`,"aria-label":"Search versions",placeholder:"Search version…",value:f,onChange:j}),t.jsxs(Ae,{value:d,onValueChange:y,children:[t.jsx(Ce,{className:"h-8 w-36","aria-label":"Filter by status",children:t.jsx(ke,{placeholder:"Status"})}),t.jsxs(_e,{children:[t.jsx(F,{value:"all",children:"All statuses"}),t.jsx(F,{value:"Backlog",children:"Backlog"}),t.jsx(F,{value:"In Progress",children:"In Progress"}),t.jsx(F,{value:"Done",children:"Done"}),t.jsx(F,{value:"Rejected",children:"Rejected"})]})]}),x.isRefetchError&&t.jsx("p",{className:"text-sm text-muted-foreground self-center",children:"Showing the last list — refresh failed."})]}),t.jsx("div",{...mt,className:"contents",children:Se==="error"?t.jsxs("div",{className:"flex-1 flex flex-col items-center justify-center gap-2 text-center",children:[t.jsx(rs,{className:"w-8 h-8 text-muted-foreground/40"}),t.jsx("p",{id:Pe,className:"text-sm font-medium",children:"Couldn't load builds"}),t.jsx("p",{id:Be,className:"text-sm text-muted-foreground",children:"Check that the relay is reachable."}),t.jsx(re,{variant:"outline",size:"sm",className:"mt-1 aria-disabled:opacity-50","aria-describedby":`${Pe} ${Be}`,"aria-disabled":be,onClick:()=>{be||(L(!0),x.refetch().finally(()=>L(!1)))},children:be?"Trying…":"Try again"})]}):Se==="loading"?t.jsx("p",{id:Le,className:"text-sm text-muted-foreground",children:"Loading…"}):Se==="empty"?t.jsxs("div",{className:"flex-1 flex flex-col items-center justify-center gap-2 text-center",children:[t.jsx(os,{className:"w-8 h-8 text-muted-foreground/40"}),t.jsx("p",{id:ze,className:"text-sm font-medium",children:Ie?"No matching builds":"No builds yet"}),t.jsx("p",{className:"text-sm text-muted-foreground",children:Ie?"No build matches the search and status filters.":u?"Upload the first build to get started.":"Builds appear here once a teammate uploads one."})]}):t.jsxs("div",{ref:Z,className:"flex flex-col gap-2","aria-busy":x.isPlaceholderData,children:[ae&&t.jsx("p",{id:V,hidden:!0,children:D.text}),A.map(({versionName:n,builds:h},o)=>t.jsx(bs,{describedBy:[o===0&&!x.isPlaceholderData?qe:null,te===we(n)?V:null].filter(Boolean).join(" ")||void 0,rowNote:te!=null&&te.startsWith("row:")?{buildId:Number(te.slice(4)),id:V}:void 0,versionName:n,builds:h,isOpen:$.isOpen(n),onToggle:()=>$.toggle(n),onNavigate:S=>l(`/app-center/build?id=${S}`),onStatusChange:oe,onScheduleDeletion:U,onCancelDeletion:J,canWrite:u},n))]})})]})]})]})}export{Rs as AppCenter};
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{k as m,j as e}from"./vendor-react-BlPSzrEe.js";import{aT as me,aD as ue,aO as pe,aN as G,aK as A,aB as F,aQ as fe,C as y,c as b,d as S,a as N,L as c,I as u,F as p,S as Y,h as Z,aA as i,B as h,s as k,W as he,ab as ge,P as xe,a0 as je,Z as we,av as f,au as D,Q as I,az as g,v as ve,w as ye,al as be,ac as Se}from"./index-B4w4Lh3e.js";import{F as Ne}from"./field-hint-CMKmbVpy.js";import{B as Pe}from"./badge-B3vaBVIQ.js";import{A as Ce,h as Ae,c as Fe,f as ke,g as De,d as Te,e as qe,b as Ee,a as Oe}from"./alert-dialog-DFz8G_zu.js";import{P as X}from"./pencil-CycAOoC_.js";import"./vendor-router-B_lVHrKl.js";const _e=D({teamName:g().min(1,"Team name is required"),logo:I(File).nullable().optional()}),Qe=D({displayName:g().min(1,"Display name is required"),avatar:I(File).nullable().optional()}),Re=D({currentPassword:g().min(1,"Enter your current password"),newPassword:g().min(ve,ye),confirmPassword:g()}).refine(x=>x.newPassword===x.confirmPassword,{message:"Passwords do not match",path:["confirmPassword"]});function Ve(){var W,K,U,M,V,$,J;const{resolvedTheme:x}=me(),ee=x==="dark"?"/logo-dark.svg":"/logo.svg",{user:o}=ue(),T=(o==null?void 0:o.role)==="Admin",q=(o==null?void 0:o.role)==="Admin"||(o==null?void 0:o.role)==="Developer"||(o==null?void 0:o.role)==="QA",j=pe(),P=G({queryKey:f.settings,queryFn:be,enabled:T}),[re,se]=m.useState(null),ae=re??((W=P.data)==null?void 0:W.logo_url)??null,E=m.useRef(null),d=A({shouldFocusError:!1,resolver:F(_e),defaultValues:{teamName:"",logo:null},values:P.data?{teamName:P.data.team_name,logo:null}:void 0,resetOptions:{keepDirtyValues:!0}});async function te(r){const s=new FormData;s.append("team_name",r.teamName),r.logo&&s.append("logo",r.logo);try{if(!(await fetch("/api/v1/settings",{method:"PATCH",credentials:"include",body:s})).ok){i.error("Failed to update workspace");return}i.success("Workspace updated"),j.invalidateQueries({queryKey:f.settings})}catch{i.error("Failed to update workspace")}}const[oe,ie]=m.useState(null),O=oe??(o==null?void 0:o.avatarUrl)??null,_=m.useRef(null),n=A({shouldFocusError:!1,resolver:F(Qe),defaultValues:{displayName:"",avatar:null},values:o?{displayName:o.displayName??"",avatar:null}:void 0,resetOptions:{keepDirtyValues:!0}});async function ne(r){const s=new FormData;s.append("display_name",r.displayName),r.avatar&&s.append("avatar",r.avatar);try{if(!(await fetch("/api/v1/profile",{method:"PATCH",credentials:"include",body:s})).ok){i.error("Failed to update profile");return}i.success("Profile updated"),j.invalidateQueries({queryKey:f.me})}catch{i.error("Failed to update profile")}}const t=A({shouldFocusError:!1,resolver:F(Re)});async function le(r){try{const s=await fetch("/api/v1/auth/change-password",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({currentPassword:r.currentPassword,newPassword:r.newPassword})});if(!s.ok){const a=await s.json();t.setError("root",{message:a.error??"Failed to change password"});return}t.reset(),i.success("Password changed")}catch{t.setError("root",{message:"Network error"})}}const C=G({queryKey:f.apps,queryFn:Se,enabled:q}).data??[],[Q,R]=m.useState({}),[L,z]=m.useState({}),[B,H]=m.useState({});async function de(r){H(s=>({...s,[r]:!0}));try{if(!(await fetch(`/api/v1/apps/${r}`,{method:"DELETE",credentials:"include"})).ok){i.error("Failed to delete app");return}j.invalidateQueries({queryKey:f.apps}),i.success("App deleted")}catch{i.error("Failed to delete app")}finally{H(s=>({...s,[r]:!1}))}}async function ce(r){var s;z(a=>({...a,[r]:!0}));try{if(!(await fetch(`/api/v1/apps/${r}`,{method:"PATCH",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:Q[r]??((s=C.find(l=>l.id===r))==null?void 0:s.name)})})).ok){i.error("Failed to update app");return}i.success("App updated"),R(({[r]:l,...v})=>v),j.invalidateQueries({queryKey:f.apps})}catch{i.error("Failed to update app")}finally{z(a=>({...a,[r]:!1}))}}const w=fe({control:n.control,name:"displayName"});return e.jsxs("div",{className:"flex flex-col gap-6 max-w-[900px] mx-auto w-full p-6",children:[e.jsx("h1",{className:"text-xl font-semibold tracking-display-sm",children:"Settings"}),T&&e.jsxs(y,{children:[e.jsx(b,{children:e.jsx(S,{children:"Workspace"})}),e.jsx(N,{children:e.jsxs("form",{onSubmit:d.handleSubmit(te),className:"flex flex-col gap-4",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsx(c,{htmlFor:"team-name",children:"Team name"}),e.jsx(u,{id:"team-name",placeholder:"My QA Team","aria-required":"true","aria-invalid":!!d.formState.errors.teamName,"aria-describedby":d.formState.errors.teamName?"teamName-error":void 0,...d.register("teamName")}),e.jsx(p,{id:"teamName-error",message:(K=d.formState.errors.teamName)==null?void 0:K.message})]}),e.jsx(Y,{}),e.jsxs("div",{className:"grid gap-2",children:[e.jsxs(c,{children:["Logo ",e.jsx("span",{className:"text-muted-foreground text-xs",children:"(png · jpg, max 2MB)"})]}),e.jsx(Z,{name:"logo",control:d.control,render:({field:r})=>e.jsxs("div",{className:"relative w-16 h-16",children:[e.jsx("img",{src:ae??ee,alt:"Workspace logo",className:"w-16 h-16 rounded-lg object-contain"}),e.jsx("button",{type:"button","aria-label":"Change logo",onClick:()=>{var s;return(s=E.current)==null?void 0:s.click()},className:"absolute bottom-0 right-0 w-6 h-6 rounded-full bg-background border border-border shadow-sm flex items-center justify-center hover:bg-accent transition-colors",children:e.jsx(X,{className:"w-3 h-3","aria-hidden":"true"})}),e.jsx("input",{ref:E,type:"file",accept:".png,.jpg,.jpeg",className:"hidden",onChange:s=>{var l;const a=(l=s.target.files)==null?void 0:l[0];if(a&&a.size>2*1024*1024){i.error("Image must be 2MB or less");return}a&&(r.onChange(a),se(URL.createObjectURL(a)))}})]})})]}),e.jsx("div",{className:"flex justify-end",children:e.jsx(h,{type:"submit",size:"sm",disabled:d.formState.isSubmitting,children:d.formState.isSubmitting?"Saving…":"Save changes"})}),e.jsx(k,{errors:d.formState.errors,submitCount:d.formState.submitCount})]})})]}),e.jsxs(y,{children:[e.jsx(b,{children:e.jsx(S,{children:"Profile"})}),e.jsx(N,{children:e.jsxs("form",{onSubmit:n.handleSubmit(ne),className:"flex flex-col gap-4",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsx(c,{htmlFor:"display-name",children:"Nickname"}),e.jsx(u,{id:"display-name",placeholder:"Your name","aria-required":"true","aria-invalid":!!n.formState.errors.displayName,"aria-describedby":n.formState.errors.displayName?"displayName-error":void 0,...n.register("displayName")}),e.jsx(p,{id:"displayName-error",message:(U=n.formState.errors.displayName)==null?void 0:U.message})]}),e.jsx(Y,{}),e.jsxs("div",{className:"grid gap-2",children:[e.jsxs(c,{children:["Avatar ",e.jsx("span",{className:"text-muted-foreground text-xs",children:"(png · jpg, max 2MB)"})]}),e.jsx(Z,{name:"avatar",control:n.control,render:({field:r})=>{var s;return e.jsxs("div",{className:"relative w-14 h-14",children:[O?e.jsx("img",{src:O,alt:"Profile avatar",className:"w-14 h-14 rounded-full object-cover border"}):e.jsx("div",{className:"w-14 h-14 rounded-full flex items-center justify-center text-lg font-medium",style:(()=>{const a=he(w||(o==null?void 0:o.email)||"");return{backgroundColor:a.bg,color:a.fg}})(),children:((s=w==null?void 0:w[0])==null?void 0:s.toUpperCase())??"?"}),e.jsx("button",{type:"button","aria-label":"Change avatar",onClick:()=>{var a;return(a=_.current)==null?void 0:a.click()},className:"absolute bottom-0 right-0 w-6 h-6 rounded-full bg-background border border-border shadow-sm flex items-center justify-center hover:bg-accent transition-colors",children:e.jsx(X,{className:"w-3 h-3","aria-hidden":"true"})}),e.jsx("input",{ref:_,type:"file",accept:".png,.jpg,.jpeg",className:"hidden",onChange:a=>{var v;const l=(v=a.target.files)==null?void 0:v[0];if(l&&l.size>2*1024*1024){i.error("Image must be 2MB or less");return}l&&(r.onChange(l),ie(URL.createObjectURL(l)))}})]})}})]}),e.jsx("div",{className:"flex justify-end",children:e.jsx(h,{type:"submit",size:"sm",disabled:n.formState.isSubmitting,children:n.formState.isSubmitting?"Saving…":"Save changes"})}),e.jsx(k,{errors:n.formState.errors,submitCount:n.formState.submitCount})]})})]}),e.jsxs(y,{children:[e.jsx(b,{children:e.jsx(S,{children:"Password"})}),e.jsx(N,{children:e.jsxs("form",{onSubmit:t.handleSubmit(le),className:"flex flex-col gap-4",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsx(c,{htmlFor:"current-password",children:"Current password"}),e.jsx(u,{id:"current-password",type:"password","aria-required":"true",autoComplete:"current-password","aria-invalid":!!t.formState.errors.currentPassword,"aria-describedby":t.formState.errors.currentPassword?"currentPassword-error":void 0,...t.register("currentPassword")}),e.jsx(p,{id:"currentPassword-error",message:(M=t.formState.errors.currentPassword)==null?void 0:M.message})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsx(c,{htmlFor:"new-password",children:"New password"}),e.jsx(u,{id:"new-password",type:"password","aria-required":"true",autoComplete:"new-password","aria-invalid":!!t.formState.errors.newPassword,"aria-describedby":ge("newPassword-hint","newPassword-error",!!t.formState.errors.newPassword),...t.register("newPassword")}),!t.formState.errors.newPassword&&e.jsx(Ne,{id:"newPassword-hint",children:xe}),e.jsx(p,{id:"newPassword-error",message:(V=t.formState.errors.newPassword)==null?void 0:V.message})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsx(c,{htmlFor:"confirm-password",children:"Confirm new password"}),e.jsx(u,{id:"confirm-password",type:"password","aria-required":"true",autoComplete:"new-password","aria-invalid":!!t.formState.errors.confirmPassword,"aria-describedby":t.formState.errors.confirmPassword?"confirmPassword-error":void 0,...t.register("confirmPassword")}),e.jsx(p,{id:"confirmPassword-error",message:($=t.formState.errors.confirmPassword)==null?void 0:$.message})]}),e.jsx(p,{assertive:!0,id:"default-error",message:(J=t.formState.errors.root)==null?void 0:J.message}),e.jsx("div",{className:"flex justify-end",children:e.jsx(h,{type:"submit",size:"sm",disabled:t.formState.isSubmitting,children:t.formState.isSubmitting?"Saving…":"Change password"})}),e.jsx(k,{errors:t.formState.errors,submitCount:t.formState.submitCount})]})})]}),q&&C.length>0&&e.jsxs(y,{children:[e.jsx(b,{children:e.jsx(S,{children:"Apps"})}),e.jsx(N,{children:e.jsx("div",{className:"flex flex-col divide-y divide-border",children:C.map(r=>e.jsxs("div",{className:"flex flex-col gap-2 py-4 first:pt-0 last:pb-0",children:[e.jsx(c,{htmlFor:`app-${r.id}`,children:"App Name"}),e.jsx(u,{id:`app-${r.id}`,value:Q[r.id]??r.name,onChange:s=>R(a=>({...a,[r.id]:s.target.value}))}),e.jsxs("div",{className:"flex flex-col gap-1.5 mt-1",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"text-xs text-muted-foreground font-mono",children:r.bundle_id_key}),e.jsx(Pe,{tone:r.platform==="ios"?"ios":r.platform==="android"?"android":void 0,variant:r.platform==="both"?"secondary":void 0,className:"capitalize",children:r.platform})]}),e.jsxs("div",{className:"flex items-center justify-end gap-2",children:[e.jsx(h,{size:"sm",variant:"outline",disabled:L[r.id],onClick:()=>ce(r.id),children:L[r.id]?"Saving…":"Save"}),e.jsxs(Ce,{children:[e.jsx(Ae,{asChild:!0,children:e.jsx(h,{size:"sm",variant:"destructive",disabled:B[r.id],children:B[r.id]?"Deleting…":"Delete"})}),e.jsxs(Fe,{children:[e.jsxs(ke,{children:[e.jsx(De,{children:"Delete app?"}),e.jsxs(Te,{children:["This will permanently delete ",e.jsx("strong",{children:r.name})," and all its builds. This action cannot be undone."]})]}),e.jsxs(qe,{children:[e.jsx(Ee,{className:"w-24",children:"Cancel"}),e.jsx(Oe,{className:je(we({variant:"destructive"}),"w-24"),onClick:()=>de(r.id),children:"Delete"})]})]})]})]})]})]},r.id))})})]})]})}export{Ve as DefaultSettings};
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{k as F,j as e}from"./vendor-react-BlPSzrEe.js";import{aK as Q,aB as W,aQ as K,aO as M,aN as U,C as R,c as T,d as O,b as E,a as z,L as m,I as f,h as G,W as Y,F as u,S as V,ab as J,P as X,B as Z,s as $,av as I,au as ee,az as v,v as ae,w as se,Q as re,aR as te}from"./index-B4w4Lh3e.js";import{F as ie}from"./field-hint-CMKmbVpy.js";import{b as ne,c as oe,L as de}from"./vendor-router-B_lVHrKl.js";import{P as le}from"./pencil-CycAOoC_.js";const ce=ee({displayName:v().optional(),avatar:re(File).nullable().optional(),password:v().min(ae,se),confirm:v()}).refine(o=>o.password===o.confirm,{message:"Passwords do not match",path:["confirm"]});function ve(){var b,N,C,S,k,P;const o=ne(),[q]=oe(),i=q.get("token")??"",[x,A]=F.useState(null),g=F.useRef(null),{register:p,handleSubmit:L,control:j,setError:d,formState:{errors:a,isSubmitting:w,submitCount:_}}=Q({shouldFocusError:!1,resolver:W(ce),defaultValues:{displayName:"",avatar:null}}),l=K({control:j,name:"displayName"})??"",B=M(),h=U({queryKey:I.inviteToken(i),queryFn:()=>te(i),enabled:i!=="",staleTime:1/0}),y=!i||h.isError?"invalid":h.isPending?"loading":"valid",D=((b=h.data)==null?void 0:b.role)??"";async function H(t){var n;try{const s=new FormData;s.append("token",i),s.append("password",t.password),(n=t.displayName)!=null&&n.trim()&&s.append("display_name",t.displayName.trim()),t.avatar&&s.append("avatar",t.avatar);const r=await fetch("/api/v1/invitations/accept",{method:"POST",body:s});if(r.status===409){d("root",{type:"already-member",message:"This email already has an account. Sign in instead, or ask your admin for a password reset link."});return}if(!r.ok){const c=await r.json().catch(()=>({}));d("root",{message:c.error??"Failed to accept invitation"});return}B.removeQueries({queryKey:I.inviteToken(i)}),o("/app-center",{replace:!0})}catch{d("root",{message:"Network error. Please try again."})}}return y==="loading"?e.jsx("div",{className:"flex min-h-svh items-center justify-center"}):y==="invalid"?e.jsx("div",{className:"bg-mesh-gradient flex min-h-svh items-center justify-center overflow-hidden p-4",children:e.jsx(R,{level:4,className:"w-full max-w-sm text-center",children:e.jsxs(T,{children:[e.jsx(O,{children:"Invitation expired"}),e.jsx(E,{children:"This invite link is invalid or has expired. Ask your admin for a new one."})]})})}):e.jsx("div",{className:"bg-mesh-gradient flex min-h-svh items-center justify-center overflow-hidden p-4",children:e.jsxs(R,{level:4,className:"w-full max-w-sm",children:[e.jsxs(T,{className:"text-center",children:[e.jsx(O,{children:"Set up your account"}),e.jsxs(E,{children:["You're joining as ",e.jsx("strong",{children:D})]})]}),e.jsx(z,{children:e.jsxs("form",{onSubmit:L(H),className:"flex flex-col gap-4",children:[e.jsxs("div",{className:"grid gap-2",children:[e.jsxs(m,{htmlFor:"display-name",children:["Nickname ",e.jsx("span",{className:"text-muted-foreground text-xs",children:"(optional)"})]}),e.jsx(f,{id:"display-name",placeholder:"Your name",...p("displayName")})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsxs(m,{children:["Avatar ",e.jsx("span",{className:"text-muted-foreground text-xs",children:"(optional, png · jpg, max 2MB)"})]}),e.jsx(G,{name:"avatar",control:j,render:({field:t})=>{var n;return e.jsxs("div",{className:"relative w-14 h-14",children:[x?e.jsx("img",{src:x,alt:"Avatar preview",className:"w-14 h-14 rounded-full object-cover border"}):e.jsx("div",{className:"w-14 h-14 rounded-full flex items-center justify-center text-lg font-medium",style:(()=>{const s=Y(l);return{backgroundColor:s.bg,color:s.fg}})(),children:((n=l==null?void 0:l[0])==null?void 0:n.toUpperCase())??"?"}),e.jsx("button",{type:"button","aria-label":a.avatar?"Change avatar — the file was rejected":"Change avatar","aria-describedby":a.avatar?"avatar-error":void 0,onClick:()=>{var s;return(s=g.current)==null?void 0:s.click()},className:"absolute bottom-0 right-0 w-6 h-6 rounded-full bg-background border border-border shadow-sm flex items-center justify-center hover:bg-accent transition-colors",children:e.jsx(le,{className:"w-3 h-3","aria-hidden":"true"})}),e.jsx("input",{ref:g,type:"file",accept:"image/png,image/jpeg",className:"hidden",onChange:s=>{var c;const r=(c=s.target.files)==null?void 0:c[0];if(r){if(r.size>2*1024*1024){d("avatar",{message:"Max 2MB for avatar"});return}t.onChange(r),A(URL.createObjectURL(r))}}})]})}}),e.jsx(u,{assertive:!0,id:"avatar-error",message:(N=a.avatar)==null?void 0:N.message})]}),e.jsx(V,{}),e.jsxs("div",{className:"grid gap-2",children:[e.jsx(m,{htmlFor:"password",children:"Password"}),e.jsx(f,{id:"password",type:"password","aria-required":"true",autoComplete:"new-password","aria-invalid":!!a.password,"aria-describedby":J("password-hint","password-error",!!a.password),...p("password")}),!a.password&&e.jsx(ie,{id:"password-hint",children:X}),e.jsx(u,{id:"password-error",message:(C=a.password)==null?void 0:C.message})]}),e.jsxs("div",{className:"grid gap-2",children:[e.jsx(m,{htmlFor:"confirm",children:"Confirm password"}),e.jsx(f,{id:"confirm",type:"password","aria-required":"true",autoComplete:"new-password","aria-invalid":!!a.confirm,"aria-describedby":a.confirm?"confirm-error":void 0,...p("confirm")}),e.jsx(u,{id:"confirm-error",message:(S=a.confirm)==null?void 0:S.message})]}),e.jsx(u,{assertive:!0,id:"invite-error",message:(k=a.root)==null?void 0:k.message}),((P=a.root)==null?void 0:P.type)==="already-member"&&e.jsx(de,{to:"/login",className:"text-sm text-center underline underline-offset-4",children:"Go to sign in"}),e.jsx(Z,{type:"submit",disabled:w,className:"w-full",children:w?"Creating account…":"Create account"}),e.jsx($,{errors:a,submitCount:_})]})})]})})}export{ve as Invite};
|
|
Binary file
|
|
Binary file
|