axiodb 22.3.1 → 22.4.2

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 (74) hide show
  1. package/electron/electron-builder.json +58 -0
  2. package/electron/main/main.cts +585 -0
  3. package/electron/main/preload.cts +51 -0
  4. package/electron/package-lock.json +8123 -0
  5. package/electron/package.json +52 -0
  6. package/electron/public/AXioDB.png +0 -0
  7. package/electron/resources/after-install.sh +45 -0
  8. package/electron/resources/after-remove.sh +29 -0
  9. package/electron/resources/icon.png +0 -0
  10. package/electron/resources/icons/128x128.png +0 -0
  11. package/electron/resources/icons/16x16.png +0 -0
  12. package/electron/resources/icons/24x24.png +0 -0
  13. package/electron/resources/icons/256x256.png +0 -0
  14. package/electron/resources/icons/32x32.png +0 -0
  15. package/electron/resources/icons/48x48.png +0 -0
  16. package/electron/resources/icons/512x512.png +0 -0
  17. package/electron/resources/icons/64x64.png +0 -0
  18. package/electron/src/App.jsx +128 -0
  19. package/electron/src/api/authApi.js +83 -0
  20. package/electron/src/api/client.js +102 -0
  21. package/electron/src/assets/AXioDB.png +0 -0
  22. package/electron/src/components/auth/CreateRoleModal.jsx +189 -0
  23. package/electron/src/components/auth/CreateUserModal.jsx +131 -0
  24. package/electron/src/components/auth/ForcePasswordChangeModal.jsx +132 -0
  25. package/electron/src/components/auth/ProtectedRoute.jsx +42 -0
  26. package/electron/src/components/auth/ResetPasswordModal.jsx +90 -0
  27. package/electron/src/components/auth/UserAvatarMenu.jsx +103 -0
  28. package/electron/src/components/collection/CreateCollectionModal.jsx +116 -0
  29. package/electron/src/components/collection/DeleteCollectionModal.jsx +85 -0
  30. package/electron/src/components/collection/SchemaViewModal.jsx +369 -0
  31. package/electron/src/components/dashboard/CollectionsChart.jsx +94 -0
  32. package/electron/src/components/dashboard/DatabaseTreeView.jsx +130 -0
  33. package/electron/src/components/dashboard/InMemoryCacheCard.jsx +60 -0
  34. package/electron/src/components/dashboard/StorageDonut.jsx +76 -0
  35. package/electron/src/components/dashboard/StorageUsageCard.jsx +59 -0
  36. package/electron/src/components/dashboard/TotalCollectionsCard.jsx +47 -0
  37. package/electron/src/components/dashboard/TotalDatabasesCard.jsx +43 -0
  38. package/electron/src/components/dashboard/TotalDocumentsCard.jsx +44 -0
  39. package/electron/src/components/database/CreateDatabaseModal.jsx +109 -0
  40. package/electron/src/components/database/DeleteDatabaseModal.jsx +97 -0
  41. package/electron/src/components/query/CodeEditor.jsx +343 -0
  42. package/electron/src/components/query/ObjectEditor.jsx +115 -0
  43. package/electron/src/components/query/ObjectView.jsx +44 -0
  44. package/electron/src/components/query/QueryEditor.jsx +32 -0
  45. package/electron/src/components/query/queryLanguage.js +755 -0
  46. package/electron/src/components/ui/Button.jsx +58 -0
  47. package/electron/src/components/ui/Card.jsx +29 -0
  48. package/electron/src/components/ui/ErrorBoundary.jsx +108 -0
  49. package/electron/src/components/ui/Feedback.jsx +66 -0
  50. package/electron/src/components/ui/Field.jsx +65 -0
  51. package/electron/src/components/ui/MetricCard.jsx +104 -0
  52. package/electron/src/components/ui/Modal.jsx +119 -0
  53. package/electron/src/components/ui/Page.jsx +40 -0
  54. package/electron/src/config/key.js +11 -0
  55. package/electron/src/index.css +181 -0
  56. package/electron/src/index.html +13 -0
  57. package/electron/src/layout/Sidebar.jsx +478 -0
  58. package/electron/src/layout/StatusBar.jsx +70 -0
  59. package/electron/src/layout/Titlebar.jsx +128 -0
  60. package/electron/src/main.jsx +42 -0
  61. package/electron/src/pages/ConnectionHub.jsx +464 -0
  62. package/electron/src/pages/Dashboard.jsx +128 -0
  63. package/electron/src/pages/Documents.jsx +823 -0
  64. package/electron/src/pages/Import.jsx +527 -0
  65. package/electron/src/pages/UserManagement.jsx +294 -0
  66. package/electron/src/pages/Welcome.jsx +157 -0
  67. package/electron/src/store/authStore.js +30 -0
  68. package/electron/src/store/connectionStore.js +103 -0
  69. package/electron/src/store/dbStore.js +165 -0
  70. package/electron/src/store/store.js +8 -0
  71. package/electron/src/utils/format.js +39 -0
  72. package/electron/vite.config.js +28 -0
  73. package/lib/Services/Indexation.operation.js +1 -1
  74. package/package.json +1 -1
