sachnun commited on
Commit
ebe211c
·
1 Parent(s): b2644a4

Update uptime route and dependencies for improved functionality

Browse files
Files changed (3) hide show
  1. app/routes/uptime.tsx +84 -78
  2. package-lock.json +193 -0
  3. package.json +1 -0
app/routes/uptime.tsx CHANGED
@@ -12,6 +12,7 @@ import { Separator } from "~/components/ui/separator";
12
  import { Alert, AlertDescription } from "~/components/ui/alert";
13
  import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "~/components/ui/dialog";
14
  import { Avatar, AvatarFallback } from "~/components/ui/avatar";
 
15
  import { Plus, LogOut, Pencil, Trash2, Activity, Clock, TrendingUp, AlertCircle } from "lucide-react";
16
  import { ThemeToggle } from "~/components/theme-toggle";
17
 
@@ -143,7 +144,7 @@ export function meta({}: Route.MetaArgs) {
143
  export default function Uptime({ loaderData, actionData }: Route.ComponentProps) {
144
  const { stats, user } = loaderData;
145
  const revalidator = useRevalidator();
146
- const [showAddForm, setShowAddForm] = useState(false);
147
  const [editingMonitor, setEditingMonitor] = useState<UptimeStats | null>(null);
148
  const [deletingMonitor, setDeletingMonitor] = useState<string | null>(null);
149
 
@@ -159,7 +160,7 @@ export default function Uptime({ loaderData, actionData }: Route.ComponentProps)
159
  // Close forms on successful action
160
  useEffect(() => {
161
  if (actionData?.success) {
162
- setShowAddForm(false);
163
  setEditingMonitor(null);
164
  setDeletingMonitor(null);
165
  revalidator.revalidate();
@@ -167,8 +168,9 @@ export default function Uptime({ loaderData, actionData }: Route.ComponentProps)
167
  }, [actionData, revalidator]);
168
 
169
  return (
170
- <div className="min-h-screen bg-background">
171
- <div className="container mx-auto px-4 py-8 max-w-7xl">
 
172
  {/* Header */}
173
  <header className="mb-8 flex items-center justify-between">
174
  <div>
@@ -200,73 +202,12 @@ export default function Uptime({ loaderData, actionData }: Route.ComponentProps)
200
 
201
  {/* Add Monitor Button */}
202
  <div className="mb-6">
203
- <Button onClick={() => setShowAddForm(!showAddForm)}>
204
  <Plus className="h-4 w-4 mr-2" />
205
- {showAddForm ? "Cancel" : "Add New Monitor"}
206
  </Button>
207
  </div>
208
 
209
- {/* Add Monitor Form */}
210
- {showAddForm && (
211
- <Card className="mb-6">
212
- <CardHeader>
213
- <CardTitle>Add New Monitor</CardTitle>
214
- <CardDescription>
215
- Start monitoring a new service endpoint
216
- </CardDescription>
217
- </CardHeader>
218
- <CardContent>
219
- {actionData?.error && (
220
- <Alert variant="destructive" className="mb-4">
221
- <AlertCircle className="h-4 w-4" />
222
- <AlertDescription>{actionData.error}</AlertDescription>
223
- </Alert>
224
- )}
225
- <Form method="post" className="space-y-4">
226
- <input type="hidden" name="intent" value="create" />
227
-
228
- <div className="space-y-2">
229
- <Label htmlFor="url">URL to Monitor</Label>
230
- <Input
231
- id="url"
232
- type="url"
233
- name="url"
234
- required
235
- placeholder="https://example.com"
236
- />
237
- </div>
238
-
239
- <div className="space-y-2">
240
- <Label htmlFor="interval">Check Interval (seconds)</Label>
241
- <Input
242
- id="interval"
243
- type="number"
244
- name="interval"
245
- required
246
- min="10"
247
- defaultValue="30"
248
- placeholder="30"
249
- />
250
- <p className="text-xs text-muted-foreground">
251
- Minimum 10 seconds
252
- </p>
253
- </div>
254
-
255
- <div className="flex gap-3">
256
- <Button type="submit">Create Monitor</Button>
257
- <Button
258
- type="button"
259
- variant="outline"
260
- onClick={() => setShowAddForm(false)}
261
- >
262
- Cancel
263
- </Button>
264
- </div>
265
- </Form>
266
- </CardContent>
267
- </Card>
268
- )}
269
-
270
  {stats.length === 0 ? (
271
  <Card>
272
  <CardContent className="pt-6 text-center py-12">
@@ -386,17 +327,26 @@ export default function Uptime({ loaderData, actionData }: Route.ComponentProps)
386
  .slice(-20)
387
  .reverse()
388
  .map((check, idx: number) => (
389
- <div
390
- key={idx}
391
- className={`w-3 h-16 rounded ${
392
- check.status === "up"
393
- ? "bg-green-500"
394
- : "bg-red-500"
395
- }`}
396
- title={`${new Date(check.timestamp).toLocaleString()} - ${
397
- check.status
398
- } (${check.responseTime}ms)`}
399
- />
 
 
 
 
 
 
 
 
 
400
  ))}
401
  </div>
402
  </div>
@@ -408,6 +358,61 @@ export default function Uptime({ loaderData, actionData }: Route.ComponentProps)
408
  </div>
409
  )}
410
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
  {/* Edit Monitor Modal */}
412
  <Dialog open={!!editingMonitor} onOpenChange={() => setEditingMonitor(null)}>
413
  <DialogContent>
@@ -502,5 +507,6 @@ export default function Uptime({ loaderData, actionData }: Route.ComponentProps)
502
  </footer>
503
  </div>
504
  </div>
 
505
  );
506
  }
 
12
  import { Alert, AlertDescription } from "~/components/ui/alert";
13
  import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "~/components/ui/dialog";
14
  import { Avatar, AvatarFallback } from "~/components/ui/avatar";
15
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "~/components/ui/tooltip";
16
  import { Plus, LogOut, Pencil, Trash2, Activity, Clock, TrendingUp, AlertCircle } from "lucide-react";
17
  import { ThemeToggle } from "~/components/theme-toggle";
18
 
 
144
  export default function Uptime({ loaderData, actionData }: Route.ComponentProps) {
145
  const { stats, user } = loaderData;
146
  const revalidator = useRevalidator();
147
+ const [showAddDialog, setShowAddDialog] = useState(false);
148
  const [editingMonitor, setEditingMonitor] = useState<UptimeStats | null>(null);
149
  const [deletingMonitor, setDeletingMonitor] = useState<string | null>(null);
150
 
 
160
  // Close forms on successful action
161
  useEffect(() => {
162
  if (actionData?.success) {
163
+ setShowAddDialog(false);
164
  setEditingMonitor(null);
165
  setDeletingMonitor(null);
166
  revalidator.revalidate();
 
168
  }, [actionData, revalidator]);
169
 
170
  return (
171
+ <TooltipProvider>
172
+ <div className="min-h-screen bg-background">
173
+ <div className="container mx-auto px-4 py-8 max-w-7xl">
174
  {/* Header */}
175
  <header className="mb-8 flex items-center justify-between">
176
  <div>
 
202
 
203
  {/* Add Monitor Button */}
204
  <div className="mb-6">
205
+ <Button onClick={() => setShowAddDialog(true)}>
206
  <Plus className="h-4 w-4 mr-2" />
207
+ Add New Monitor
208
  </Button>
209
  </div>
210
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
  {stats.length === 0 ? (
212
  <Card>
213
  <CardContent className="pt-6 text-center py-12">
 
327
  .slice(-20)
328
  .reverse()
329
  .map((check, idx: number) => (
330
+ <Tooltip key={idx}>
331
+ <TooltipTrigger asChild>
332
+ <div
333
+ className={`w-3 h-16 rounded cursor-pointer transition-opacity hover:opacity-80 ${
334
+ check.status === "up"
335
+ ? "bg-green-500"
336
+ : "bg-red-500"
337
+ }`}
338
+ />
339
+ </TooltipTrigger>
340
+ <TooltipContent>
341
+ <div className="space-y-1">
342
+ <p className="font-semibold">{check.status === "up" ? "Online" : "Offline"}</p>
343
+ <p>{new Date(check.timestamp).toLocaleString()}</p>
344
+ {check.responseTime && <p>Response: {check.responseTime}ms</p>}
345
+ {check.statusCode && <p>Status Code: {check.statusCode}</p>}
346
+ {check.error && <p className="text-red-300">Error: {check.error}</p>}
347
+ </div>
348
+ </TooltipContent>
349
+ </Tooltip>
350
  ))}
351
  </div>
352
  </div>
 
358
  </div>
359
  )}
360
 
361
+ {/* Add Monitor Modal */}
362
+ <Dialog open={showAddDialog} onOpenChange={setShowAddDialog}>
363
+ <DialogContent>
364
+ <DialogHeader>
365
+ <DialogTitle>Add New Monitor</DialogTitle>
366
+ <DialogDescription>
367
+ Start monitoring a new service endpoint
368
+ </DialogDescription>
369
+ </DialogHeader>
370
+ {actionData?.error && (
371
+ <Alert variant="destructive">
372
+ <AlertCircle className="h-4 w-4" />
373
+ <AlertDescription>{actionData.error}</AlertDescription>
374
+ </Alert>
375
+ )}
376
+ <Form method="post" className="space-y-4">
377
+ <input type="hidden" name="intent" value="create" />
378
+
379
+ <div className="space-y-2">
380
+ <Label htmlFor="url">URL to Monitor</Label>
381
+ <Input
382
+ id="url"
383
+ type="url"
384
+ name="url"
385
+ required
386
+ placeholder="https://example.com"
387
+ />
388
+ </div>
389
+
390
+ <div className="space-y-2">
391
+ <Label htmlFor="interval">Check Interval (seconds)</Label>
392
+ <Input
393
+ id="interval"
394
+ type="number"
395
+ name="interval"
396
+ required
397
+ min="10"
398
+ defaultValue="30"
399
+ placeholder="30"
400
+ />
401
+ <p className="text-xs text-muted-foreground">
402
+ Minimum 10 seconds
403
+ </p>
404
+ </div>
405
+
406
+ <DialogFooter>
407
+ <Button type="button" variant="outline" onClick={() => setShowAddDialog(false)}>
408
+ Cancel
409
+ </Button>
410
+ <Button type="submit">Create Monitor</Button>
411
+ </DialogFooter>
412
+ </Form>
413
+ </DialogContent>
414
+ </Dialog>
415
+
416
  {/* Edit Monitor Modal */}
417
  <Dialog open={!!editingMonitor} onOpenChange={() => setEditingMonitor(null)}>
418
  <DialogContent>
 
507
  </footer>
508
  </div>
509
  </div>
510
+ </TooltipProvider>
511
  );
512
  }
package-lock.json CHANGED
@@ -13,6 +13,7 @@
13
  "@radix-ui/react-dialog": "^1.1.15",
14
  "@radix-ui/react-separator": "^1.1.7",
15
  "@radix-ui/react-slot": "^1.2.3",
 
16
  "@react-router/node": "^7.9.2",
17
  "@react-router/serve": "^7.9.2",
18
  "class-variance-authority": "^0.7.1",
@@ -1573,6 +1574,44 @@
1573
  "node": ">=18"
1574
  }
