@kyro-cms/admin 0.1.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 (102) hide show
  1. package/.astro/content.d.ts +154 -0
  2. package/.astro/settings.json +5 -0
  3. package/.astro/types.d.ts +2 -0
  4. package/astro.config.mjs +28 -0
  5. package/bun.lock +1374 -0
  6. package/dist/client/_astro/AdminLayout.DkDpng53.css +1 -0
  7. package/dist/client/_astro/AutoForm.3eJCmCJp.js +1 -0
  8. package/dist/client/_astro/client.DyczpTbx.js +9 -0
  9. package/dist/client/_astro/index.B02hbnpo.js +1 -0
  10. package/dist/client/fonts/Serotiva-Black.woff2 +0 -0
  11. package/dist/client/fonts/Serotiva-Bold.woff2 +0 -0
  12. package/dist/client/fonts/Serotiva-Medium.woff2 +0 -0
  13. package/dist/client/fonts/Serotiva-Regular.woff2 +0 -0
  14. package/dist/client/fonts/Serotiva-SemiBold.woff2 +0 -0
  15. package/dist/server/chunks/AdminLayout_D-_JeUqC.mjs +26 -0
  16. package/dist/server/chunks/_id__BzI_o0qT.mjs +50 -0
  17. package/dist/server/chunks/_id__Cd-jOuY3.mjs +238 -0
  18. package/dist/server/chunks/_id__DvbD--iR.mjs +992 -0
  19. package/dist/server/chunks/_id__vpVaEo16.mjs +128 -0
  20. package/dist/server/chunks/_virtual_astro_server-island-manifest_CQQ1F5PF.mjs +7 -0
  21. package/dist/server/chunks/_virtual_astro_session-driver_Bk3Q189E.mjs +4 -0
  22. package/dist/server/chunks/astro-component_Dbx3T2Nh.mjs +37 -0
  23. package/dist/server/chunks/audit-logs_DrnUMRvY.mjs +74 -0
  24. package/dist/server/chunks/config_CPXslElD.mjs +4221 -0
  25. package/dist/server/chunks/dataStore_Dl7cA2Qp.mjs +89 -0
  26. package/dist/server/chunks/index_CVqOkerS.mjs +2960 -0
  27. package/dist/server/chunks/index_CX8SQ4BF.mjs +55 -0
  28. package/dist/server/chunks/index_CYofDU51.mjs +58 -0
  29. package/dist/server/chunks/index_DdNRhuaM.mjs +55 -0
  30. package/dist/server/chunks/index_DupPvtIF.mjs +42 -0
  31. package/dist/server/chunks/index_YTS_M-B9.mjs +263 -0
  32. package/dist/server/chunks/index_YeCzuVps.mjs +53 -0
  33. package/dist/server/chunks/login_DLyqMRO8.mjs +93 -0
  34. package/dist/server/chunks/logout_CSbt5wea.mjs +50 -0
  35. package/dist/server/chunks/me_C04jlYhH.mjs +41 -0
  36. package/dist/server/chunks/new_BbQ9b55M.mjs +92 -0
  37. package/dist/server/chunks/node_9bvTewss.mjs +1014 -0
  38. package/dist/server/chunks/noop-entrypoint_BOlrdqWF.mjs +3 -0
  39. package/dist/server/chunks/sequence_9cl7AJy-.mjs +2503 -0
  40. package/dist/server/chunks/server_peBx9VXG.mjs +8117 -0
  41. package/dist/server/chunks/sharp_pmJ7nHES.mjs +142 -0
  42. package/dist/server/chunks/users_Dzddy_YR.mjs +137 -0
  43. package/dist/server/entry.mjs +5 -0
  44. package/dist/server/virtual_astro_middleware.mjs +48 -0
  45. package/package.json +33 -0
  46. package/public/fonts/Serotiva-Black.woff2 +0 -0
  47. package/public/fonts/Serotiva-Bold.woff2 +0 -0
  48. package/public/fonts/Serotiva-Medium.woff2 +0 -0
  49. package/public/fonts/Serotiva-Regular.woff2 +0 -0
  50. package/public/fonts/Serotiva-SemiBold.woff2 +0 -0
  51. package/src/collections/auth/index.ts +155 -0
  52. package/src/components/ActionBar.tsx +215 -0
  53. package/src/components/Admin.tsx +214 -0
  54. package/src/components/AutoForm.tsx +1123 -0
  55. package/src/components/BulkActionsBar.tsx +80 -0
  56. package/src/components/CreateView.tsx +99 -0
  57. package/src/components/DetailView.tsx +329 -0
  58. package/src/components/Icons.tsx +23 -0
  59. package/src/components/ListView.tsx +192 -0
  60. package/src/components/StatusBadge.tsx +76 -0
  61. package/src/components/ThemeProvider.tsx +155 -0
  62. package/src/components/VersionHistoryPanel.tsx +205 -0
  63. package/src/components/fields/CheckboxField.tsx +37 -0
  64. package/src/components/fields/DateField.tsx +42 -0
  65. package/src/components/fields/NumberField.tsx +44 -0
  66. package/src/components/fields/RelationshipField.tsx +87 -0
  67. package/src/components/fields/SelectField.tsx +56 -0
  68. package/src/components/fields/TextField.tsx +49 -0
  69. package/src/components/index.ts +30 -0
  70. package/src/components/layout/Breadcrumbs.tsx +36 -0
  71. package/src/components/layout/Header.tsx +37 -0
  72. package/src/components/layout/Layout.tsx +25 -0
  73. package/src/components/layout/Sidebar.tsx +462 -0
  74. package/src/components/ui/Badge.tsx +14 -0
  75. package/src/components/ui/Button.tsx +41 -0
  76. package/src/components/ui/Dropdown.tsx +82 -0
  77. package/src/components/ui/Modal.tsx +135 -0
  78. package/src/components/ui/SlidePanel.tsx +73 -0
  79. package/src/components/ui/Spinner.tsx +24 -0
  80. package/src/components/ui/Toast.tsx +78 -0
  81. package/src/layouts/AdminLayout.astro +197 -0
  82. package/src/lib/config.ts +68 -0
  83. package/src/lib/dataStore.ts +111 -0
  84. package/src/middleware.ts +48 -0
  85. package/src/pages/[collection]/[id].astro +176 -0
  86. package/src/pages/[collection]/index.astro +180 -0
  87. package/src/pages/api/[collection]/[id].ts +258 -0
  88. package/src/pages/api/[collection]/index.ts +289 -0
  89. package/src/pages/api/auth/[id].ts +142 -0
  90. package/src/pages/api/auth/audit-logs.ts +80 -0
  91. package/src/pages/api/auth/login.ts +101 -0
  92. package/src/pages/api/auth/logout.ts +48 -0
  93. package/src/pages/api/auth/me.ts +36 -0
  94. package/src/pages/api/auth/users.ts +150 -0
  95. package/src/pages/audit/index.astro +110 -0
  96. package/src/pages/index.astro +225 -0
  97. package/src/pages/roles/index.astro +114 -0
  98. package/src/pages/users/[id].astro +174 -0
  99. package/src/pages/users/index.astro +142 -0
  100. package/src/pages/users/new.astro +91 -0
  101. package/src/styles/main.css +1449 -0
  102. package/tsconfig.json +12 -0
