@solidxai/core-ui 0.1.7-beta.6 → 0.1.7-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/dist/components/auth/SolidOTPVerify.d.ts +3 -0
  2. package/dist/components/auth/SolidOTPVerify.d.ts.map +1 -0
  3. package/dist/components/auth/SolidOTPVerify.js +67 -0
  4. package/dist/components/auth/SolidOTPVerify.js.map +1 -0
  5. package/dist/components/auth/SolidOTPVerify.tsx +133 -0
  6. package/dist/components/common/AuthBanner.js.map +1 -1
  7. package/dist/components/core/common/LoadDynamicJsxComponent.d.ts +2 -0
  8. package/dist/components/core/common/LoadDynamicJsxComponent.d.ts.map +1 -0
  9. package/dist/components/core/common/LoadDynamicJsxComponent.js +50 -0
  10. package/dist/components/core/common/LoadDynamicJsxComponent.js.map +1 -0
  11. package/dist/components/core/common/LoadDynamicJsxComponent.tsx +70 -0
  12. package/dist/components/core/kanban/SolidManyToOneFilterElement.d.ts.map +1 -1
  13. package/dist/components/core/kanban/SolidManyToOneFilterElement.js.map +1 -1
  14. package/dist/components/core/kanban/SolidManyToOneFilterElement.tsx +2 -1
  15. package/dist/components/core/kanban/kanban-fields/SolidMediaMultipleKanbanField.d.ts.map +1 -1
  16. package/dist/components/core/kanban/kanban-fields/SolidMediaMultipleKanbanField.js +2 -2
  17. package/dist/components/core/kanban/kanban-fields/SolidMediaMultipleKanbanField.js.map +1 -1
  18. package/dist/components/core/kanban/kanban-fields/SolidMediaMultipleKanbanField.tsx +10 -21
  19. package/dist/components/core/kanban/kanban-fields/SolidMediaSingleKanbanField.d.ts.map +1 -1
  20. package/dist/components/core/kanban/kanban-fields/SolidMediaSingleKanbanField.js +2 -2
  21. package/dist/components/core/kanban/kanban-fields/SolidMediaSingleKanbanField.js.map +1 -1
  22. package/dist/components/core/kanban/kanban-fields/SolidMediaSingleKanbanField.tsx +10 -18
  23. package/dist/components/core/kanban/kanban-fields/SolidShortTextKanbanField.d.ts.map +1 -1
  24. package/dist/components/core/kanban/kanban-fields/SolidShortTextKanbanField.js +6 -3
  25. package/dist/components/core/kanban/kanban-fields/SolidShortTextKanbanField.js.map +1 -1
  26. package/dist/components/core/kanban/kanban-fields/SolidShortTextKanbanField.tsx +24 -30
  27. package/dist/components/core/kanban/kanban-fields/relations/SolidRelationManyToOneKanbanField.js.map +1 -1
  28. package/dist/components/core/kanban/kanban-fields/relations/SolidRelationManyToOneKanbanField.tsx +2 -2
  29. package/dist/components/core/list/SolidListViewRowButtonContextMenu.d.ts +1 -1
  30. package/dist/components/core/list/SolidListViewRowButtonContextMenu.d.ts.map +1 -1
  31. package/dist/components/core/list/SolidListViewRowButtonContextMenu.js +7 -6
  32. package/dist/components/core/list/SolidListViewRowButtonContextMenu.js.map +1 -1
  33. package/dist/components/core/list/SolidListViewRowButtonContextMenu.tsx +10 -9
  34. package/dist/components/core/users/ApiKeysTab/ApiKeysTab.d.ts +7 -0
  35. package/dist/components/core/users/ApiKeysTab/ApiKeysTab.d.ts.map +1 -0
  36. package/dist/components/core/users/ApiKeysTab/ApiKeysTab.js +138 -0
  37. package/dist/components/core/users/ApiKeysTab/ApiKeysTab.js.map +1 -0
  38. package/dist/components/core/users/ApiKeysTab/ApiKeysTab.tsx +246 -0
  39. package/dist/components/core/users/ApiKeysTab/GenerateApiKeyModal.d.ts +8 -0
  40. package/dist/components/core/users/ApiKeysTab/GenerateApiKeyModal.d.ts.map +1 -0
  41. package/dist/components/core/users/ApiKeysTab/GenerateApiKeyModal.js +156 -0
  42. package/dist/components/core/users/ApiKeysTab/GenerateApiKeyModal.js.map +1 -0
  43. package/dist/components/core/users/ApiKeysTab/GenerateApiKeyModal.tsx +184 -0
  44. package/dist/components/core/users/ApiKeysTab/RevealApiKeyModal.d.ts +9 -0
  45. package/dist/components/core/users/ApiKeysTab/RevealApiKeyModal.d.ts.map +1 -0
  46. package/dist/components/core/users/ApiKeysTab/RevealApiKeyModal.js +37 -0
  47. package/dist/components/core/users/ApiKeysTab/RevealApiKeyModal.js.map +1 -0
  48. package/dist/components/core/users/ApiKeysTab/RevealApiKeyModal.tsx +111 -0
  49. package/dist/components/core/users/ApiKeysTab/index.d.ts +4 -0
  50. package/dist/components/core/users/ApiKeysTab/index.d.ts.map +1 -0
  51. package/dist/components/core/users/ApiKeysTab/index.js +4 -0
  52. package/dist/components/core/users/ApiKeysTab/index.js.map +1 -0
  53. package/dist/components/core/users/ApiKeysTab/index.ts +3 -0
  54. package/dist/helpers/# no such endpoints exist, need to calle +3 -0
  55. package/dist/nextAuth/authProviders.d.ts +4 -0
  56. package/dist/nextAuth/authProviders.d.ts.map +1 -0
  57. package/dist/nextAuth/authProviders.js +198 -0
  58. package/dist/nextAuth/authProviders.js.map +1 -0
  59. package/dist/nextAuth/authProviders.tsx +232 -0
  60. package/dist/nextAuth/handleLogout.d.ts +2 -0
  61. package/dist/nextAuth/handleLogout.d.ts.map +1 -0
  62. package/dist/nextAuth/handleLogout.js +36 -0
  63. package/dist/nextAuth/handleLogout.js.map +1 -0
  64. package/dist/nextAuth/handleLogout.tsx +39 -0
  65. package/dist/nextAuth/refreshAccessToken.d.ts +2 -0
  66. package/dist/nextAuth/refreshAccessToken.d.ts.map +1 -0
  67. package/dist/nextAuth/refreshAccessToken.js +24 -0
  68. package/dist/nextAuth/refreshAccessToken.js.map +1 -0
  69. package/dist/nextAuth/refreshAccessToken.tsx +28 -0
  70. package/dist/redux/api/apiKeyApi.d.ts +40 -0
  71. package/dist/redux/api/apiKeyApi.d.ts.map +1 -0
  72. package/dist/redux/api/apiKeyApi.js +36 -0
  73. package/dist/redux/api/apiKeyApi.js.map +1 -0
  74. package/dist/redux/api/apiKeyApi.ts +60 -0
  75. package/dist/redux/features/settingsSlice.d.ts +20 -0
  76. package/dist/redux/features/settingsSlice.d.ts.map +1 -0
  77. package/dist/redux/features/settingsSlice.js +39 -0
  78. package/dist/redux/features/settingsSlice.js.map +1 -0
  79. package/dist/redux/features/settingsSlice.ts +60 -0
  80. package/package.json +1 -1
  81. package/dist/components/core/list/SolidDataTablePagination.d.ts +0 -15
  82. package/dist/components/core/list/SolidDataTablePagination.d.ts.map +0 -1
  83. package/dist/components/core/list/SolidDataTablePagination.js +0 -22
  84. package/dist/components/core/list/SolidDataTablePagination.js.map +0 -1
  85. package/dist/components/core/list/SolidDataTablePagination.tsx +0 -71
  86. package/dist/components/solid-ui/SolidButton.d.ts +0 -14
  87. package/dist/components/solid-ui/SolidButton.d.ts.map +0 -1
  88. package/dist/components/solid-ui/SolidButton.js +0 -36
  89. package/dist/components/solid-ui/SolidButton.js.map +0 -1
  90. package/dist/components/solid-ui/SolidButton.tsx +0 -54
  91. package/dist/components/solid-ui/SolidTabs.d.ts +0 -18
  92. package/dist/components/solid-ui/SolidTabs.d.ts.map +0 -1
  93. package/dist/components/solid-ui/SolidTabs.js +0 -22
  94. package/dist/components/solid-ui/SolidTabs.js.map +0 -1
  95. package/dist/components/solid-ui/SolidTabs.tsx +0 -73
  96. package/dist/components/solid-ui/index.d.ts +0 -3
  97. package/dist/components/solid-ui/index.d.ts.map +0 -1
  98. package/dist/components/solid-ui/index.js +0 -3
  99. package/dist/components/solid-ui/index.js.map +0 -1
  100. package/dist/components/solid-ui/index.ts +0 -2
