@riligar/elysia-sqlite 1.1.0 → 1.1.5

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 (73) hide show
  1. package/package.json +6 -3
  2. package/src/index.js +7 -1
  3. package/src/ui/dist/assets/_baseUniq-CtjpPx06.js +1 -0
  4. package/src/ui/dist/assets/arc-BFeA2lFC.js +1 -0
  5. package/src/ui/dist/assets/architectureDiagram-VXUJARFQ-cJh390m5.js +36 -0
  6. package/src/ui/dist/assets/blockDiagram-VD42YOAC-BhDUcRai.js +122 -0
  7. package/src/ui/dist/assets/c4Diagram-YG6GDRKO-CMTMP654.js +10 -0
  8. package/src/ui/dist/assets/channel-DVo75U_z.js +1 -0
  9. package/src/ui/dist/assets/chunk-4BX2VUAB-hf_dt5BT.js +1 -0
  10. package/src/ui/dist/assets/chunk-55IACEB6-xClE-JKq.js +1 -0
  11. package/src/ui/dist/assets/chunk-B4BG7PRW-D6pt13Ec.js +165 -0
  12. package/src/ui/dist/assets/chunk-DI55MBZ5-Dj9Qg80I.js +220 -0
  13. package/src/ui/dist/assets/chunk-FMBD7UC4-1ZlekxIE.js +15 -0
  14. package/src/ui/dist/assets/chunk-QN33PNHL-DAmen2cg.js +1 -0
  15. package/src/ui/dist/assets/chunk-QZHKN3VN-C-3XzKoW.js +1 -0
  16. package/src/ui/dist/assets/chunk-TZMSLE5B-bM4mxnvE.js +1 -0
  17. package/src/ui/dist/assets/classDiagram-2ON5EDUG-DQalC8tR.js +1 -0
  18. package/src/ui/dist/assets/classDiagram-v2-WZHVMYZB-DQalC8tR.js +1 -0
  19. package/src/ui/dist/assets/clone-VeB9KXOY.js +1 -0
  20. package/src/ui/dist/assets/cose-bilkent-S5V4N54A-BxFitB5E.js +1 -0
  21. package/src/ui/dist/assets/cytoscape.esm-BQaXIfA_.js +331 -0
  22. package/src/ui/dist/assets/dagre-6UL2VRFP-DZSkvetS.js +4 -0
  23. package/src/ui/dist/assets/defaultLocale-C4B-KCzX.js +1 -0
  24. package/src/ui/dist/assets/diagram-PSM6KHXK-CYnji4ok.js +24 -0
  25. package/src/ui/dist/assets/diagram-QEK2KX5R-BlF0GwV_.js +43 -0
  26. package/src/ui/dist/assets/diagram-S2PKOQOG-BMf9iQYN.js +24 -0
  27. package/src/ui/dist/assets/erDiagram-Q2GNP2WA-B9nG6AIS.js +60 -0
  28. package/src/ui/dist/assets/flowDiagram-NV44I4VS-cRvIpj4q.js +162 -0
  29. package/src/ui/dist/assets/ganttDiagram-JELNMOA3-D5PauD1v.js +267 -0
  30. package/src/ui/dist/assets/gitGraphDiagram-NY62KEGX-BPtCepD0.js +65 -0
  31. package/src/ui/dist/assets/graph-C9jtFm6q.js +1 -0
  32. package/src/ui/dist/assets/index-D564tWvR.js +525 -0
  33. package/src/ui/dist/assets/index-DXG21mfz.css +1 -0
  34. package/src/ui/dist/assets/infoDiagram-WHAUD3N6-0hbOLvdM.js +2 -0
  35. package/src/ui/dist/assets/init-Gi6I4Gst.js +1 -0
  36. package/src/ui/dist/assets/journeyDiagram-XKPGCS4Q-CBy5upkf.js +139 -0
  37. package/src/ui/dist/assets/kanban-definition-3W4ZIXB7-DrobDgMJ.js +89 -0
  38. package/src/ui/dist/assets/katex-Cu_Erd72.js +261 -0
  39. package/src/ui/dist/assets/layout-DM9qC5Az.js +1 -0
  40. package/src/ui/dist/assets/linear-Fky2CJQk.js +1 -0
  41. package/src/ui/dist/assets/min-Bjr2uZ4W.js +1 -0
  42. package/src/ui/dist/assets/mindmap-definition-VGOIOE7T-CIlFmeVI.js +68 -0
  43. package/src/ui/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  44. package/src/ui/dist/assets/pieDiagram-ADFJNKIX-DMIZSQUQ.js +30 -0
  45. package/src/ui/dist/assets/quadrantDiagram-AYHSOK5B-DBCuVneU.js +7 -0
  46. package/src/ui/dist/assets/requirementDiagram-UZGBJVZJ-Dc87Li5r.js +64 -0
  47. package/src/ui/dist/assets/sankeyDiagram-TZEHDZUN-SnjPjzIj.js +10 -0
  48. package/src/ui/dist/assets/sequenceDiagram-WL72ISMW-CegtRWdp.js +145 -0
  49. package/src/ui/dist/assets/stateDiagram-FKZM4ZOC-DRXLGy91.js +1 -0
  50. package/src/ui/dist/assets/stateDiagram-v2-4FDKWEC3-DfAIcXf2.js +1 -0
  51. package/src/ui/dist/assets/timeline-definition-IT6M3QCI-Dfw0Almx.js +61 -0
  52. package/src/ui/dist/assets/treemap-KMMF4GRG-HxWgHRrr.js +128 -0
  53. package/src/ui/dist/assets/xychartDiagram-PRI3JC2R-C86J7PBK.js +7 -0
  54. package/src/ui/{index.html → dist/index.html} +2 -1
  55. package/src/ui/bun.lock +0 -612
  56. package/src/ui/package.json +0 -29
  57. package/src/ui/postcss.config.cjs +0 -14
  58. package/src/ui/src/App.jsx +0 -2103
  59. package/src/ui/src/components/DataGrid.jsx +0 -122
  60. package/src/ui/src/components/EditableCell.jsx +0 -166
  61. package/src/ui/src/components/ExportButton.jsx +0 -95
  62. package/src/ui/src/components/FKPreview.jsx +0 -106
  63. package/src/ui/src/components/Filter.jsx +0 -302
  64. package/src/ui/src/components/HoldButton.jsx +0 -230
  65. package/src/ui/src/components/Login.jsx +0 -148
  66. package/src/ui/src/components/Onboarding.jsx +0 -127
  67. package/src/ui/src/components/Pagination.jsx +0 -35
  68. package/src/ui/src/components/SecuritySettings.jsx +0 -273
  69. package/src/ui/src/components/TableSelector.jsx +0 -75
  70. package/src/ui/src/hooks/useFilter.js +0 -120
  71. package/src/ui/src/index.css +0 -123
  72. package/src/ui/src/main.jsx +0 -115
  73. package/src/ui/vite.config.js +0 -19