1575
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1576
  "node_modules/@inquirer/ansi": {
1577
  "version": "1.0.1",
1578
  "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.1.tgz",
@@ -3132,6 +3171,29 @@
3132
  "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
3133
  "license": "MIT"
3134
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3135
  "node_modules/@radix-ui/react-avatar": {
3136
  "version": "1.1.10",
3137
  "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz",
@@ -3310,6 +3372,38 @@
3310
  }
3311
  }
3312
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3313
  "node_modules/@radix-ui/react-portal": {
3314
  "version": "1.1.9",
3315
  "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
@@ -3422,6 +3516,40 @@
3422
  }
3423
  }
3424
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3425
  "node_modules/@radix-ui/react-use-callback-ref": {
3426
  "version": "1.1.1",
3427
  "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
@@ -3525,6 +3653,71 @@
3525
  }
3526
  }
3527
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3528
  "node_modules/@react-router/dev": {
3529
  "version": "7.9.4",
3530
  "resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-7.9.4.tgz",
 
13
  "@radix-ui/react-dialog": "^1.1.15",
14
  "@radix-ui/react-separator": "^1.1.7",
15
  "@radix-ui/react-slot": "^1.2.3",
16
+ "@radix-ui/react-tooltip": "^1.2.8",
17
  "@react-router/node": "^7.9.2",
18
  "@react-router/serve": "^7.9.2",
19
  "class-variance-authority": "^0.7.1",
 
1574
  "node": ">=18"
1575
  }