@@ -0,0 +1,138 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var __generator = (this && this.__generator) || function (thisArg, body) {
11
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
12
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
13
+ function verb(n) { return function (v) { return step([n, v]); }; }
14
+ function step(op) {
15
+ if (f) throw new TypeError("Generator is already executing.");
16
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
17
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
18
+ if (y = 0, t) op = [op[0] & 2, t.value];
19
+ switch (op[0]) {
20
+ case 0: case 1: t = op; break;
21
+ case 4: _.label++; return { value: op[1], done: false };
22
+ case 5: _.label++; y = op[1]; op = [0]; continue;
23
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
24
+ default:
25
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
26
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
27
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
28
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
29
+ if (t[2]) _.ops.pop();
30
+ _.trys.pop(); continue;
31
+ }
32
+ op = body.call(thisArg, _);
33
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
34
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
+ }
36
+ };
37
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
38
+ import { useState } from "react";
39
+ import { useDispatch } from "react-redux";
40
+ import { KeyRound, Plus } from "lucide-react";
41
+ import { SolidButton, SolidSpinner, SolidSwitch, SolidTag } from "../../../shad-cn-ui";
42
+ import { showToast } from "../../../../redux/features/toastSlice";
43
+ import { useGetUserApiKeysQuery, useUpdateApiKeyMutation, } from "../../../../redux/api/apiKeyApi";
44
+ import { GenerateApiKeyModal } from "./GenerateApiKeyModal";
45
+ import { RevealApiKeyModal } from "./RevealApiKeyModal";
46
+ function formatDate(iso) {
47
+ if (!iso)
48
+ return "—";
49
+ return new Date(iso).toLocaleDateString(undefined, {
50
+ year: "numeric",
51
+ month: "short",
52
+ day: "numeric",
53
+ });
54
+ }
55
+ function getExpiryStatus(expiresAt) {
56
+ if (!expiresAt)
57
+ return "never";
58
+ var diff = new Date(expiresAt).getTime() - Date.now();
59
+ if (diff < 0)
60
+ return "expired";
61
+ if (diff < 7 * 24 * 60 * 60 * 1000)
62
+ return "expiring-soon";
63
+ return "ok";
64
+ }
65
+ function ApiKeysTable(_a) {
66
+ var keys = _a.keys, onToggleActive = _a.onToggleActive, isTogglingId = _a.isTogglingId;
67
+ if (keys.length === 0) {
68
+ return (_jsxs("div", { className: "flex flex-column align-items-center justify-content-center gap-3 py-6", style: { color: "var(--solid-text-secondary, #888)" }, children: [_jsx(KeyRound, { size: 32, strokeWidth: 1.5 }), _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "m-0", style: { fontSize: 14, fontWeight: 500 }, children: "No API keys" }), _jsx("p", { className: "m-0 mt-1", style: { fontSize: 12 }, children: "Generate a key to enable programmatic access." })] })] }));
69
+ }
70
+ return (_jsx("div", { style: { overflowX: "auto" }, children: _jsxs("table", { style: { width: "100%", borderCollapse: "collapse" }, children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { className: "solid-api-keys-th", children: "Name" }), _jsx("th", { className: "solid-api-keys-th", children: "Key" }), _jsx("th", { className: "solid-api-keys-th", children: "Status" }), _jsx("th", { className: "solid-api-keys-th", children: "Expires" }), _jsx("th", { className: "solid-api-keys-th", children: "Last Used" }), _jsx("th", { className: "solid-api-keys-th", children: "Active" })] }) }), _jsx("tbody", { children: keys.map(function (key) {
71
+ var expiryStatus = getExpiryStatus(key.expiresAt);
72
+ var isExpired = expiryStatus === "expired";
73
+ return (_jsxs("tr", { className: isExpired ? "solid-api-keys-row--expired" : undefined, children: [_jsx("td", { className: "solid-api-keys-td", children: _jsx("span", { style: { fontWeight: 500 }, children: key.name }) }), _jsx("td", { className: "solid-api-keys-td", children: _jsx("code", { style: {
74
+ fontFamily: "monospace",
75
+ fontSize: 13,
76
+ background: "var(--solid-surface-secondary, #f5f5f5)",
77
+ padding: "2px 6px",
78
+ borderRadius: 4,
79
+ }, children: key.maskedKey }) }), _jsx("td", { className: "solid-api-keys-td", children: isExpired ? (_jsx(SolidTag, { tone: "danger", children: "Expired" })) : !key.isActive ? (_jsx(SolidTag, { tone: "warn", children: "Inactive" })) : expiryStatus === "expiring-soon" ? (_jsx(SolidTag, { tone: "warn", children: "Expiring soon" })) : (_jsx(SolidTag, { tone: "success", children: "Active" })) }), _jsx("td", { className: "solid-api-keys-td", style: {
80
+ color: expiryStatus === "expired"
81
+ ? "var(--solid-danger-color, #ef4444)"
82
+ : expiryStatus === "expiring-soon"
83
+ ? "var(--solid-warn-color, #f59e0b)"
84
+ : undefined,
85
+ }, children: expiryStatus === "never" ? (_jsx("span", { style: { color: "var(--solid-text-secondary, #888)" }, children: "Never" })) : (formatDate(key.expiresAt)) }), _jsx("td", { className: "solid-api-keys-td", style: { color: "var(--solid-text-secondary, #888)" }, children: formatDate(key.lastUsedAt) }), _jsx("td", { className: "solid-api-keys-td", children: isTogglingId === key.id ? (_jsx(SolidSpinner, {})) : (_jsx(SolidSwitch, { checked: key.isActive, disabled: isExpired, onChange: function () { return onToggleActive(key); } })) })] }, key.id));
86
+ }) })] }) }));
87
+ }
88
+ export function ApiKeysTab(_a) {
89
+ var _this = this;
90
+ var _b, _c;
91
+ var userId = _a.userId, _d = _a.canCreate, canCreate = _d === void 0 ? false : _d;
92
+ var dispatch = useDispatch();
93
+ var _e = useGetUserApiKeysQuery(userId), data = _e.data, isLoading = _e.isLoading, isError = _e.isError;
94
+ var updateApiKey = useUpdateApiKeyMutation()[0];
95
+ var _f = useState(null), togglingId = _f[0], setTogglingId = _f[1];
96
+ var _g = useState(false), showGenerate = _g[0], setShowGenerate = _g[1];
97
+ var _h = useState(null), revealKey = _h[0], setRevealKey = _h[1];
98
+ var keys = (_c = (_b = data === null || data === void 0 ? void 0 : data.data) === null || _b === void 0 ? void 0 : _b.apiKeys) !== null && _c !== void 0 ? _c : [];
99
+ var handleToggle = function (key) { return __awaiter(_this, void 0, void 0, function () {
100
+ var err_1;
101
+ var _a;
102
+ return __generator(this, function (_b) {
103
+ switch (_b.label) {
104
+ case 0:
105
+ setTogglingId(key.id);
106
+ _b.label = 1;
107
+ case 1:
108
+ _b.trys.push([1, 3, 4, 5]);
109
+ return [4 /*yield*/, updateApiKey({ id: key.id, isActive: !key.isActive }).unwrap()];
110
+ case 2:
111
+ _b.sent();
112
+ dispatch(showToast({
113
+ severity: "success",
114
+ summary: "Updated",
115
+ detail: "API key \"".concat(key.name, "\" ").concat(!key.isActive ? "activated" : "deactivated", "."),
116
+ }));
117
+ return [3 /*break*/, 5];
118
+ case 3:
119
+ err_1 = _b.sent();
120
+ dispatch(showToast({
121
+ severity: "error",
122
+ summary: "Error",
123
+ detail: ((_a = err_1 === null || err_1 === void 0 ? void 0 : err_1.data) === null || _a === void 0 ? void 0 : _a.message) || "Failed to update API key.",
124
+ }));
125
+ return [3 /*break*/, 5];
126
+ case 4:
127
+ setTogglingId(null);
128
+ return [7 /*endfinally*/];
129
+ case 5: return [2 /*return*/];
130
+ }
131
+ });
132
+ }); };
133
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "solid-api-keys-tab", children: [_jsxs("div", { className: "flex align-items-center justify-content-between mb-4", children: [_jsxs("div", { children: [_jsx("p", { className: "m-0", style: { fontWeight: 600, fontSize: 14 }, children: "API Keys" }), _jsx("p", { className: "m-0 mt-1", style: { fontSize: 12, color: "var(--solid-text-secondary, #888)" }, children: "Keys grant programmatic access. Store them securely \u2014 they are shown only once." })] }), canCreate && (_jsxs(SolidButton, { size: "small", type: "button", onClick: function () { return setShowGenerate(true); }, children: [_jsx(Plus, { size: 14 }), "Generate Key"] }))] }), isLoading ? (_jsx("div", { className: "flex justify-content-center py-5", children: _jsx(SolidSpinner, {}) })) : isError && keys.length === 0 ? (_jsxs("div", { className: "flex flex-column align-items-center justify-content-center gap-2 py-5", style: { color: "var(--solid-text-secondary, #888)", fontSize: 13 }, children: [_jsx("p", { className: "m-0", children: "Something went wrong while loading API keys." }), _jsx("p", { className: "m-0", children: "Please refresh the page and try again." })] })) : (_jsx(ApiKeysTable, { keys: keys, onToggleActive: handleToggle, isTogglingId: togglingId }))] }), _jsx(GenerateApiKeyModal, { open: showGenerate, onClose: function () { return setShowGenerate(false); }, onCreated: function (apiKey, keyName) {
134
+ setShowGenerate(false);
135
+ setRevealKey({ apiKey: apiKey, keyName: keyName });
136
+ } }), revealKey && (_jsx(RevealApiKeyModal, { open: true, apiKey: revealKey.apiKey, keyName: revealKey.keyName, onClose: function () { return setRevealKey(null); } }))] }));
137
+ }
138
+ //# sourceMappingURL=ApiKeysTab.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApiKeysTab.js","sourceRoot":"","sources":["../../../../../src/components/core/users/ApiKeysTab/ApiKeysTab.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useState } from \"react\";\nimport { useDispatch } from \"react-redux\";\nimport { KeyRound, Plus } from \"lucide-react\";\nimport { SolidButton, SolidSpinner, SolidSwitch, SolidTag } from \"../../../shad-cn-ui\";\nimport { showToast } from \"../../../../redux/features/toastSlice\";\nimport {\n useGetUserApiKeysQuery,\n useUpdateApiKeyMutation,\n type ApiKeyRecord,\n} from \"../../../../redux/api/apiKeyApi\";\nimport { GenerateApiKeyModal } from \"./GenerateApiKeyModal\";\nimport { RevealApiKeyModal } from \"./RevealApiKeyModal\";\n\nfunction formatDate(iso: string | null): string {\n if (!iso) return \"—\";\n return new Date(iso).toLocaleDateString(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n });\n}\n\nfunction getExpiryStatus(expiresAt: string | null): \"expired\" | \"expiring-soon\" | \"ok\" | \"never\" {\n if (!expiresAt) return \"never\";\n const diff = new Date(expiresAt).getTime() - Date.now();\n if (diff < 0) return \"expired\";\n if (diff < 7 * 24 * 60 * 60 * 1000) return \"expiring-soon\";\n return \"ok\";\n}\n\nfunction ApiKeysTable({\n keys,\n onToggleActive,\n isTogglingId,\n}: {\n keys: ApiKeyRecord[];\n onToggleActive: (key: ApiKeyRecord) => void;\n isTogglingId: string | null;\n}) {\n if (keys.length === 0) {\n return (\n <div\n className=\"flex flex-column align-items-center justify-content-center gap-3 py-6\"\n style={{ color: \"var(--solid-text-secondary, #888)\" }}\n >\n <KeyRound size={32} strokeWidth={1.5} />\n <div className=\"text-center\">\n <p className=\"m-0\" style={{ fontSize: 14, fontWeight: 500 }}>\n No API keys\n </p>\n <p className=\"m-0 mt-1\" style={{ fontSize: 12 }}>\n Generate a key to enable programmatic access.\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div style={{ overflowX: \"auto\" }}>\n <table style={{ width: \"100%\", borderCollapse: \"collapse\" }}>\n <thead>\n <tr>\n <th className=\"solid-api-keys-th\">Name</th>\n <th className=\"solid-api-keys-th\">Key</th>\n <th className=\"solid-api-keys-th\">Status</th>\n <th className=\"solid-api-keys-th\">Expires</th>\n <th className=\"solid-api-keys-th\">Last Used</th>\n <th className=\"solid-api-keys-th\">Active</th>\n </tr>\n </thead>\n <tbody>\n {keys.map((key) => {\n const expiryStatus = getExpiryStatus(key.expiresAt);\n const isExpired = expiryStatus === \"expired\";\n\n return (\n <tr key={key.id} className={isExpired ? \"solid-api-keys-row--expired\" : undefined}>\n <td className=\"solid-api-keys-td\">\n <span style={{ fontWeight: 500 }}>{key.name}</span>\n </td>\n\n <td className=\"solid-api-keys-td\">\n <code\n style={{\n fontFamily: \"monospace\",\n fontSize: 13,\n background: \"var(--solid-surface-secondary, #f5f5f5)\",\n padding: \"2px 6px\",\n borderRadius: 4,\n }}\n >\n {key.maskedKey}\n </code>\n </td>\n\n <td className=\"solid-api-keys-td\">\n {isExpired ? (\n <SolidTag tone=\"danger\">Expired</SolidTag>\n ) : !key.isActive ? (\n <SolidTag tone=\"warn\">Inactive</SolidTag>\n ) : expiryStatus === \"expiring-soon\" ? (\n <SolidTag tone=\"warn\">Expiring soon</SolidTag>\n ) : (\n <SolidTag tone=\"success\">Active</SolidTag>\n )}\n </td>\n\n <td\n className=\"solid-api-keys-td\"\n style={{\n color:\n expiryStatus === \"expired\"\n ? \"var(--solid-danger-color, #ef4444)\"\n : expiryStatus === \"expiring-soon\"\n ? \"var(--solid-warn-color, #f59e0b)\"\n : undefined,\n }}\n >\n {expiryStatus === \"never\" ? (\n <span style={{ color: \"var(--solid-text-secondary, #888)\" }}>Never</span>\n ) : (\n formatDate(key.expiresAt)\n )}\n </td>\n\n <td className=\"solid-api-keys-td\" style={{ color: \"var(--solid-text-secondary, #888)\" }}>\n {formatDate(key.lastUsedAt)}\n </td>\n\n <td className=\"solid-api-keys-td\">\n {isTogglingId === key.id ? (\n <SolidSpinner />\n ) : (\n <SolidSwitch\n checked={key.isActive}\n disabled={isExpired}\n onChange={() => onToggleActive(key)}\n />\n )}\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n );\n}\n\ninterface ApiKeysTabProps {\n userId: string;\n canCreate?: boolean;\n}\n\nexport function ApiKeysTab({ userId, canCreate = false }: ApiKeysTabProps) {\n const dispatch = useDispatch();\n const { data, isLoading, isError } = useGetUserApiKeysQuery(userId);\n const [updateApiKey] = useUpdateApiKeyMutation();\n const [togglingId, setTogglingId] = useState<string | null>(null);\n const [showGenerate, setShowGenerate] = useState(false);\n const [revealKey, setRevealKey] = useState<{ apiKey: string; keyName: string } | null>(null);\n\n const keys: ApiKeyRecord[] = data?.data?.apiKeys ?? [];\n\n const handleToggle = async (key: ApiKeyRecord) => {\n setTogglingId(key.id);\n try {\n await updateApiKey({ id: key.id, isActive: !key.isActive }).unwrap();\n dispatch(\n showToast({\n severity: \"success\",\n summary: \"Updated\",\n detail: `API key \"${key.name}\" ${!key.isActive ? \"activated\" : \"deactivated\"}.`,\n })\n );\n } catch (err: any) {\n dispatch(\n showToast({\n severity: \"error\",\n summary: \"Error\",\n detail: err?.data?.message || \"Failed to update API key.\",\n })\n );\n } finally {\n setTogglingId(null);\n }\n };\n\n return (\n <>\n <div className=\"solid-api-keys-tab\">\n <div className=\"flex align-items-center justify-content-between mb-4\">\n <div>\n <p className=\"m-0\" style={{ fontWeight: 600, fontSize: 14 }}>\n API Keys\n </p>\n <p className=\"m-0 mt-1\" style={{ fontSize: 12, color: \"var(--solid-text-secondary, #888)\" }}>\n Keys grant programmatic access. Store them securely — they are shown only once.\n </p>\n </div>\n {canCreate && (\n <SolidButton size=\"small\" type=\"button\" onClick={() => setShowGenerate(true)}>\n <Plus size={14} />\n Generate Key\n </SolidButton>\n )}\n </div>\n\n {isLoading ? (\n <div className=\"flex justify-content-center py-5\">\n <SolidSpinner />\n </div>\n ) : isError && keys.length === 0 ? (\n <div\n className=\"flex flex-column align-items-center justify-content-center gap-2 py-5\"\n style={{ color: \"var(--solid-text-secondary, #888)\", fontSize: 13 }}\n >\n <p className=\"m-0\">Something went wrong while loading API keys.</p>\n <p className=\"m-0\">Please refresh the page and try again.</p>\n </div>\n ) : (\n <ApiKeysTable keys={keys} onToggleActive={handleToggle} isTogglingId={togglingId} />\n )}\n </div>\n\n <GenerateApiKeyModal\n open={showGenerate}\n onClose={() => setShowGenerate(false)}\n onCreated={(apiKey, keyName) => {\n setShowGenerate(false);\n setRevealKey({ apiKey, keyName });\n }}\n />\n\n {revealKey && (\n <RevealApiKeyModal\n open={true}\n apiKey={revealKey.apiKey}\n keyName={revealKey.keyName}\n onClose={() => setRevealKey(null)}\n />\n )}\n </>\n );\n}\n"]}
@@ -0,0 +1,246 @@
1
+ import { useState } from "react";
2
+ import { useDispatch } from "react-redux";
3
+ import { KeyRound, Plus } from "lucide-react";
4
+ import { SolidButton, SolidSpinner, SolidSwitch, SolidTag } from "../../../shad-cn-ui";
5
+ import { showToast } from "../../../../redux/features/toastSlice";
6
+ import {
7
+ useGetUserApiKeysQuery,
8
+ useUpdateApiKeyMutation,
9
+ type ApiKeyRecord,
10
+ } from "../../../../redux/api/apiKeyApi";
11
+ import { GenerateApiKeyModal } from "./GenerateApiKeyModal";
12
+ import { RevealApiKeyModal } from "./RevealApiKeyModal";
13
+
14
+ function formatDate(iso: string | null): string {
15
+ if (!iso) return "—";
16
+ return new Date(iso).toLocaleDateString(undefined, {
17
+ year: "numeric",
18
+ month: "short",
19
+ day: "numeric",
20
+ });
21
+ }
22
+
23
+ function getExpiryStatus(expiresAt: string | null): "expired" | "expiring-soon" | "ok" | "never" {
24
+ if (!expiresAt) return "never";
25
+ const diff = new Date(expiresAt).getTime() - Date.now();
26
+ if (diff < 0) return "expired";
27
+ if (diff < 7 * 24 * 60 * 60 * 1000) return "expiring-soon";
28
+ return "ok";
29
+ }
30
+
31
+ function ApiKeysTable({
32
+ keys,
33
+ onToggleActive,
34
+ isTogglingId,
35
+ }: {
36
+ keys: ApiKeyRecord[];
37
+ onToggleActive: (key: ApiKeyRecord) => void;
38
+ isTogglingId: string | null;
39
+ }) {
40
+ if (keys.length === 0) {
41
+ return (
42
+ <div
43
+ className="flex flex-column align-items-center justify-content-center gap-3 py-6"
44
+ style={{ color: "var(--solid-text-secondary, #888)" }}
45
+ >
46
+ <KeyRound size={32} strokeWidth={1.5} />
47
+ <div className="text-center">
48
+ <p className="m-0" style={{ fontSize: 14, fontWeight: 500 }}>
49
+ No API keys
50
+ </p>
51
+ <p className="m-0 mt-1" style={{ fontSize: 12 }}>
52
+ Generate a key to enable programmatic access.
53
+ </p>
54
+ </div>
55
+ </div>
56
+ );
57
+ }
58
+
59
+ return (
60
+ <div style={{ overflowX: "auto" }}>
61
+ <table style={{ width: "100%", borderCollapse: "collapse" }}>
62
+ <thead>
63
+ <tr>
64
+ <th className="solid-api-keys-th">Name</th>
65
+ <th className="solid-api-keys-th">Key</th>
66
+ <th className="solid-api-keys-th">Status</th>
67
+ <th className="solid-api-keys-th">Expires</th>
68
+ <th className="solid-api-keys-th">Last Used</th>
69
+ <th className="solid-api-keys-th">Active</th>
70
+ </tr>
71
+ </thead>
72
+ <tbody>
73
+ {keys.map((key) => {
74
+ const expiryStatus = getExpiryStatus(key.expiresAt);
75
+ const isExpired = expiryStatus === "expired";
76
+
77
+ return (
78
+ <tr key={key.id} className={isExpired ? "solid-api-keys-row--expired" : undefined}>
79
+ <td className="solid-api-keys-td">
80
+ <span style={{ fontWeight: 500 }}>{key.name}</span>
81
+ </td>
82
+
83
+ <td className="solid-api-keys-td">
84
+ <code
85
+ style={{
86
+ fontFamily: "monospace",
87
+ fontSize: 13,
88
+ background: "var(--solid-surface-secondary, #f5f5f5)",
89
+ padding: "2px 6px",
90
+ borderRadius: 4,
91
+ }}
92
+ >
93
+ {key.maskedKey}
94
+ </code>
95
+ </td>
96
+
97
+ <td className="solid-api-keys-td">
98
+ {isExpired ? (
99
+ <SolidTag tone="danger">Expired</SolidTag>
100
+ ) : !key.isActive ? (
101
+ <SolidTag tone="warn">Inactive</SolidTag>
102
+ ) : expiryStatus === "expiring-soon" ? (
103
+ <SolidTag tone="warn">Expiring soon</SolidTag>
104
+ ) : (
105
+ <SolidTag tone="success">Active</SolidTag>
106
+ )}
107
+ </td>
108
+
109
+ <td
110
+ className="solid-api-keys-td"
111
+ style={{
112
+ color:
113
+ expiryStatus === "expired"
114
+ ? "var(--solid-danger-color, #ef4444)"
115
+ : expiryStatus === "expiring-soon"
116
+ ? "var(--solid-warn-color, #f59e0b)"
117
+ : undefined,
118
+ }}
119
+ >
120
+ {expiryStatus === "never" ? (
121
+ <span style={{ color: "var(--solid-text-secondary, #888)" }}>Never</span>
122
+ ) : (
123
+ formatDate(key.expiresAt)
124
+ )}
125
+ </td>
126
+
127
+ <td className="solid-api-keys-td" style={{ color: "var(--solid-text-secondary, #888)" }}>
128
+ {formatDate(key.lastUsedAt)}
129
+ </td>
130
+
131
+ <td className="solid-api-keys-td">
132
+ {isTogglingId === key.id ? (
133
+ <SolidSpinner />
134
+ ) : (
135
+ <SolidSwitch
136
+ checked={key.isActive}
137
+ disabled={isExpired}
138
+ onChange={() => onToggleActive(key)}
139
+ />
140
+ )}
141
+ </td>
142
+ </tr>
143
+ );
144
+ })}
145
+ </tbody>
146
+ </table>
147
+ </div>
148
+ );
149
+ }
150
+
151
+ interface ApiKeysTabProps {
152
+ userId: string;
153
+ canCreate?: boolean;
154
+ }
155
+
156
+ export function ApiKeysTab({ userId, canCreate = false }: ApiKeysTabProps) {
157
+ const dispatch = useDispatch();
158
+ const { data, isLoading, isError } = useGetUserApiKeysQuery(userId);
159
+ const [updateApiKey] = useUpdateApiKeyMutation();
160
+ const [togglingId, setTogglingId] = useState<string | null>(null);
161
+ const [showGenerate, setShowGenerate] = useState(false);
162
+ const [revealKey, setRevealKey] = useState<{ apiKey: string; keyName: string } | null>(null);
163
+
164
+ const keys: ApiKeyRecord[] = data?.data?.apiKeys ?? [];
165
+
166
+ const handleToggle = async (key: ApiKeyRecord) => {
167
+ setTogglingId(key.id);
168
+ try {
169
+ await updateApiKey({ id: key.id, isActive: !key.isActive }).unwrap();
170
+ dispatch(
171
+ showToast({
172
+ severity: "success",
173
+ summary: "Updated",
174
+ detail: `API key "${key.name}" ${!key.isActive ? "activated" : "deactivated"}.`,
175
+ })
176
+ );
177
+ } catch (err: any) {
178
+ dispatch(
179
+ showToast({
180
+ severity: "error",
181
+ summary: "Error",
182
+ detail: err?.data?.message || "Failed to update API key.",
183
+ })
184
+ );
185
+ } finally {
186
+ setTogglingId(null);
187
+ }
188
+ };
189
+
190
+ return (
191
+ <>
192
+ <div className="solid-api-keys-tab">
193
+ <div className="flex align-items-center justify-content-between mb-4">
194
+ <div>
195
+ <p className="m-0" style={{ fontWeight: 600, fontSize: 14 }}>
196
+ API Keys
197
+ </p>
198
+ <p className="m-0 mt-1" style={{ fontSize: 12, color: "var(--solid-text-secondary, #888)" }}>
199
+ Keys grant programmatic access. Store them securely — they are shown only once.
200
+ </p>
201
+ </div>
202
+ {canCreate && (
203
+ <SolidButton size="small" type="button" onClick={() => setShowGenerate(true)}>
204
+ <Plus size={14} />
205
+ Generate Key
206
+ </SolidButton>
207
+ )}
208
+ </div>
209
+
210
+ {isLoading ? (
211
+ <div className="flex justify-content-center py-5">
212
+ <SolidSpinner />
213
+ </div>
214
+ ) : isError && keys.length === 0 ? (
215
+ <div
216
+ className="flex flex-column align-items-center justify-content-center gap-2 py-5"
217
+ style={{ color: "var(--solid-text-secondary, #888)", fontSize: 13 }}
218
+ >
219
+ <p className="m-0">Something went wrong while loading API keys.</p>
220
+ <p className="m-0">Please refresh the page and try again.</p>
221
+ </div>
222
+ ) : (
223
+ <ApiKeysTable keys={keys} onToggleActive={handleToggle} isTogglingId={togglingId} />
224
+ )}
225
+ </div>
226
+
227
+ <GenerateApiKeyModal
228
+ open={showGenerate}
229
+ onClose={() => setShowGenerate(false)}
230
+ onCreated={(apiKey, keyName) => {
231
+ setShowGenerate(false);
232
+ setRevealKey({ apiKey, keyName });
233
+ }}
234
+ />
235
+
236
+ {revealKey && (
237
+ <RevealApiKeyModal
238
+ open={true}
239
+ apiKey={revealKey.apiKey}
240
+ keyName={revealKey.keyName}
241
+ onClose={() => setRevealKey(null)}
242
+ />
243
+ )}
244
+ </>
245
+ );
246
+ }
@@ -0,0 +1,8 @@
1
+ interface GenerateApiKeyModalProps {
2
+ open: boolean;
3
+ onClose: () => void;
4
+ onCreated: (rawApiKey: string, keyName: string) => void;
5
+ }
6
+ export declare function GenerateApiKeyModal({ open, onClose, onCreated }: GenerateApiKeyModalProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
8
+ //# sourceMappingURL=GenerateApiKeyModal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GenerateApiKeyModal.d.ts","sourceRoot":"","sources":["../../../../../src/components/core/users/ApiKeysTab/GenerateApiKeyModal.tsx"],"names":[],"mappings":"AAmDA,UAAU,wBAAwB;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACzD;AAED,wBAAgB,mBAAmB,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,wBAAwB,2CA8HzF"}
@@ -0,0 +1,156 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
13
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
14
+ return new (P || (P = Promise))(function (resolve, reject) {
15
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
16
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
17
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
18
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
19
+ });
20
+ };
21
+ var __generator = (this && this.__generator) || function (thisArg, body) {
22
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
23
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
24
+ function verb(n) { return function (v) { return step([n, v]); }; }
25
+ function step(op) {
26
+ if (f) throw new TypeError("Generator is already executing.");
27
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
28
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
29
+ if (y = 0, t) op = [op[0] & 2, t.value];
30
+ switch (op[0]) {
31
+ case 0: case 1: t = op; break;
32
+ case 4: _.label++; return { value: op[1], done: false };
33
+ case 5: _.label++; y = op[1]; op = [0]; continue;
34
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
35
+ default:
36
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
37
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
38
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
39
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
40
+ if (t[2]) _.ops.pop();
41
+ _.trys.pop(); continue;
42
+ }
43
+ op = body.call(thisArg, _);
44
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
45
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
46
+ }
47
+ };
48
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
49
+ import { useEffect, useState } from "react";
50
+ import { useDispatch } from "react-redux";
51
+ import { useFormik } from "formik";
52
+ import * as Yup from "yup";
53
+ import { SolidButton, SolidDatePicker, SolidDialog, SolidDialogBody, SolidDialogFooter, SolidDialogHeader, SolidDialogSeparator, SolidDialogTitle, SolidInput, SolidMessage, SolidSelect, } from "../../../shad-cn-ui";
54
+ import { showToast } from "../../../../redux/features/toastSlice";
55
+ import { useCreateApiKeyMutation } from "../../../../redux/api/apiKeyApi";
56
+ var EXPIRY_OPTIONS = [
57
+ { label: "30 days", value: "30d" },
58
+ { label: "60 days", value: "60d" },
59
+ { label: "90 days", value: "90d" },
60
+ { label: "Never", value: "never" },
61
+ { label: "Custom date", value: "custom" },
62
+ ];
63
+ function addDays(days) {
64
+ var d = new Date();
65
+ d.setDate(d.getDate() + days);
66
+ return d.toISOString();
67
+ }
68
+ function resolveExpiresAt(expiryOption, customDate) {
69
+ switch (expiryOption) {
70
+ case "30d":
71
+ return addDays(30);
72
+ case "60d":
73
+ return addDays(60);
74
+ case "90d":
75
+ return addDays(90);
76
+ case "never":
77
+ return undefined;
78
+ case "custom":
79
+ return customDate ? customDate.toISOString() : undefined;
80
+ default:
81
+ return undefined;
82
+ }
83
+ }
84
+ export function GenerateApiKeyModal(_a) {
85
+ var _this = this;
86
+ var open = _a.open, onClose = _a.onClose, onCreated = _a.onCreated;
87
+ var dispatch = useDispatch();
88
+ var _b = useCreateApiKeyMutation(), createApiKey = _b[0], isLoading = _b[1].isLoading;
89
+ var _c = useState(null), customDate = _c[0], setCustomDate = _c[1];
90
+ var formik = useFormik({
91
+ initialValues: {
92
+ name: "",
93
+ expiryOption: "30d",
94
+ },
95
+ validationSchema: Yup.object({
96
+ name: Yup.string()
97
+ .trim()
98
+ .min(2, "Name must be at least 2 characters")
99
+ .max(64, "Name must be 64 characters or fewer")
100
+ .required("Key name is required"),
101
+ expiryOption: Yup.string().required("Expiry is required"),
102
+ }),
103
+ onSubmit: function (values, helpers) { return __awaiter(_this, void 0, void 0, function () {
104
+ var expiresAt, response, err_1, detail;
105
+ var _a;
106
+ return __generator(this, function (_b) {
107
+ switch (_b.label) {
108
+ case 0:
109
+ if (values.expiryOption === "custom" && !customDate) {
110
+ helpers.setFieldError("expiryOption", "Please select a custom expiry date");
111
+ return [2 /*return*/];
112
+ }
113
+ expiresAt = resolveExpiresAt(values.expiryOption, customDate);
114
+ _b.label = 1;
115
+ case 1:
116
+ _b.trys.push([1, 3, , 4]);
117
+ return [4 /*yield*/, createApiKey(__assign({ name: values.name.trim() }, (expiresAt ? { expiresAt: expiresAt } : {}))).unwrap()];
118
+ case 2:
119
+ response = _b.sent();
120
+ onCreated(response.data.apiKey, values.name.trim());
121
+ handleClose();
122
+ return [3 /*break*/, 4];
123
+ case 3:
124
+ err_1 = _b.sent();
125
+ detail = ((_a = err_1 === null || err_1 === void 0 ? void 0 : err_1.data) === null || _a === void 0 ? void 0 : _a.message) || "Failed to generate API key. Please try again.";
126
+ dispatch(showToast({ severity: "error", summary: "Error", detail: detail }));
127
+ return [3 /*break*/, 4];
128
+ case 4: return [2 /*return*/];
129
+ }
130
+ });
131
+ }); },
132
+ });
133
+ var handleClose = function () {
134
+ formik.resetForm();
135
+ setCustomDate(null);
136
+ onClose();
137
+ };
138
+ // Reset form when modal opens
139
+ useEffect(function () {
140
+ if (open) {
141
+ formik.resetForm();
142
+ setCustomDate(null);
143
+ }
144
+ }, [open]);
145
+ var nameError = formik.touched.name && formik.errors.name ? String(formik.errors.name) : "";
146
+ var expiryError = formik.touched.expiryOption && formik.errors.expiryOption
147
+ ? String(formik.errors.expiryOption)
148
+ : "";
149
+ return (_jsxs(SolidDialog, { open: open, onOpenChange: handleClose, style: { maxWidth: 480 }, children: [_jsx(SolidDialogHeader, { children: _jsx(SolidDialogTitle, { children: "Generate API Key" }) }), _jsx(SolidDialogSeparator, {}), _jsx(SolidDialogBody, { children: _jsx("form", { id: "generate-api-key-form", onSubmit: formik.handleSubmit, children: _jsxs("div", { className: "flex flex-column gap-3", children: [_jsxs("div", { className: "flex flex-column gap-2", children: [_jsxs("label", { htmlFor: "api-key-name", className: "form-field-label", children: ["Key Name ", _jsx("span", { style: { color: "var(--solid-danger-color, #ef4444)" }, children: "*" })] }), _jsx(SolidInput, { id: "api-key-name", name: "name", autoComplete: "off", placeholder: "e.g. CI/CD pipeline, Mobile app", value: formik.values.name, onChange: formik.handleChange, onBlur: formik.handleBlur }), nameError && _jsx(SolidMessage, { severity: "error", text: nameError })] }), _jsxs("div", { className: "flex flex-column gap-2", children: [_jsx("label", { htmlFor: "api-key-expiry", className: "form-field-label", children: "Expires" }), _jsx(SolidSelect, { value: formik.values.expiryOption, options: EXPIRY_OPTIONS, optionLabel: "label", optionValue: "value", onChange: function (_a) {
150
+ var value = _a.value;
151
+ formik.setFieldValue("expiryOption", value);
152
+ if (value !== "custom")
153
+ setCustomDate(null);
154
+ } }), formik.values.expiryOption === "custom" && (_jsx(SolidDatePicker, { selected: customDate, onChange: function (date) { return setCustomDate(date); }, minDate: new Date(), placeholderText: "Select expiry date", dateFormat: "dd/MM/yyyy" })), expiryError && _jsx(SolidMessage, { severity: "error", text: expiryError })] })] }) }) }), _jsx(SolidDialogSeparator, {}), _jsxs(SolidDialogFooter, { children: [_jsx(SolidButton, { variant: "outline", type: "button", onClick: handleClose, disabled: isLoading, children: "Cancel" }), _jsx(SolidButton, { type: "submit", form: "generate-api-key-form", loading: isLoading, children: "Generate Key" })] })] }));
155
+ }
156
+ //# sourceMappingURL=GenerateApiKeyModal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GenerateApiKeyModal.js","sourceRoot":"","sources":["../../../../../src/components/core/users/ApiKeysTab/GenerateApiKeyModal.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useEffect, useState } from \"react\";\nimport { useDispatch } from \"react-redux\";\nimport { useFormik } from \"formik\";\nimport * as Yup from \"yup\";\nimport {\n SolidButton,\n SolidDatePicker,\n SolidDialog,\n SolidDialogBody,\n SolidDialogFooter,\n SolidDialogHeader,\n SolidDialogSeparator,\n SolidDialogTitle,\n SolidInput,\n SolidMessage,\n SolidSelect,\n} from \"../../../shad-cn-ui\";\nimport { showToast } from \"../../../../redux/features/toastSlice\";\nimport { useCreateApiKeyMutation } from \"../../../../redux/api/apiKeyApi\";\n\nconst EXPIRY_OPTIONS = [\n { label: \"30 days\", value: \"30d\" },\n { label: \"60 days\", value: \"60d\" },\n { label: \"90 days\", value: \"90d\" },\n { label: \"Never\", value: \"never\" },\n { label: \"Custom date\", value: \"custom\" },\n];\n\nfunction addDays(days: number): string {\n const d = new Date();\n d.setDate(d.getDate() + days);\n return d.toISOString();\n}\n\nfunction resolveExpiresAt(expiryOption: string, customDate: Date | null): string | undefined {\n switch (expiryOption) {\n case \"30d\":\n return addDays(30);\n case \"60d\":\n return addDays(60);\n case \"90d\":\n return addDays(90);\n case \"never\":\n return undefined;\n case \"custom\":\n return customDate ? customDate.toISOString() : undefined;\n default:\n return undefined;\n }\n}\n\ninterface GenerateApiKeyModalProps {\n open: boolean;\n onClose: () => void;\n onCreated: (rawApiKey: string, keyName: string) => void;\n}\n\nexport function GenerateApiKeyModal({ open, onClose, onCreated }: GenerateApiKeyModalProps) {\n const dispatch = useDispatch();\n const [createApiKey, { isLoading }] = useCreateApiKeyMutation();\n const [customDate, setCustomDate] = useState<Date | null>(null);\n\n const formik = useFormik({\n initialValues: {\n name: \"\",\n expiryOption: \"30d\",\n },\n validationSchema: Yup.object({\n name: Yup.string()\n .trim()\n .min(2, \"Name must be at least 2 characters\")\n .max(64, \"Name must be 64 characters or fewer\")\n .required(\"Key name is required\"),\n expiryOption: Yup.string().required(\"Expiry is required\"),\n }),\n onSubmit: async (values, helpers) => {\n if (values.expiryOption === \"custom\" && !customDate) {\n helpers.setFieldError(\"expiryOption\", \"Please select a custom expiry date\");\n return;\n }\n\n const expiresAt = resolveExpiresAt(values.expiryOption, customDate);\n\n try {\n const response = await createApiKey({\n name: values.name.trim(),\n ...(expiresAt ? { expiresAt } : {}),\n }).unwrap();\n\n onCreated(response.data.apiKey, values.name.trim());\n handleClose();\n } catch (err: any) {\n const detail = err?.data?.message || \"Failed to generate API key. Please try again.\";\n dispatch(showToast({ severity: \"error\", summary: \"Error\", detail }));\n }\n },\n });\n\n const handleClose = () => {\n formik.resetForm();\n setCustomDate(null);\n onClose();\n };\n\n // Reset form when modal opens\n useEffect(() => {\n if (open) {\n formik.resetForm();\n setCustomDate(null);\n }\n }, [open]);\n\n const nameError =\n formik.touched.name && formik.errors.name ? String(formik.errors.name) : \"\";\n const expiryError =\n formik.touched.expiryOption && formik.errors.expiryOption\n ? String(formik.errors.expiryOption)\n : \"\";\n\n return (\n <SolidDialog open={open} onOpenChange={handleClose} style={{ maxWidth: 480 }}>\n <SolidDialogHeader>\n <SolidDialogTitle>Generate API Key</SolidDialogTitle>\n </SolidDialogHeader>\n <SolidDialogSeparator />\n <SolidDialogBody>\n <form id=\"generate-api-key-form\" onSubmit={formik.handleSubmit}>\n <div className=\"flex flex-column gap-3\">\n <div className=\"flex flex-column gap-2\">\n <label htmlFor=\"api-key-name\" className=\"form-field-label\">\n Key Name <span style={{ color: \"var(--solid-danger-color, #ef4444)\" }}>*</span>\n </label>\n <SolidInput\n id=\"api-key-name\"\n name=\"name\"\n autoComplete=\"off\"\n placeholder=\"e.g. CI/CD pipeline, Mobile app\"\n value={formik.values.name}\n onChange={formik.handleChange}\n onBlur={formik.handleBlur}\n />\n {nameError && <SolidMessage severity=\"error\" text={nameError} />}\n </div>\n\n <div className=\"flex flex-column gap-2\">\n <label htmlFor=\"api-key-expiry\" className=\"form-field-label\">\n Expires\n </label>\n <SolidSelect\n value={formik.values.expiryOption}\n options={EXPIRY_OPTIONS}\n optionLabel=\"label\"\n optionValue=\"value\"\n onChange={({ value }) => {\n formik.setFieldValue(\"expiryOption\", value);\n if (value !== \"custom\") setCustomDate(null);\n }}\n />\n {formik.values.expiryOption === \"custom\" && (\n <SolidDatePicker\n selected={customDate}\n onChange={(date: Date | null) => setCustomDate(date)}\n minDate={new Date()}\n placeholderText=\"Select expiry date\"\n dateFormat=\"dd/MM/yyyy\"\n />\n )}\n {expiryError && <SolidMessage severity=\"error\" text={expiryError} />}\n </div>\n </div>\n </form>\n </SolidDialogBody>\n <SolidDialogSeparator />\n <SolidDialogFooter>\n <SolidButton variant=\"outline\" type=\"button\" onClick={handleClose} disabled={isLoading}>\n Cancel\n </SolidButton>\n <SolidButton type=\"submit\" form=\"generate-api-key-form\" loading={isLoading}>\n Generate Key\n </SolidButton>\n </SolidDialogFooter>\n </SolidDialog>\n );\n}\n"]}