@@ -1,302 +0,0 @@
1
- import { useState } from "react";
2
- import {
3
- TextInput,
4
- Paper,
5
- Group,
6
- Badge,
7
- Stack,
8
- Text,
9
- Chip,
10
- ActionIcon,
11
- Button,
12
- } from "@mantine/core";
13
- import { useClickOutside } from "@mantine/hooks";
14
- import { IconSearch, IconX, IconRefresh } from "@tabler/icons-react";
15
-
16
- export function Filter({
17
- data: { query, activeFilters, logicalOperators },
18
- setData: { setQuery, setActiveFilters, setLogicalOperators },
19
- filterOptions = {},
20
- }) {
21
- const [showMenu, setShowMenu] = useState(false);
22
- const [menuStep, setMenuStep] = useState(null);
23
- const [tempKey, setTempKey] = useState(null);
24
- const [tempOperator, setTempOperator] = useState(null);
25
-
26
- const ref = useClickOutside(() => {
27
- setShowMenu(false);
28
- setMenuStep(null);
29
- setTempKey(null);
30
- setTempOperator(null);
31
- });
32
-
33
- const operators = [
34
- "=",
35
- "!=",
36
- ">",
37
- "<",
38
- ">=",
39
- "<=",
40
- "contains",
41
- "not_contains",
42
- ];
43
-
44
- const getMenuOptions = () => {
45
- if (menuStep === "key") {
46
- return Object.keys(filterOptions);
47
- }
48
- if (menuStep === "operator") {
49
- return operators;
50
- }
51
- if (menuStep === "value" && tempKey) {
52
- if (
53
- Array.isArray(filterOptions[tempKey]) &&
54
- filterOptions[tempKey].length > 0
55
- ) {
56
- return filterOptions[tempKey];
57
- }
58
- return [];
59
- }
60
- return [];
61
- };
62
-
63
- const selectMenuOption = (option) => {
64
- if (menuStep === "key") {
65
- setTempKey(option);
66
- setMenuStep("operator");
67
- } else if (menuStep === "operator") {
68
- setTempOperator(option);
69
- setMenuStep("value");
70
- } else if (menuStep === "value" && tempKey && tempOperator) {
71
- const filterValue = `${tempOperator} ${option}`;
72
- setActiveFilters((prev) => {
73
- const current = prev[tempKey];
74
- const newValue = current ? `${current},${filterValue}` : filterValue;
75
- return {
76
- ...prev,
77
- [tempKey]: newValue,
78
- };
79
- });
80
- closeMenu();
81
- }
82
- };
83
-
84
- const closeMenu = () => {
85
- setShowMenu(false);
86
- setMenuStep(null);
87
- setTempKey(null);
88
- setTempOperator(null);
89
- };
90
-
91
- const removeFilter = (key) => {
92
- setActiveFilters((prev) => {
93
- const updated = { ...prev };
94
- delete updated[key];
95
- return updated;
96
- });
97
- };
98
-
99
- const clearAllFilters = () => {
100
- setActiveFilters({});
101
- setQuery("");
102
- };
103
-
104
- const hasActiveFilters = Object.keys(activeFilters).length > 0;
105
-
106
- return (
107
- <Stack gap="xs">
108
- {/* Unified Search/Filter Input */}
109
- <div style={{ position: "relative" }} ref={ref}>
110
- <TextInput
111
- placeholder="Search or filter..."
112
- leftSection={<IconSearch size={14} />}
113
- rightSection={
114
- query.trim() || hasActiveFilters ? (
115
- <ActionIcon
116
- size="xs"
117
- color="gray"
118
- radius="sm"
119
- variant="subtle"
120
- onClick={clearAllFilters}
121
- >
122
- <IconX size={12} />
123
- </ActionIcon>
124
- ) : null
125
- }
126
- size="sm"
127
- radius="md"
128
- value={query}
129
- onChange={(e) => setQuery(e.currentTarget.value)}
130
- onFocus={() => {
131
- if (!showMenu) {
132
- setMenuStep("key");
133
- setShowMenu(true);
134
- }
135
- }}
136
- styles={{
137
- input: {
138
- border: "1px solid #E5E5E5",
139
- "&:focus": {
140
- borderColor: "#9CA3AF",
141
- },
142
- },
143
- }}
144
- />
145
-
146
- {/* Filter Picker Dropdown */}
147
- {showMenu && menuStep && (
148
- <Paper
149
- shadow="sm"
150
- p="sm"
151
- radius="md"
152
- withBorder
153
- style={{
154
- position: "absolute",
155
- top: "100%",
156
- left: 0,
157
- right: 0,
158
- zIndex: 1000,
159
- marginTop: 4,
160
- }}
161
- >
162
- <Stack gap={8}>
163
- <Text size="xs" fw={600} c="dimmed" tt="uppercase">
164
- {menuStep === "key" && "Filter by column"}
165
- {menuStep === "operator" && `Operator for ${tempKey}`}
166
- {menuStep === "value" && `Value for ${tempKey} ${tempOperator}`}
167
- </Text>
168
- <Group gap={6} wrap="wrap">
169
- {getMenuOptions().map((option, idx) => (
170
- <Chip
171
- key={idx}
172
- onClick={() => selectMenuOption(option)}
173
- variant="light"
174
- checked={false}
175
- size="xs"
176
- radius="md"
177
- styles={{
178
- label: {
179
- cursor: "pointer",
180
- },
181
- }}
182
- >
183
- {option}
184
- </Chip>
185
- ))}
186
- {menuStep === "value" && (
187
- <TextInput
188
- placeholder="Type value..."
189
- size="xs"
190
- radius="md"
191
- autoFocus
192
- style={{ flex: 1, minWidth: 120 }}
193
- onKeyDown={(e) => {
194
- if (e.key === "Enter") {
195
- e.preventDefault();
196
- e.stopPropagation();
197
- selectMenuOption(e.currentTarget.value);
198
- }
199
- }}
200
- />
201
- )}
202
- </Group>
203
- </Stack>
204
- </Paper>
205
- )}
206
- </div>
207
-
208
- {/* Active Filters Display */}
209
- {hasActiveFilters && (
210
- <Group gap={6} wrap="wrap" align="center">
211
- {Object.entries(activeFilters).map(
212
- ([key, valueStr], filterIdx, filterArray) => {
213
- const values = valueStr.split(",").map((v) => v.trim());
214
- const isLastFilter = filterIdx === filterArray.length - 1;
215
- return (
216
- <Group key={key} gap={4} align="center">
217
- <Group gap={2}>
218
- {values.map((value, idx) => (
219
- <Badge
220
- key={`${key}-${idx}`}
221
- size="md"
222
- radius="md"
223
- variant="outline"
224
- color="gray"
225
- pr={4}
226
- rightSection={
227
- <IconX
228
- size={12}
229
- style={{ cursor: "pointer" }}
230
- onClick={() => {
231
- const newValues = values.filter(
232
- (_, i) => i !== idx
233
- );
234
- if (newValues.length === 0) {
235
- removeFilter(key);
236
- } else {
237
- setActiveFilters((prev) => ({
238
- ...prev,
239
- [key]: newValues.join(","),
240
- }));
241
- }
242
- }}
243
- />
244
- }
245
- styles={{
246
- root: {
247
- textTransform: "none",
248
- fontWeight: 500,
249
- },
250
- }}
251
- >
252
- <Text span c="dimmed" size="xs">
253
- {key}
254
- </Text>{" "}
255
- {value}
256
- </Badge>
257
- ))}
258
- </Group>
259
- {!isLastFilter && (
260
- <Button
261
- size="xs"
262
- variant="subtle"
263
- color="gray"
264
- onClick={() => {
265
- setLogicalOperators((prev) => ({
266
- ...prev,
267
- [key]: prev[key] === "OR" ? "AND" : "OR",
268
- }));
269
- }}
270
- title="Toggle AND/OR"
271
- fw={700}
272
- px={4}
273
- h="auto"
274
- py={2}
275
- c="dimmed"
276
- styles={{
277
- label: {
278
- fontSize: "10px",
279
- },
280
- }}
281
- >
282
- {logicalOperators[key] || "AND"}
283
- </Button>
284
- )}
285
- </Group>
286
- );
287
- }
288
- )}
289
- <ActionIcon
290
- size="sm"
291
- variant="subtle"
292
- color="red"
293
- onClick={clearAllFilters}
294
- title="Clear all"
295
- >
296
- <IconX size={14} />
297
- </ActionIcon>
298
- </Group>
299
- )}
300
- </Stack>
301
- );
302
- }
@@ -1,230 +0,0 @@
1
- import { useState, useRef, useCallback, useEffect } from "react";
2
- import {
3
- ActionIcon,
4
- RingProgress,
5
- Tooltip,
6
- Box,
7
- Button,
8
- Loader,
9
- } from "@mantine/core";
10
- import { IconTrash, IconCopy, IconCheck } from "@tabler/icons-react";
11
-
12
- /**
13
- * HoldButton - Botão que requer segurar por um tempo para confirmar ação
14
- * Ideal para ações destrutivas como delete
15
- */
16
- export function HoldButton({
17
- onConfirm,
18
- holdDuration = 3000,
19
- icon: Icon = IconTrash,
20
- color = "red",
21
- size = "md",
22
- tooltip = "Segure para confirmar",
23
- confirmingTooltip = "Segurando...",
24
- ...props
25
- }) {
26
- const [progress, setProgress] = useState(0);
27
- const [isHolding, setIsHolding] = useState(false);
28
- const intervalRef = useRef(null);
29
- const startTimeRef = useRef(null);
30
-
31
- const startHold = useCallback(() => {
32
- if (props.disabled || props.loading) return;
33
-
34
- setIsHolding(true);
35
- startTimeRef.current = Date.now();
36
-
37
- intervalRef.current = setInterval(() => {
38
- const elapsed = Date.now() - startTimeRef.current;
39
- const newProgress = Math.min((elapsed / holdDuration) * 100, 100);
40
- setProgress(newProgress);
41
-
42
- if (newProgress >= 100) {
43
- clearInterval(intervalRef.current);
44
- setIsHolding(false);
45
- setProgress(0);
46
- onConfirm?.();
47
- }
48
- }, 50);
49
- }, [holdDuration, onConfirm, props.disabled, props.loading]);
50
-
51
- const cancelHold = useCallback(() => {
52
- if (intervalRef.current) {
53
- clearInterval(intervalRef.current);
54
- intervalRef.current = null;
55
- }
56
- setIsHolding(false);
57
- setProgress(0);
58
- }, []);
59
-
60
- useEffect(() => {
61
- return () => {
62
- if (intervalRef.current) clearInterval(intervalRef.current);
63
- };
64
- }, []);
65
-
66
- return (
67
- <Tooltip label={isHolding ? confirmingTooltip : tooltip} withArrow>
68
- <Box
69
- style={{
70
- position: "relative",
71
- display: "inline-flex",
72
- verticalAlign: "middle",
73
- }}
74
- >
75
- {isHolding && (
76
- <RingProgress
77
- size={size === "sm" ? 28 : size === "md" ? 34 : 42}
78
- thickness={2}
79
- sections={[{ value: progress, color }]}
80
- style={{
81
- position: "absolute",
82
- top: "50%",
83
- left: "50%",
84
- transform: "translate(-50%, -50%)",
85
- pointerEvents: "none",
86
- }}
87
- />
88
- )}
89
- <ActionIcon
90
- variant="subtle"
91
- color={color}
92
- size={size}
93
- onMouseDown={startHold}
94
- onMouseUp={cancelHold}
95
- onMouseLeave={cancelHold}
96
- onTouchStart={startHold}
97
- onTouchEnd={cancelHold}
98
- style={{
99
- opacity: isHolding ? 0.7 : 1,
100
- transition: "opacity 0.2s",
101
- ...props.style,
102
- }}
103
- {...props}
104
- >
105
- {props.loading ? (
106
- <Loader size={12} color={color} />
107
- ) : (
108
- <Icon size={size === "sm" ? 14 : size === "md" ? 18 : 22} />
109
- )}
110
- </ActionIcon>
111
- </Box>
112
- </Tooltip>
113
- );
114
- }
115
-
116
- /**
117
- * TextHoldButton - Versão em texto do botão de segurar
118
- * Usado para compatibilidade com type="button" ou quando children é passado
119
- */
120
- export function TextHoldButton({
121
- onDelete,
122
- timerSeconds = 3,
123
- children,
124
- loading,
125
- disabled,
126
- variant = "light",
127
- color = "red",
128
- leftSection = <IconTrash size={14} />,
129
- ...buttonProps
130
- }) {
131
- const [isDeleting, setIsDeleting] = useState(false);
132
- const [deleteCountdown, setDeleteCountdown] = useState(timerSeconds);
133
- const deleteTimerRef = useRef(null);
134
- const deleteIntervalRef = useRef(null);
135
-
136
- const startDeleteTimer = () => {
137
- if (loading || disabled) return;
138
- setIsDeleting(true);
139
- setDeleteCountdown(timerSeconds);
140
-
141
- deleteTimerRef.current = setTimeout(() => {
142
- handleDelete();
143
- }, timerSeconds * 1000);
144
-
145
- deleteIntervalRef.current = setInterval(() => {
146
- setDeleteCountdown((prev) => {
147
- const newCount = prev - 1;
148
- if (newCount <= 0) {
149
- clearInterval(deleteIntervalRef.current);
150
- return 0;
151
- }
152
- return newCount;
153
- });
154
- }, 1000);
155
- };
156
-
157
- const stopDeleteTimer = () => {
158
- setIsDeleting(false);
159
- setDeleteCountdown(timerSeconds);
160
- if (deleteTimerRef.current) clearTimeout(deleteTimerRef.current);
161
- if (deleteIntervalRef.current) clearInterval(deleteIntervalRef.current);
162
- };
163
-
164
- const handleDelete = async () => {
165
- stopDeleteTimer();
166
- if (onDelete) await onDelete();
167
- };
168
-
169
- useEffect(() => {
170
- return () => stopDeleteTimer();
171
- }, []);
172
-
173
- return (
174
- <Tooltip
175
- label="Mantenha pressionado até a contagem terminar para confirmar a exclusão"
176
- withArrow
177
- multiline
178
- w={220}
179
- >
180
- <Button
181
- variant={variant}
182
- color={isDeleting ? "orange" : color}
183
- leftSection={leftSection}
184
- onMouseDown={startDeleteTimer}
185
- onMouseUp={stopDeleteTimer}
186
- onMouseLeave={stopDeleteTimer}
187
- onTouchStart={startDeleteTimer}
188
- onTouchEnd={stopDeleteTimer}
189
- disabled={loading || disabled}
190
- style={{
191
- transition: "all 0.2s ease",
192
- transform: isDeleting ? "scale(0.98)" : "scale(1)",
193
- ...buttonProps.style,
194
- }}
195
- {...buttonProps}
196
- >
197
- {isDeleting
198
- ? `Excluindo em ${deleteCountdown}s...`
199
- : children || "Segure para Excluir"}
200
- </Button>
201
- </Tooltip>
202
- );
203
- }
204
-
205
- /**
206
- * Wrapper para manter compatibilidade com Components.Buttons.Delete
207
- */
208
- export function ButtonDelete({
209
- onDelete,
210
- timerSeconds = 3,
211
- type = "button",
212
- ...props
213
- }) {
214
- if (type === "icon") {
215
- return (
216
- <HoldButton
217
- onConfirm={onDelete}
218
- holdDuration={timerSeconds * 1000}
219
- {...props}
220
- />
221
- );
222
- }
223
- return (
224
- <TextHoldButton
225
- onDelete={onDelete}
226
- timerSeconds={timerSeconds}
227
- {...props}
228
- />
229
- );
230
- }
@@ -1,148 +0,0 @@
1
- import { useState } from "react";
2
- import {
3
- Paper,
4
- Title,
5
- Text,
6
- TextInput,
7
- PasswordInput,
8
- Button,
9
- Container,
10
- Group,
11
- Stack,
12
- ThemeIcon,
13
- Box,
14
- Transition,
15
- } from "@mantine/core";
16
- import { IconDatabase, IconLock, IconDeviceMobile } from "@tabler/icons-react";
17
- import { notifications } from "@mantine/notifications";
18
-
19
- export function Login({ onLogin }) {
20
- const [username, setUsername] = useState("");
21
- const [password, setPassword] = useState("");
22
- const [totpCode, setTotpCode] = useState("");
23
- const [showTotp, setShowTotp] = useState(false);
24
- const [loading, setLoading] = useState(false);
25
-
26
- const handleSubmit = async (e) => {
27
- e.preventDefault();
28
- setLoading(true);
29
- try {
30
- const response = await fetch("/admin/auth/login", {
31
- method: "POST",
32
- headers: { "Content-Type": "application/json" },
33
- body: JSON.stringify({ username, password, totpCode }),
34
- });
35
- const data = await response.json();
36
-
37
- if (data.success) {
38
- onLogin();
39
- } else {
40
- if (data.code === "2FA_REQUIRED") {
41
- setShowTotp(true);
42
- notifications.show({
43
- title: "Authentication Required",
44
- message: "Please enter your 2FA code",
45
- color: "blue",
46
- icon: <IconDeviceMobile size={16} />
47
- });
48
- } else {
49
- notifications.show({
50
- title: "Login Failed",
51
- message: data.error || "Invalid credentials",
52
- color: "red",
53
- });
54
- }
55
- }
56
- } catch (error) {
57
- notifications.show({
58
- title: "Error",
59
- message: "Failed to connect to server",
60
- color: "red",
61
- });
62
- }
63
- setLoading(false);
64
- };
65
-
66
- return (
67
- <Box
68
- style={{
69
- height: "100vh",
70
- display: "flex",
71
- alignItems: "center",
72
- justifyContent: "center",
73
- backgroundColor: "#FBFAF8",
74
- }}
75
- >
76
- <Container size={420}>
77
- <Stack align="center" mb="xl">
78
- <ThemeIcon size={60} radius="md" color="dark">
79
- <IconDatabase size={34} />
80
- </ThemeIcon>
81
- <Title order={1} fw={700}>
82
- SQLite Admin Login
83
- </Title>
84
- </Stack>
85
-
86
- <Paper withBorder shadow="md" p={30} radius="md">
87
- <form onSubmit={handleSubmit}>
88
- <Stack>
89
- <TextInput
90
- label="Username"
91
- placeholder="Your username"
92
- required
93
- value={username}
94
- onChange={(e) => setUsername(e.target.value)}
95
- disabled={showTotp}
96
- />
97
- <PasswordInput
98
- label="Password"
99
- placeholder="Your password"
100
- required
101
- value={password}
102
- onChange={(e) => setPassword(e.target.value)}
103
- disabled={showTotp}
104
- />
105
-
106
- {showTotp && (
107
- <TextInput
108
- label="Authenticator Code (2FA)"
109
- placeholder="000 000"
110
- required
111
- autoFocus
112
- value={totpCode}
113
- onChange={(e) => setTotpCode(e.target.value)}
114
- leftSection={<IconDeviceMobile size={16} />}
115
- maxLength={6}
116
- />
117
- )}
118
-
119
- <Group mt="lg">
120
- <Button
121
- type="submit"
122
- color="dark"
123
- fullWidth
124
- loading={loading}
125
- leftSection={<IconLock size={18} />}
126
- >
127
- {showTotp ? "Verify & Login" : "Login"}
128
- </Button>
129
- </Group>
130
-
131
- {showTotp && (
132
- <Text
133
- size="xs"
134
- c="dimmed"
135
- ta="center"
136
- style={{ cursor: 'pointer' }}
137
- onClick={() => { setShowTotp(false); setTotpCode(""); }}
138
- >
139
- Cancel
140
- </Text>
141
- )}
142
- </Stack>
143
- </form>
144
- </Paper>
145
- </Container>
146
- </Box>
147
- );
148
- }