@@ -0,0 +1,60 @@
1
+ import { ChipIcon } from '@heroicons/react/outline'
2
+ import { useEffect, useState } from 'react'
3
+
4
+ /**
5
+ * Component to display the total in-memory cache size in the AxioDB system
6
+ */
7
+ const InMemoryCacheCard = ({ CacheStorageInfo }) => {
8
+ const [loading, setLoading] = useState(true)
9
+
10
+ useEffect(() => {
11
+ // Simulate API call with dummy data
12
+ const fetchData = () => {
13
+ setTimeout(() => {
14
+ setLoading(false)
15
+ }, 800)
16
+ }
17
+
18
+ fetchData()
19
+ }, [])
20
+
21
+ // Calculate percentage of cache used
22
+ const usagePercentage =
23
+ (CacheStorageInfo.Storage / CacheStorageInfo.Max) * 100
24
+
25
+ return (
26
+ <div className='bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow'>
27
+ <div className='flex justify-between mb-3'>
28
+ <div>
29
+ <h3 className='text-lg font-medium text-ink-900'>In-Memory Cache</h3>
30
+ {loading
31
+ ? (
32
+ <div className='h-8 mt-2 bg-ink-200 rounded animate-pulse w-24' />
33
+ )
34
+ : (
35
+ <p className='text-3xl font-bold text-orange-600 mt-2'>
36
+ {CacheStorageInfo.Storage}{' '}
37
+ <span className='text-lg'>{CacheStorageInfo.Unit}</span>
38
+ </p>
39
+ )}
40
+ <p className='text-sm text-ink-500 mt-1'>
41
+ of {CacheStorageInfo.Max} {CacheStorageInfo.Unit} allocated
42
+ </p>
43
+ </div>
44
+ <div className='p-3 bg-orange-100 rounded-full'>
45
+ <ChipIcon className='h-8 w-8 text-orange-600' />
46
+ </div>
47
+ </div>
48
+
49
+ {/* Progress bar */}
50
+ <div className='w-full bg-ink-200 rounded-full h-2.5 mt-2'>
51
+ <div
52
+ className='bg-orange-600 h-2.5 rounded-full'
53
+ style={{ width: `${loading ? 0 : usagePercentage}%` }}
54
+ />
55
+ </div>
56
+ </div>
57
+ )
58
+ }
59
+
60
+ export default InMemoryCacheCard
@@ -0,0 +1,76 @@
1
+ import { Cell, Pie, PieChart, ResponsiveContainer } from 'recharts'
2
+ import Card, { CardHeader } from '../ui/Card'
3
+ import { Skeleton } from '../ui/Feedback'
4
+ import { formatStorage } from '../../utils/format'
5
+
6
+ /**
7
+ * Used-vs-free ring for a storage figure, with the percentage in the middle.
8
+ *
9
+ * A donut is the right shape here because the question is "how much of the whole is gone",
10
+ * not "how has it changed" - there is no time series behind these numbers.
11
+ */
12
+ const StorageDonut = ({ title, subtitle, used = 0, total = 0, unit = 'MB', loading = false, color = 'var(--color-viz-1)' }) => {
13
+ const safeTotal = total > 0 ? total : 1
14
+ const percent = Math.min((used / safeTotal) * 100, 100)
15
+ const data = [
16
+ { name: 'Used', value: Math.max(used, 0) },
17
+ { name: 'Free', value: Math.max(safeTotal - used, 0) }
18
+ ]
19
+
20
+ return (
21
+ <Card className='flex flex-col'>
22
+ <CardHeader title={title} subtitle={subtitle} />
23
+ <div className='flex flex-1 items-center gap-5 p-5'>
24
+ {loading
25
+ ? <Skeleton className='h-32 w-32 rounded-full' />
26
+ : (
27
+ <div className='relative h-32 w-32 shrink-0'>
28
+ <ResponsiveContainer width='100%' height='100%'>
29
+ <PieChart>
30
+ <Pie
31
+ data={data}
32
+ dataKey='value'
33
+ innerRadius='72%'
34
+ outerRadius='100%'
35
+ startAngle={90}
36
+ endAngle={-270}
37
+ stroke='none'
38
+ animationDuration={800}
39
+ animationEasing='ease-out'
40
+ >
41
+ <Cell fill={color} />
42
+ <Cell fill='var(--color-ink-200)' />
43
+ </Pie>
44
+ </PieChart>
45
+ </ResponsiveContainer>
46
+ <div className='pointer-events-none absolute inset-0 flex flex-col items-center justify-center'>
47
+ <span className='text-xl font-bold tabular-nums text-ink-900'>
48
+ {percent.toFixed(percent < 10 ? 1 : 0)}%
49
+ </span>
50
+ <span className='text-[10px] uppercase tracking-wide text-ink-400'>used</span>
51
+ </div>
52
+ </div>
53
+ )}
54
+
55
+ <dl className='min-w-0 flex-1 space-y-3'>
56
+ <div>
57
+ <dt className='text-xs text-ink-500'>Used</dt>
58
+ <dd className='text-sm font-semibold tabular-nums text-ink-900'>
59
+ {loading ? <Skeleton className='h-4 w-20' /> : formatStorage(used, unit)}
60
+ </dd>
61
+ </div>
62
+ <div>
63
+ <dt className='text-xs text-ink-500'>Available</dt>
64
+ <dd className='text-sm font-semibold tabular-nums text-ink-900'>
65
+ {loading
66
+ ? <Skeleton className='h-4 w-20' />
67
+ : formatStorage(Math.max(total - used, 0), unit)}
68
+ </dd>
69
+ </div>
70
+ </dl>
71
+ </div>
72
+ </Card>
73
+ )
74
+ }
75
+
76
+ export default StorageDonut
@@ -0,0 +1,59 @@
1
+ import { ServerIcon } from '@heroicons/react/outline'
2
+ import { useEffect, useState } from 'react'
3
+
4
+ /**
5
+ * Component to display the total storage used in the AxioDB system
6
+ */
7
+ const StorageUsageCard = ({ storageInfo }) => {
8
+ const [loading, setLoading] = useState(true)
9
+
10
+ useEffect(() => {
11
+ // Simulate API call with dummy data
12
+ const fetchData = () => {
13
+ setTimeout(() => {
14
+ setLoading(false)
15
+ }, 800)
16
+ }
17
+
18
+ fetchData()
19
+ }, [])
20
+
21
+ // Calculate percentage of storage used
22
+ const usagePercentage = (storageInfo?.total / storageInfo?.machine) * 100
23
+
24
+ return (
25
+ <div className='bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow'>
26
+ <div className='flex justify-between mb-3'>
27
+ <div>
28
+ <h3 className='text-lg font-medium text-ink-900'>Storage Used</h3>
29
+ {loading
30
+ ? (
31
+ <div className='h-8 mt-2 bg-ink-200 rounded animate-pulse w-24' />
32
+ )
33
+ : (
34
+ <p className='text-3xl font-bold text-brand-600 mt-2'>
35
+ {storageInfo?.total}{' '}
36
+ <span className='text-lg'>{storageInfo?.matrixUnit}</span>
37
+ </p>
38
+ )}
39
+ <p className='text-sm text-ink-500 mt-1'>
40
+ of {storageInfo?.machine} {storageInfo?.matrixUnit} available
41
+ </p>
42
+ </div>
43
+ <div className='p-3 bg-brand-100 rounded-full'>
44
+ <ServerIcon className='h-8 w-8 text-brand-600' />
45
+ </div>
46
+ </div>
47
+
48
+ {/* Progress bar */}
49
+ <div className='w-full bg-ink-200 rounded-full h-2.5 mt-2'>
50
+ <div
51
+ className='bg-brand-600 h-2.5 rounded-full'
52
+ style={{ width: `${loading ? 0 : usagePercentage}%` }}
53
+ />
54
+ </div>
55
+ </div>
56
+ )
57
+ }
58
+
59
+ export default StorageUsageCard
@@ -0,0 +1,47 @@
1
+ import { CollectionIcon } from '@heroicons/react/outline'
2
+ import { useEffect, useState } from 'react'
3
+
4
+ /**
5
+ * Component to display the total number of collections in the AxioDB system
6
+ */
7
+ const TotalCollectionsCard = ({ totalCollections }) => {
8
+ const [loading, setLoading] = useState(true)
9
+
10
+ useEffect(() => {
11
+ // Simulate API call with dummy data
12
+ const fetchData = () => {
13
+ setTimeout(() => {
14
+ setLoading(false)
15
+ }, 600)
16
+ }
17
+
18
+ fetchData()
19
+ }, [])
20
+
21
+ return (
22
+ <div className='bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow'>
23
+ <div className='flex justify-between'>
24
+ <div>
25
+ {/* <h3 className="text-lg font-medium text-ink-900">
26
+ Total Collections
27
+ </h3> */}
28
+ {loading
29
+ ? (
30
+ <div className='h-8 mt-2 bg-ink-200 rounded animate-pulse w-16' />
31
+ )
32
+ : (
33
+ <p className='text-2xl font-bold text-indigo-600 mt-2'>
34
+ {totalCollections}{' '}
35
+ {totalCollections <= 1 ? 'Collection' : 'Collections'}
36
+ </p>
37
+ )}
38
+ </div>
39
+ <div className='p-3 bg-indigo-100 rounded-full'>
40
+ <CollectionIcon className='h-8 w-8 text-indigo-600' />
41
+ </div>
42
+ </div>
43
+ </div>
44
+ )
45
+ }
46
+
47
+ export default TotalCollectionsCard
@@ -0,0 +1,43 @@
1
+ import { DatabaseIcon } from '@heroicons/react/outline'
2
+ import { useEffect, useState } from 'react'
3
+
4
+ /**
5
+ * Component to display the total number of databases in the AxioDB system
6
+ */
7
+ const TotalDatabasesCard = ({ totalDatabases }) => {
8
+ // In a real application, this would come from an API
9
+ const [loading, setLoading] = useState(true)
10
+
11
+ useEffect(() => {
12
+ // Simulate fetching total databases count
13
+ setTimeout(() => {
14
+ setLoading(false)
15
+ }, 1000) // Simulate a 1 second delay
16
+ }, [])
17
+
18
+ return (
19
+ <div className='bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow'>
20
+ <div className='flex justify-between'>
21
+ <div>
22
+ {/* <h3 className="text-lg font-medium text-ink-900">Total Databases</h3> */}
23
+ {loading
24
+ ? (
25
+ <div className='h-8 mt-2 bg-ink-200 rounded animate-pulse w-16 text-center' />
26
+ )
27
+ : (
28
+ <p className='text-2xl font-bold text-brand-700 mt-2 text-center'>
29
+ {totalDatabases <= 1
30
+ ? `${totalDatabases} Database`
31
+ : `${totalDatabases} Databases`}
32
+ </p>
33
+ )}
34
+ </div>
35
+ <div className='p-3 bg-info-50 rounded-full'>
36
+ <DatabaseIcon className='h-8 w-8 text-brand-700' />
37
+ </div>
38
+ </div>
39
+ </div>
40
+ )
41
+ }
42
+
43
+ export default TotalDatabasesCard
@@ -0,0 +1,44 @@
1
+ import { DocumentTextIcon } from '@heroicons/react/outline'
2
+ import { useEffect, useState } from 'react'
3
+
4
+ /**
5
+ * Component to display the total number of documents in the AxioDB system
6
+ */
7
+ const TotalDocumentsCard = ({ totalDocuments }) => {
8
+ const [loading, setLoading] = useState(true)
9
+
10
+ useEffect(() => {
11
+ const fetchData = () => {
12
+ setTimeout(() => {
13
+ setLoading(false)
14
+ }, 700)
15
+ }
16
+
17
+ fetchData()
18
+ }, [])
19
+
20
+ return (
21
+ <div className='bg-white rounded-lg shadow-md p-6 hover:shadow-lg transition-shadow'>
22
+ <div className='flex justify-between'>
23
+ <div>
24
+ <h3 className='text-lg font-medium text-ink-900'>Total Documents</h3>
25
+ {loading
26
+ ? (
27
+ <div className='h-8 mt-2 bg-ink-200 rounded animate-pulse w-20' />
28
+ )
29
+ : (
30
+ <p className='text-3xl font-bold text-purple-600 mt-2'>
31
+ {totalDocuments}
32
+ </p>
33
+ )}
34
+ <p className='text-sm text-ink-500 mt-1'>Across all collections</p>
35
+ </div>
36
+ <div className='p-3 bg-purple-100 rounded-full'>
37
+ <DocumentTextIcon className='h-8 w-8 text-purple-600' />
38
+ </div>
39
+ </div>
40
+ </div>
41
+ )
42
+ }
43
+
44
+ export default TotalDocumentsCard
@@ -0,0 +1,109 @@
1
+ import { useState } from 'react'
2
+ import apiClient from '../../api/client'
3
+ import Modal from '../ui/Modal'
4
+ import Button from '../ui/Button'
5
+ import { Input } from '../ui/Field'
6
+
7
+ const CreateDatabaseModal = ({ isOpen, onClose, onDatabaseCreated }) => {
8
+ const [databaseName, setDatabaseName] = useState('')
9
+ const [isSubmitting, setIsSubmitting] = useState(false)
10
+ const [error, setError] = useState('')
11
+
12
+ // Reset form state when modal is opened/closed
13
+ const handleClose = () => {
14
+ setDatabaseName('')
15
+ setError('')
16
+ setIsSubmitting(false) // Ensure submission state is reset when closing
17
+ onClose()
18
+ }
19
+
20
+ const handleSubmit = async (e) => {
21
+ e.preventDefault()
22
+
23
+ // Validate input
24
+ if (!databaseName.trim()) {
25
+ setError('Database name is required')
26
+ return
27
+ }
28
+
29
+ // Alphanumeric validation (plus underscores)
30
+ if (!/^[a-zA-Z0-9_]+$/.test(databaseName)) {
31
+ setError(
32
+ 'Database name can only contain letters, numbers, and underscores'
33
+ )
34
+ return
35
+ }
36
+
37
+ setIsSubmitting(true)
38
+ setError('')
39
+
40
+ try {
41
+ const response = await apiClient.post('/api/db/create-database', {
42
+ name: databaseName
43
+ })
44
+
45
+ if (
46
+ response.data.statusCode === 200 ||
47
+ response.data.statusCode === 201
48
+ ) {
49
+ onDatabaseCreated(databaseName)
50
+ setIsSubmitting(false) // Reset submission state on success
51
+ handleClose()
52
+ } else {
53
+ setIsSubmitting(false) // Reset submission state on success
54
+ throw new Error('Failed to create database')
55
+ }
56
+ } catch (error) {
57
+ console.error('Error creating database:', error)
58
+ setError(
59
+ error.response?.data?.message ||
60
+ 'Failed to create database. Please try again.'
61
+ )
62
+ setIsSubmitting(false)
63
+ }
64
+ }
65
+ const DatabaseIcon = (
66
+ <svg className='h-5 w-5' fill='none' viewBox='0 0 24 24' stroke='currentColor' strokeWidth={2}>
67
+ <ellipse cx='12' cy='6' rx='8' ry='3' />
68
+ <path d='M4 6v12c0 1.66 3.58 3 8 3s8-1.34 8-3V6' />
69
+ </svg>
70
+ )
71
+
72
+ return (
73
+ <Modal
74
+ isOpen={isOpen}
75
+ onClose={handleClose}
76
+ title='Create database'
77
+ icon={DatabaseIcon}
78
+ size='md'
79
+ footer={
80
+ <>
81
+ <Button variant='secondary' onClick={handleClose} disabled={isSubmitting}>
82
+ Cancel
83
+ </Button>
84
+ <Button onClick={handleSubmit} loading={isSubmitting} disabled={!databaseName.trim()}>
85
+ Create database
86
+ </Button>
87
+ </>
88
+ }
89
+ >
90
+ <form onSubmit={handleSubmit}>
91
+ <Input
92
+ label='Database name'
93
+ value={databaseName}
94
+ onChange={(event) => setDatabaseName(event.target.value)}
95
+ placeholder='AppDB'
96
+ mono
97
+ autoFocus
98
+ error={error || undefined}
99
+ hint='Letters, numbers and underscores only. The name "config" is reserved by AxioDB.'
100
+ />
101
+
102
+ {/* Lets Enter submit the form without rendering a second visible button. */}
103
+ <button type='submit' className='hidden' aria-hidden='true' tabIndex={-1} />
104
+ </form>
105
+ </Modal>
106
+ )
107
+ }
108
+
109
+ export default CreateDatabaseModal
@@ -0,0 +1,97 @@
1
+ import { useState } from 'react'
2
+ import apiClient from '../../api/client'
3
+ import Modal from '../ui/Modal'
4
+ import Button from '../ui/Button'
5
+ import { Alert } from '../ui/Feedback'
6
+
7
+ const WarningIcon = (
8
+ <svg className='h-5 w-5' fill='none' viewBox='0 0 24 24' stroke='currentColor' strokeWidth={2}>
9
+ <path strokeLinecap='round' strokeLinejoin='round' d='M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z' />
10
+ </svg>
11
+ )
12
+
13
+ /**
14
+ * Destructive confirmation. Requires the database name to be typed back before the delete
15
+ * button unlocks - this removes every collection and document underneath it, and there is no
16
+ * undo, so a single mis-aimed click should not be enough.
17
+ */
18
+ const DeleteDatabaseModal = ({ isOpen, dbName, onClose, onConfirmDelete }) => {
19
+ const [isDeleting, setIsDeleting] = useState(false)
20
+ const [confirmation, setConfirmation] = useState('')
21
+ const [error, setError] = useState(null)
22
+
23
+ const handleClose = () => {
24
+ setConfirmation('')
25
+ setError(null)
26
+ onClose()
27
+ }
28
+
29
+ const handleDelete = async () => {
30
+ setIsDeleting(true)
31
+ setError(null)
32
+
33
+ try {
34
+ const response = await apiClient.delete('/api/db/delete-database', {
35
+ params: { dbName }
36
+ })
37
+
38
+ if (response.status !== 200) throw new Error('Failed to delete database')
39
+
40
+ setConfirmation('')
41
+ onConfirmDelete()
42
+ } catch (err) {
43
+ console.error('Error deleting database:', err)
44
+ setError(err.response?.data?.message || 'Failed to delete database. Please try again.')
45
+ } finally {
46
+ setIsDeleting(false)
47
+ }
48
+ }
49
+
50
+ return (
51
+ <Modal
52
+ isOpen={isOpen}
53
+ onClose={handleClose}
54
+ title='Delete database'
55
+ subtitle={dbName}
56
+ icon={WarningIcon}
57
+ tone='danger'
58
+ size='md'
59
+ footer={
60
+ <>
61
+ <Button variant='secondary' onClick={handleClose} disabled={isDeleting}>
62
+ Cancel
63
+ </Button>
64
+ <Button
65
+ variant='danger'
66
+ onClick={handleDelete}
67
+ loading={isDeleting}
68
+ disabled={confirmation !== dbName}
69
+ >
70
+ Delete permanently
71
+ </Button>
72
+ </>
73
+ }
74
+ >
75
+ <Alert tone='danger' title='This cannot be undone'>
76
+ Deleting <strong>{dbName}</strong> removes every collection and document inside it
77
+ from disk.
78
+ </Alert>
79
+
80
+ <label htmlFor='confirm-db-name' className='mt-5 block text-sm text-ink-700'>
81
+ Type <span className='font-mono font-semibold text-ink-900'>{dbName}</span> to confirm:
82
+ </label>
83
+ <input
84
+ id='confirm-db-name'
85
+ value={confirmation}
86
+ onChange={(event) => setConfirmation(event.target.value)}
87
+ autoComplete='off'
88
+ placeholder={dbName}
89
+ className='mt-2 w-full rounded-lg border border-ink-300 px-3 py-2 font-mono text-sm shadow-sm transition-all focus:border-danger-500 focus:outline-none focus:ring-2 focus:ring-danger-500/25'
90
+ />
91
+
92
+ {error && <Alert tone='danger' className='mt-4'>{error}</Alert>}
93
+ </Modal>
94
+ )
95
+ }
96
+
97
+ export default DeleteDatabaseModal