1576
  },
1577
+ "node_modules/@floating-ui/core": {
1578
+ "version": "1.7.3",
1579
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
1580
+ "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
1581
+ "license": "MIT",
1582
+ "dependencies": {
1583
+ "@floating-ui/utils": "^0.2.10"
1584
+ }
1585
+ },
1586
+ "node_modules/@floating-ui/dom": {
1587
+ "version": "1.7.4",
1588
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
1589
+ "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
1590
+ "license": "MIT",
1591
+ "dependencies": {
1592
+ "@floating-ui/core": "^1.7.3",
1593
+ "@floating-ui/utils": "^0.2.10"
1594
+ }
1595
+ },
1596
+ "node_modules/@floating-ui/react-dom": {
1597
+ "version": "2.1.6",
1598
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz",
1599
+ "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==",
1600
+ "license": "MIT",
1601
+ "dependencies": {
1602
+ "@floating-ui/dom": "^1.7.4"
1603
+ },
1604
+ "peerDependencies": {
1605
+ "react": ">=16.8.0",
1606
+ "react-dom": ">=16.8.0"
1607
+ }
1608
+ },
1609
+ "node_modules/@floating-ui/utils": {
1610
+ "version": "0.2.10",
1611
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
1612
+ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
1613
+ "license": "MIT"
1614
+ },
1615
  "node_modules/@inquirer/ansi": {
1616
  "version": "1.0.1",
1617
  "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.1.tgz",
 
3171
  "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
3172
  "license": "MIT"
3173
  },