@@ -0,0 +1,80 @@
1
+ import React from "react";
2
+ import { Dropdown, DropdownItem, DropdownSeparator } from "./ui/Dropdown";
3
+ import { CountBadge } from "./StatusBadge";
4
+
5
+ interface BulkAction {
6
+ label: string;
7
+ onClick: () => void;
8
+ icon?: React.ReactNode;
9
+ danger?: boolean;
10
+ }
11
+
12
+ interface BulkActionsBarProps {
13
+ selectedCount: number;
14
+ onClearSelection: () => void;
15
+ actions: BulkAction[];
16
+ onSelectAll?: () => void;
17
+ }
18
+
19
+ export function BulkActionsBar({
20
+ selectedCount,
21
+ onClearSelection,
22
+ actions,
23
+ onSelectAll,
24
+ }: BulkActionsBarProps) {
25
+ if (selectedCount === 0) return null;
26
+
27
+ return (
28
+ <div className="flex items-center justify-between py-2 px-4 bg-gray-50 border-b border-gray-200">
29
+ <div className="flex items-center gap-4">
30
+ <div className="flex items-center gap-2">
31
+ <CountBadge count={selectedCount} />
32
+ <span className="text-sm text-gray-600">selected</span>
33
+ </div>
34
+ <button
35
+ onClick={onClearSelection}
36
+ className="text-sm text-gray-500 hover:text-gray-700"
37
+ >
38
+ Clear selection
39
+ </button>
40
+ {onSelectAll && (
41
+ <button
42
+ onClick={onSelectAll}
43
+ className="text-sm text-gray-500 hover:text-gray-700"
44
+ >
45
+ Select all
46
+ </button>
47
+ )}
48
+ </div>
49
+
50
+ <Dropdown
51
+ trigger={
52
+ <button className="kyro-btn kyro-btn-secondary kyro-btn-sm">
53
+ Actions
54
+ <svg
55
+ width="12"
56
+ height="12"
57
+ viewBox="0 0 24 24"
58
+ fill="none"
59
+ stroke="currentColor"
60
+ strokeWidth="2"
61
+ >
62
+ <path d="M6 9l6 6 6-6" />
63
+ </svg>
64
+ </button>
65
+ }
66
+ >
67
+ {actions.map((action, index) => (
68
+ <DropdownItem
69
+ key={index}
70
+ onClick={action.onClick}
71
+ icon={action.icon as React.ReactElement}
72
+ danger={action.danger}
73
+ >
74
+ {action.label}
75
+ </DropdownItem>
76
+ ))}
77
+ </Dropdown>
78
+ </div>
79
+ );
80
+ }
@@ -0,0 +1,99 @@
1
+ import { useState } from "react";
2
+ import type { KyroConfig, CollectionConfig } from "@kyro-cms/core";
3
+ import { AutoForm } from "./AutoForm";
4
+ import { Spinner } from "./ui/Spinner";
5
+
6
+ interface CreateViewProps {
7
+ config: KyroConfig;
8
+ collection: CollectionConfig;
9
+ onCancel: () => void;
10
+ onSuccess: () => void;
11
+ onError: (message: string) => void;
12
+ }
13
+
14
+ export function CreateView({
15
+ config,
16
+ collection,
17
+ onCancel,
18
+ onSuccess,
19
+ onError,
20
+ }: CreateViewProps) {
21
+ const [data, setData] = useState<Record<string, unknown>>({});
22
+ const [saving, setSaving] = useState(false);
23
+
24
+ const fields = collection.fields || [];
25
+ const label = collection.label || collection.slug;
26
+
27
+ const handleSubmit = async (e: React.FormEvent) => {
28
+ e.preventDefault();
29
+ try {
30
+ setSaving(true);
31
+ const response = await fetch(`/api/${collection.slug}`, {
32
+ method: "POST",
33
+ headers: { "Content-Type": "application/json" },
34
+ body: JSON.stringify(data),
35
+ });
36
+
37
+ if (!response.ok) {
38
+ const error = await response.json();
39
+ throw new Error(error.message || "Failed to create");
40
+ }
41
+
42
+ onSuccess();
43
+ } catch (err) {
44
+ onError(err instanceof Error ? err.message : "Failed to create");
45
+ } finally {
46
+ setSaving(false);
47
+ }
48
+ };
49
+
50
+ return (
51
+ <div className="kyro-detail">
52
+ <div className="kyro-detail-header">
53
+ <button className="kyro-detail-back" onClick={onCancel}>
54
+ <svg
55
+ width="18"
56
+ height="18"
57
+ viewBox="0 0 24 24"
58
+ fill="none"
59
+ stroke="currentColor"
60
+ strokeWidth="2"
61
+ >
62
+ <path d="M19 12H5M12 19l-7-7 7-7" />
63
+ </svg>
64
+ </button>
65
+ <h2 className="kyro-detail-title">Create {label}</h2>
66
+ <div className="kyro-detail-actions">
67
+ <button
68
+ className="kyro-btn kyro-btn-secondary kyro-btn-md"
69
+ onClick={onCancel}
70
+ disabled={saving}
71
+ >
72
+ Cancel
73
+ </button>
74
+ <button
75
+ className="kyro-btn kyro-btn-primary kyro-btn-md"
76
+ onClick={handleSubmit}
77
+ disabled={saving}
78
+ >
79
+ {saving ? <Spinner size="sm" /> : `Create ${label}`}
80
+ </button>
81
+ </div>
82
+ </div>
83
+
84
+ <div className="kyro-detail-body">
85
+ <div className="kyro-card">
86
+ <div className="kyro-card-content">
87
+ <form onSubmit={handleSubmit}>
88
+ <AutoForm
89
+ config={{ ...collection, fields }}
90
+ data={data}
91
+ onChange={setData}
92
+ />
93
+ </form>
94
+ </div>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ );
99
+ }
@@ -0,0 +1,329 @@
1
+ import { useState, useEffect, useCallback, useRef } from "react";
2
+ import type {
3
+ KyroConfig,
4
+ CollectionConfig,
5
+ GlobalConfig,
6
+ } from "@kyro-cms/core";
7
+ import { AutoForm } from "./AutoForm";
8
+ import { ActionBar, type DocumentStatus, type SaveStatus } from "./ActionBar";
9
+ import { ConfirmModal } from "./ui/Modal";
10
+ import { Spinner } from "./ui/Spinner";
11
+
12
+ interface DetailViewProps {
13
+ config: KyroConfig;
14
+ collection?: CollectionConfig;
15
+ global?: GlobalConfig;
16
+ documentId?: string;
17
+ onBack: () => void;
18
+ onSave: () => void;
19
+ onDelete?: () => void;
20
+ onError: (message: string) => void;
21
+ mode?: "collection" | "global";
22
+ autosave?: boolean;
23
+ autosaveDelay?: number;
24
+ }
25
+
26
+ export function DetailView({
27
+ config,
28
+ collection,
29
+ global,
30
+ documentId,
31
+ onBack,
32
+ onSave,
33
+ onDelete,
34
+ onError,
35
+ mode = "collection",
36
+ autosave = true,
37
+ autosaveDelay = 2000,
38
+ }: DetailViewProps) {
39
+ const [data, setData] = useState<Record<string, unknown>>({});
40
+ const [originalData, setOriginalData] = useState<Record<string, unknown>>({});
41
+ const [loading, setLoading] = useState(true);
42
+ const [saving, setSaving] = useState(false);
43
+ const [saveStatus, setSaveStatus] = useState<SaveStatus>("idle");
44
+ const [deleting, setDeleting] = useState(false);
45
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
46
+ const [status, setStatus] = useState<DocumentStatus>("draft");
47
+ const [createdAt, setCreatedAt] = useState<string | null>(null);
48
+ const [updatedAt, setUpdatedAt] = useState<string | null>(null);
49
+ const [publishedAt, setPublishedAt] = useState<string | null>(null);
50
+
51
+ const autosaveTimerRef = useRef<NodeJS.Timeout | null>(null);
52
+ const hasChangesRef = useRef(false);
53
+
54
+ const fields = global?.fields || collection?.fields || [];
55
+ const label = global?.label || collection?.label || "Document";
56
+ const slug = global?.slug || collection?.slug || "";
57
+
58
+ const hasChanges = JSON.stringify(data) !== JSON.stringify(originalData);
59
+
60
+ useEffect(() => {
61
+ if (mode === "global") {
62
+ loadGlobal();
63
+ } else if (documentId) {
64
+ loadDocument();
65
+ }
66
+ }, [documentId, mode]);
67
+
68
+ useEffect(() => {
69
+ hasChangesRef.current = hasChanges;
70
+ }, [hasChanges]);
71
+
72
+ useEffect(() => {
73
+ if (autosave && hasChanges && !loading) {
74
+ if (autosaveTimerRef.current) {
75
+ clearTimeout(autosaveTimerRef.current);
76
+ }
77
+ autosaveTimerRef.current = setTimeout(() => {
78
+ if (hasChangesRef.current) {
79
+ handleSave(true);
80
+ }
81
+ }, autosaveDelay);
82
+ }
83
+
84
+ return () => {
85
+ if (autosaveTimerRef.current) {
86
+ clearTimeout(autosaveTimerRef.current);
87
+ }
88
+ };
89
+ }, [data, autosave, autosaveDelay]);
90
+
91
+ const loadDocument = async () => {
92
+ try {
93
+ setLoading(true);
94
+ const response = await fetch(`/api/${slug}/${documentId}`);
95
+ if (!response.ok) throw new Error("Failed to load document");
96
+ const result = await response.json();
97
+ const docData = result.data || {};
98
+ setData(docData);
99
+ setOriginalData(docData);
100
+ setStatus(result.status || "draft");
101
+ setCreatedAt(result.createdAt || null);
102
+ setUpdatedAt(result.updatedAt || null);
103
+ setPublishedAt(result.publishedAt || null);
104
+ } catch {
105
+ onError("Failed to load document");
106
+ } finally {
107
+ setLoading(false);
108
+ }
109
+ };
110
+
111
+ const loadGlobal = async () => {
112
+ try {
113
+ setLoading(true);
114
+ const response = await fetch(`/api/globals/${slug}`);
115
+ if (!response.ok) throw new Error("Failed to load global");
116
+ const result = await response.json();
117
+ const globalData = result.data || {};
118
+ setData(globalData);
119
+ setOriginalData(globalData);
120
+ setCreatedAt(result.createdAt || null);
121
+ setUpdatedAt(result.updatedAt || null);
122
+ } catch {
123
+ onError("Failed to load global");
124
+ } finally {
125
+ setLoading(false);
126
+ }
127
+ };
128
+
129
+ const handleSave = useCallback(
130
+ async (isAutosave = false) => {
131
+ try {
132
+ setSaveStatus("saving");
133
+ const endpoint =
134
+ mode === "global"
135
+ ? `/api/globals/${slug}`
136
+ : `/api/${slug}/${documentId}`;
137
+
138
+ const response = await fetch(endpoint, {
139
+ method: "PATCH",
140
+ headers: { "Content-Type": "application/json" },
141
+ body: JSON.stringify(data),
142
+ });
143
+
144
+ if (!response.ok) throw new Error("Failed to save");
145
+
146
+ if (!isAutosave) {
147
+ setOriginalData(data);
148
+ onSave();
149
+ }
150
+ setSaveStatus("saved");
151
+ setUpdatedAt(new Date().toISOString());
152
+
153
+ setTimeout(() => {
154
+ setSaveStatus("idle");
155
+ }, 2000);
156
+ } catch {
157
+ setSaveStatus("error");
158
+ if (!isAutosave) {
159
+ onError("Failed to save changes");
160
+ }
161
+ } finally {
162
+ setSaving(false);
163
+ }
164
+ },
165
+ [data, mode, slug, documentId, onSave, onError],
166
+ );
167
+
168
+ const handlePublish = async () => {
169
+ try {
170
+ setSaving(true);
171
+ const response = await fetch(`/api/${slug}/${documentId}/publish`, {
172
+ method: "POST",
173
+ });
174
+ if (!response.ok) throw new Error("Failed to publish");
175
+ setStatus("published");
176
+ setPublishedAt(new Date().toISOString());
177
+ } catch {
178
+ onError("Failed to publish");
179
+ } finally {
180
+ setSaving(false);
181
+ }
182
+ };
183
+
184
+ const handleUnpublish = async () => {
185
+ try {
186
+ setSaving(true);
187
+ const response = await fetch(`/api/${slug}/${documentId}/unpublish`, {
188
+ method: "POST",
189
+ });
190
+ if (!response.ok) throw new Error("Failed to unpublish");
191
+ setStatus("draft");
192
+ } catch {
193
+ onError("Failed to unpublish");
194
+ } finally {
195
+ setSaving(false);
196
+ }
197
+ };
198
+
199
+ const handleDuplicate = async () => {
200
+ try {
201
+ const response = await fetch(`/api/${slug}/${documentId}/duplicate`, {
202
+ method: "POST",
203
+ });
204
+ if (!response.ok) throw new Error("Failed to duplicate");
205
+ onError("Document duplicated successfully");
206
+ } catch {
207
+ onError("Failed to duplicate document");
208
+ }
209
+ };
210
+
211
+ const handleDelete = async () => {
212
+ try {
213
+ setDeleting(true);
214
+ const response = await fetch(`/api/${slug}/${documentId}`, {
215
+ method: "DELETE",
216
+ });
217
+
218
+ if (!response.ok) throw new Error("Failed to delete");
219
+ onDelete?.();
220
+ } catch {
221
+ onError("Failed to delete document");
222
+ } finally {
223
+ setDeleting(false);
224
+ setShowDeleteConfirm(false);
225
+ }
226
+ };
227
+
228
+ if (loading) {
229
+ return (
230
+ <div className="kyro-detail">
231
+ <div className="kyro-loading">
232
+ <Spinner />
233
+ </div>
234
+ </div>
235
+ );
236
+ }
237
+
238
+ return (
239
+ <div className="kyro-detail">
240
+ <ActionBar
241
+ status={status}
242
+ saveStatus={saveStatus}
243
+ hasChanges={hasChanges}
244
+ onSave={() => handleSave(false)}
245
+ onPublish={mode === "collection" ? handlePublish : undefined}
246
+ onUnpublish={status === "published" ? handleUnpublish : undefined}
247
+ onDuplicate={mode === "collection" ? handleDuplicate : undefined}
248
+ onViewHistory={() => {}}
249
+ onPreview={() =>
250
+ window.open(`/preview/${slug}/${documentId}`, "_blank")
251
+ }
252
+ onDelete={
253
+ mode === "collection" ? () => setShowDeleteConfirm(true) : undefined
254
+ }
255
+ publishedAt={publishedAt}
256
+ updatedAt={updatedAt}
257
+ />
258
+
259
+ <div className="kyro-detail-body">
260
+ <div className="kyro-card">
261
+ <div className="kyro-card-content">
262
+ <AutoForm
263
+ config={
264
+ collection
265
+ ? { ...collection, fields: fields }
266
+ : ({ slug: "unknown", fields: fields } as CollectionConfig)
267
+ }
268
+ data={data}
269
+ onChange={setData}
270
+ />
271
+ </div>
272
+ </div>
273
+
274
+ <div className="kyro-meta-card">
275
+ <div className="kyro-card">
276
+ <div className="kyro-card-header">
277
+ <h3 className="kyro-card-title">Info</h3>
278
+ </div>
279
+ <div className="kyro-card-content">
280
+ <div className="kyro-meta-item">
281
+ <span className="kyro-meta-label">Status</span>
282
+ <span
283
+ className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${
284
+ status === "published"
285
+ ? "bg-green-100 text-green-700"
286
+ : "bg-gray-100 text-gray-600"
287
+ }`}
288
+ >
289
+ {status === "published" ? "Published" : "Draft"}
290
+ </span>
291
+ </div>
292
+ <div className="kyro-meta-item">
293
+ <span className="kyro-meta-label">Created</span>
294
+ <span className="kyro-meta-value">
295
+ {createdAt ? new Date(createdAt).toLocaleString() : "N/A"}
296
+ </span>
297
+ </div>
298
+ <div className="kyro-meta-item">
299
+ <span className="kyro-meta-label">Updated</span>
300
+ <span className="kyro-meta-value">
301
+ {updatedAt ? new Date(updatedAt).toLocaleString() : "N/A"}
302
+ </span>
303
+ </div>
304
+ {publishedAt && (
305
+ <div className="kyro-meta-item">
306
+ <span className="kyro-meta-label">Published</span>
307
+ <span className="kyro-meta-value">
308
+ {new Date(publishedAt).toLocaleString()}
309
+ </span>
310
+ </div>
311
+ )}
312
+ </div>
313
+ </div>
314
+ </div>
315
+ </div>
316
+
317
+ <ConfirmModal
318
+ open={showDeleteConfirm}
319
+ onClose={() => setShowDeleteConfirm(false)}
320
+ onConfirm={handleDelete}
321
+ title={`Delete ${label}?`}
322
+ message="This action cannot be undone."
323
+ confirmLabel="Delete"
324
+ variant="danger"
325
+ loading={deleting}
326
+ />
327
+ </div>
328
+ );
329
+ }
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import { Menu, Home, Database, User } from 'lucide-react';
3
+
4
+ interface IconProps {
5
+ size?: number;
6
+ className?: string;
7
+ }
8
+
9
+ export const MenuIcon: React.FC<IconProps> = ({ size = 24, className = '' }) => (
10
+ <Menu size={size} className={className} />
11
+ );
12
+
13
+ export const HomeIcon: React.FC<IconProps> = ({ size = 18, className = '' }) => (
14
+ <Home size={size} className={className} />
15
+ );
16
+
17
+ export const DatabaseIcon: React.FC<IconProps> = ({ size = 18, className = '' }) => (
18
+ <Database size={size} className={className} />
19
+ );
20
+
21
+ export const UserIcon: React.FC<IconProps> = ({ size = 20, className = '' }) => (
22
+ <User size={size} className={className} />
23
+ );