3174
+ "node_modules/@radix-ui/react-arrow": {
3175
+ "version": "1.1.7",
3176
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
3177
+ "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==",
3178
+ "license": "MIT",
3179
+ "dependencies": {
3180
+ "@radix-ui/react-primitive": "2.1.3"
3181
+ },
3182
+ "peerDependencies": {
3183
+ "@types/react": "*",
3184
+ "@types/react-dom": "*",
3185
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
3186
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
3187
+ },
3188
+ "peerDependenciesMeta": {
3189
+ "@types/react": {
3190
+ "optional": true
3191
+ },
3192
+ "@types/react-dom": {
3193
+ "optional": true
3194
+ }
3195
+ }
3196
+ },
3197
  "node_modules/@radix-ui/react-avatar": {
3198
  "version": "1.1.10",
3199
  "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz",
 
3372
  }
3373
  }
3374
  },
3375
+ "node_modules/@radix-ui/react-popper": {
3376
+ "version": "1.2.8",
3377
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
3378
+ "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==",
3379
+ "license": "MIT",
3380
+ "dependencies": {
3381
+ "@floating-ui/react-dom": "^2.0.0",
3382
+ "@radix-ui/react-arrow": "1.1.7",
3383
+ "@radix-ui/react-compose-refs": "1.1.2",
3384
+ "@radix-ui/react-context": "1.1.2",
3385
+ "@radix-ui/react-primitive": "2.1.3",
3386
+ "@radix-ui/react-use-callback-ref": "1.1.1",
3387
+ "@radix-ui/react-use-layout-effect": "1.1.1",
3388
+ "@radix-ui/react-use-rect": "1.1.1",
3389
+ "@radix-ui/react-use-size": "1.1.1",
3390
+ "@radix-ui/rect": "1.1.1"
3391
+ },
3392
+ "peerDependencies": {
3393
+ "@types/react": "*",
3394
+ "@types/react-dom": "*",
3395
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
3396
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
3397
+ },
3398
+ "peerDependenciesMeta": {
3399
+ "@types/react": {
3400
+ "optional": true
3401
+ },
3402
+ "@types/react-dom": {
3403
+ "optional": true
3404
+ }
3405
+ }
3406
+ },
3407
  "node_modules/@radix-ui/react-portal": {
3408
  "version": "1.1.9",
3409
  "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
 
3516
  }
3517
  }
3518
  },
3519
+ "node_modules/@radix-ui/react-tooltip": {
3520
+ "version": "1.2.8",
3521
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz",
3522
+ "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==",
3523
+ "license": "MIT",
3524
+ "dependencies": {
3525
+ "@radix-ui/primitive": "1.1.3",
3526
+ "@radix-ui/react-compose-refs": "1.1.2",
3527
+ "@radix-ui/react-context": "1.1.2",
3528
+ "@radix-ui/react-dismissable-layer": "1.1.11",
3529
+ "@radix-ui/react-id": "1.1.1",
3530
+ "@radix-ui/react-popper": "1.2.8",
3531
+ "@radix-ui/react-portal": "1.1.9",
3532
+ "@radix-ui/react-presence": "1.1.5",
3533
+ "@radix-ui/react-primitive": "2.1.3",
3534
+ "@radix-ui/react-slot": "1.2.3",
3535
+ "@radix-ui/react-use-controllable-state": "1.2.2",
3536
+ "@radix-ui/react-visually-hidden": "1.2.3"
3537
+ },
3538
+ "peerDependencies": {
3539
+ "@types/react": "*",
3540
+ "@types/react-dom": "*",
3541
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
3542
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
3543
+ },
3544
+ "peerDependenciesMeta": {
3545
+ "@types/react": {
3546
+ "optional": true
3547
+ },
3548
+ "@types/react-dom": {
3549
+ "optional": true
3550
+ }
3551
+ }
3552
+ },
3553
  "node_modules/@radix-ui/react-use-callback-ref": {
3554
  "version": "1.1.1",
3555
  "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
 
3653
  }
3654
  }
3655
  },
3656
+ "node_modules/@radix-ui/react-use-rect": {
3657
+ "version": "1.1.1",
3658
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz",
3659
+ "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
3660
+ "license": "MIT",
3661
+ "dependencies": {
3662
+ "@radix-ui/rect": "1.1.1"
3663
+ },
3664
+ "peerDependencies": {
3665
+ "@types/react": "*",
3666
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
3667
+ },
3668
+ "peerDependenciesMeta": {
3669
+ "@types/react": {
3670
+ "optional": true
3671
+ }
3672
+ }
3673
+ },
3674
+ "node_modules/@radix-ui/react-use-size": {
3675
+ "version": "1.1.1",
3676
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
3677
+ "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
3678
+ "license": "MIT",
3679
+ "dependencies": {
3680
+ "@radix-ui/react-use-layout-effect": "1.1.1"
3681
+ },
3682
+ "peerDependencies": {
3683
+ "@types/react": "*",
3684
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
3685
+ },
3686
+ "peerDependenciesMeta": {
3687
+ "@types/react": {
3688
+ "optional": true
3689
+ }
3690
+ }
3691
+ },
3692
+ "node_modules/@radix-ui/react-visually-hidden": {
3693
+ "version": "1.2.3",
3694
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz",
3695
+ "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==",
3696
+ "license": "MIT",
3697
+ "dependencies": {
3698
+ "@radix-ui/react-primitive": "2.1.3"
3699
+ },
3700
+ "peerDependencies": {
3701
+ "@types/react": "*",
3702
+ "@types/react-dom": "*",
3703
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
3704
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
3705
+ },
3706
+ "peerDependenciesMeta": {
3707
+ "@types/react": {
3708
+ "optional": true
3709
+ },
3710
+ "@types/react-dom": {
3711
+ "optional": true
3712
+ }
3713
+ }
3714
+ },
3715
+ "node_modules/@radix-ui/rect": {
3716
+ "version": "1.1.1",
3717
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
3718
+ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
3719
+ "license": "MIT"
3720
+ },
3721
  "node_modules/@react-router/dev": {
3722
  "version": "7.9.4",
3723
  "resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-7.9.4.tgz",
package.json CHANGED
@@ -16,6 +16,7 @@
16
  "@radix-ui/react-dialog": "^1.1.15",
17
  "@radix-ui/react-separator": "^1.1.7",
18
  "@radix-ui/react-slot": "^1.2.3",
 
19
  "@react-router/node": "^7.9.2",
20
  "@react-router/serve": "^7.9.2",
21
  "class-variance-authority": "^0.7.1",
 
16
  "@radix-ui/react-dialog": "^1.1.15",
17
  "@radix-ui/react-separator": "^1.1.7",
18
  "@radix-ui/react-slot": "^1.2.3",
19
+ "@radix-ui/react-tooltip": "^1.2.8",
20
  "@react-router/node": "^7.9.2",
21
  "@react-router/serve": "^7.9.2",
22
  "class-variance-authority": "^0.7.1",