@kyro-cms/admin 0.1.5 → 0.1.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 (164) hide show
  1. package/README.md +149 -51
  2. package/package.json +52 -5
  3. package/src/collections/auth/index.ts +2 -2
  4. package/src/collections/portfolio/index.ts +343 -0
  5. package/src/components/ActionBar.tsx +153 -16
  6. package/src/components/Admin.tsx +136 -27
  7. package/src/components/ApiExplorer.tsx +325 -0
  8. package/src/components/ApiKeysManager.tsx +563 -0
  9. package/src/components/AuditLogsPage.tsx +664 -0
  10. package/src/components/AutoForm.tsx +1417 -661
  11. package/src/components/BrandingHub.tsx +267 -0
  12. package/src/components/BulkActionsBar.tsx +3 -3
  13. package/src/components/CreateView.tsx +3 -3
  14. package/src/components/Dashboard.tsx +393 -0
  15. package/src/components/DetailView.tsx +199 -57
  16. package/src/components/DeveloperCenter.tsx +403 -0
  17. package/src/components/EnhancedListView.tsx +786 -0
  18. package/src/components/GraphQLExplorer.tsx +675 -0
  19. package/src/components/GraphQLPlayground.tsx +627 -0
  20. package/src/components/ListView.tsx +191 -53
  21. package/src/components/MediaGallery.tsx +1569 -0
  22. package/src/components/Modal.tsx +149 -0
  23. package/src/components/RestPlayground.tsx +951 -0
  24. package/src/components/Sidebar.astro +237 -0
  25. package/src/components/UserManagement.tsx +204 -0
  26. package/src/components/VersionHistoryPanel.tsx +3 -3
  27. package/src/components/WebhookManager.tsx +608 -0
  28. package/src/components/blocks/AccordionBlock.tsx +97 -0
  29. package/src/components/blocks/ArrayBlock.tsx +75 -0
  30. package/src/components/blocks/BlockEditModal.MARKER +12 -0
  31. package/src/components/blocks/BlockEditModal.tsx +774 -0
  32. package/src/components/blocks/ButtonBlock.tsx +165 -0
  33. package/src/components/blocks/ChildBlocksTree.tsx +551 -0
  34. package/src/components/blocks/CodeBlock.tsx +66 -0
  35. package/src/components/blocks/ColumnsBlock.tsx +151 -0
  36. package/src/components/blocks/DividerBlock.tsx +43 -0
  37. package/src/components/blocks/FileBlock.tsx +64 -0
  38. package/src/components/blocks/HeadingBlock.tsx +81 -0
  39. package/src/components/blocks/HeroBlock.tsx +157 -0
  40. package/src/components/blocks/ImageBlock.tsx +83 -0
  41. package/src/components/blocks/LinkBlock.tsx +71 -0
  42. package/src/components/blocks/ListBlock.tsx +39 -0
  43. package/src/components/blocks/ParagraphBlock.tsx +61 -0
  44. package/src/components/blocks/RelationshipBlock.tsx +279 -0
  45. package/src/components/blocks/VStackBlock.tsx +75 -0
  46. package/src/components/blocks/VideoBlock.tsx +45 -0
  47. package/src/components/blocks/index.ts +10 -0
  48. package/src/components/fields/BlocksField.tsx +323 -0
  49. package/src/components/fields/CheckboxField.tsx +15 -9
  50. package/src/components/fields/CodeField.tsx +234 -0
  51. package/src/components/fields/DateField.tsx +38 -11
  52. package/src/components/fields/EditorClient.tsx +271 -0
  53. package/src/components/fields/FileField.tsx +390 -0
  54. package/src/components/fields/HybridContentField.tsx +109 -0
  55. package/src/components/fields/ImageField.tsx +429 -0
  56. package/src/components/fields/JSONField.tsx +361 -0
  57. package/src/components/fields/MarkdownField.tsx +282 -0
  58. package/src/components/fields/NumberField.tsx +42 -12
  59. package/src/components/fields/PortableTextField.tsx +143 -0
  60. package/src/components/fields/PortableTextRenderer.tsx +68 -0
  61. package/src/components/fields/RelationshipField.tsx +231 -59
  62. package/src/components/fields/SelectField.tsx +25 -15
  63. package/src/components/fields/TextField.tsx +45 -14
  64. package/src/components/fields/extensions/blockComponents.tsx +237 -0
  65. package/src/components/fields/extensions/blocksStore.ts +273 -0
  66. package/src/components/fields/index.ts +13 -0
  67. package/src/components/index.ts +1 -2
  68. package/src/components/layout/Header.tsx +2 -2
  69. package/src/components/layout/Layout.tsx +2 -2
  70. package/src/components/ui/Badge.tsx +9 -4
  71. package/src/components/ui/BlockDrawer.tsx +79 -0
  72. package/src/components/ui/Button.tsx +1 -1
  73. package/src/components/ui/CommandPalette.tsx +362 -0
  74. package/src/components/ui/CommandPaletteWrapper.tsx +97 -0
  75. package/src/components/ui/Dropdown.tsx +1 -1
  76. package/src/components/ui/Modal.tsx +37 -12
  77. package/src/components/ui/PromptModal.tsx +94 -0
  78. package/src/components/ui/SlidePanel.tsx +43 -16
  79. package/src/components/ui/Toast.tsx +80 -14
  80. package/src/env.d.ts +16 -0
  81. package/src/env.ts +20 -0
  82. package/src/index.ts +0 -1
  83. package/src/layouts/AdminLayout.astro +164 -170
  84. package/src/layouts/AuthLayout.astro +50 -0
  85. package/src/lib/MediaService.ts +541 -0
  86. package/src/lib/auth/sqlite-adapter.ts +319 -0
  87. package/src/lib/config.ts +22 -6
  88. package/src/lib/dataStore.ts +132 -74
  89. package/src/lib/db/adapter.ts +54 -0
  90. package/src/lib/db/drizzle-mysql-adapter.ts +194 -0
  91. package/src/lib/db/drizzle-mysql-auth-adapter.ts +327 -0
  92. package/src/lib/db/drizzle-postgres-adapter.ts +202 -0
  93. package/src/lib/db/drizzle-postgres-auth-adapter.ts +304 -0
  94. package/src/lib/db/drizzle-sqlite-adapter.ts +227 -0
  95. package/src/lib/db/drizzle-sqlite-auth-adapter.ts +548 -0
  96. package/src/lib/db/index.ts +449 -0
  97. package/src/lib/db/mongodb-adapter.ts +207 -0
  98. package/src/lib/db/mongodb-auth-adapter.ts +305 -0
  99. package/src/lib/db/schema/mysql-auth.ts +113 -0
  100. package/src/lib/db/schema/mysql-content.ts +20 -0
  101. package/src/lib/db/schema/postgres-auth.ts +116 -0
  102. package/src/lib/db/schema/postgres-content.ts +35 -0
  103. package/src/lib/db/schema/postgres-media.ts +52 -0
  104. package/src/lib/db/schema/postgres-settings.ts +11 -0
  105. package/src/lib/db/schema/sqlite-auth.ts +112 -0
  106. package/src/lib/db/schema/sqlite-content.ts +20 -0
  107. package/src/lib/graphql/index.ts +1 -0
  108. package/src/lib/graphql/schema.ts +443 -0
  109. package/src/lib/rate-limit.ts +267 -0
  110. package/src/lib/storage.ts +374 -0
  111. package/src/lib/store.ts +85 -0
  112. package/src/middleware.ts +116 -28
  113. package/src/pages/[collection]/[id].astro +178 -122
  114. package/src/pages/[collection]/index.astro +24 -156
  115. package/src/pages/admin/api-explorer.astro +98 -0
  116. package/src/pages/admin/graphql-explorer.astro +40 -0
  117. package/src/pages/admin/graphql.astro +97 -0
  118. package/src/pages/admin/index.astro +286 -0
  119. package/src/pages/admin/keys.astro +8 -0
  120. package/src/pages/admin/rest-playground.astro +44 -0
  121. package/src/pages/admin/webhooks.astro +8 -0
  122. package/src/pages/api/[collection]/[id]/publish.ts +44 -0
  123. package/src/pages/api/[collection]/[id]/unpublish.ts +42 -0
  124. package/src/pages/api/[collection]/[id]/versions.ts +36 -0
  125. package/src/pages/api/[collection]/[id].ts +102 -159
  126. package/src/pages/api/[collection]/index.ts +151 -230
  127. package/src/pages/api/auth/[id].ts +48 -69
  128. package/src/pages/api/auth/audit-logs.ts +20 -43
  129. package/src/pages/api/auth/login.ts +159 -45
  130. package/src/pages/api/auth/logout.ts +50 -20
  131. package/src/pages/api/auth/refresh.ts +119 -0
  132. package/src/pages/api/auth/register.ts +110 -40
  133. package/src/pages/api/auth/users.ts +22 -97
  134. package/src/pages/api/collections.ts +59 -0
  135. package/src/pages/api/globals/[slug]/test.ts +172 -0
  136. package/src/pages/api/globals/[slug].ts +42 -0
  137. package/src/pages/api/graphql.ts +90 -0
  138. package/src/pages/api/health.ts +417 -40
  139. package/src/pages/api/keys/[id].ts +26 -0
  140. package/src/pages/api/keys/index.ts +75 -0
  141. package/src/pages/api/media/[id].ts +309 -0
  142. package/src/pages/api/media/folders.ts +609 -0
  143. package/src/pages/api/media/index.ts +146 -0
  144. package/src/pages/api/media/resize.ts +267 -0
  145. package/src/pages/api/search.ts +82 -0
  146. package/src/pages/api/slug-availability.ts +70 -0
  147. package/src/pages/api/storage-config.ts +20 -0
  148. package/src/pages/api/storage-status.ts +206 -0
  149. package/src/pages/api/upload.ts +334 -0
  150. package/src/pages/api/webhooks/index.ts +71 -0
  151. package/src/pages/audit/index.astro +2 -104
  152. package/src/pages/login.astro +82 -0
  153. package/src/pages/media.astro +10 -0
  154. package/src/pages/preview/[collection]/[id].astro +178 -0
  155. package/src/pages/register.astro +102 -0
  156. package/src/pages/roles/index.astro +21 -21
  157. package/src/pages/settings/[slug].astro +162 -0
  158. package/src/pages/settings/index.astro +9 -0
  159. package/src/pages/users/[id].astro +29 -21
  160. package/src/pages/users/index.astro +22 -17
  161. package/src/pages/users/new.astro +18 -17
  162. package/src/styles/main.css +553 -128
  163. package/src/components/layout/Sidebar.tsx +0 -497
  164. package/src/pages/index.astro +0 -225
@@ -1,4 +1,15 @@
1
1
  import React, { useEffect, type ReactNode } from "react";
2
+ import { createPortal } from "react-dom";
3
+
4
+ export function ModalContent({ children }: { children: ReactNode }) {
5
+ return <div className="text-[var(--kyro-text-secondary)]">{children}</div>;
6
+ }
7
+
8
+ export function ModalActions({ children }: { children: ReactNode }) {
9
+ return (
10
+ <div className="flex items-center justify-end gap-3 mt-6">{children}</div>
11
+ );
12
+ }
2
13
 
3
14
  interface ModalProps {
4
15
  open: boolean;
@@ -7,6 +18,7 @@ interface ModalProps {
7
18
  children: ReactNode;
8
19
  footer?: ReactNode;
9
20
  size?: "sm" | "md" | "lg";
21
+ variant?: "default" | "danger";
10
22
  }
11
23
 
12
24
  export function Modal({
@@ -16,6 +28,7 @@ export function Modal({
16
28
  children,
17
29
  footer,
18
30
  size = "md",
31
+ variant = "default",
19
32
  }: ModalProps) {
20
33
  useEffect(() => {
21
34
  const handleEscape = (e: KeyboardEvent) => {
@@ -41,20 +54,27 @@ export function Modal({
41
54
  lg: "max-w-2xl",
42
55
  };
43
56
 
44
- return (
45
- <div className="fixed inset-0 z-50 flex items-center justify-center">
57
+ return createPortal(
58
+ <div className="fixed inset-0 z-[9999] flex items-center justify-center">
46
59
  <div
47
60
  className="absolute inset-0 bg-black/50 backdrop-blur-sm"
48
61
  onClick={onClose}
49
62
  />
50
63
  <div
51
- className={`relative w-full ${sizeClasses[size]} mx-4 bg-white rounded-xl shadow-2xl animate-in fade-in zoom-in-95 duration-200`}
64
+ className={`relative w-full ${sizeClasses[size]} mx-4 bg-[var(--kyro-surface)] rounded-lg shadow-2xl animate-in fade-in zoom-in-95 duration-200 border ${
65
+ variant === "danger"
66
+ ? "border-red-500/20"
67
+ : "border-[var(--kyro-border)]"
68
+ }`}
52
69
  >
53
- <div className="flex items-center justify-between px-6 py-4 border-b border-gray-200">
54
- <h2 className="text-lg font-semibold text-gray-900">{title}</h2>
70
+ <div className="flex items-center justify-between px-6 py-4 border-b border-[var(--kyro-border)]">
71
+ <h2 className="text-lg font-semibold text-[var(--kyro-text-primary)]">
72
+ {title}
73
+ </h2>
55
74
  <button
75
+ type="button"
56
76
  onClick={onClose}
57
- className="p-1 text-gray-400 hover:text-gray-600 rounded-lg hover:bg-gray-100 transition-colors"
77
+ className="p-1 text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] rounded-lg hover:bg-[var(--kyro-surface-accent)] transition-colors"
58
78
  >
59
79
  <svg
60
80
  width="20"
@@ -70,12 +90,13 @@ export function Modal({
70
90
  </div>
71
91
  <div className="px-6 py-4">{children}</div>
72
92
  {footer && (
73
- <div className="flex items-center justify-end gap-3 px-6 py-4 border-t border-gray-200 bg-gray-50 rounded-b-xl">
93
+ <div className="flex items-center justify-end gap-3 px-6 py-4 border-t border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)] rounded-b-lg">
74
94
  {footer}
75
95
  </div>
76
96
  )}
77
97
  </div>
78
- </div>
98
+ </div>,
99
+ document.body,
79
100
  );
80
101
  }
81
102
 
@@ -111,17 +132,21 @@ export function ConfirmModal({
111
132
  footer={
112
133
  <>
113
134
  <button
135
+ type="button"
114
136
  onClick={onClose}
115
137
  disabled={loading}
116
- className="kyro-btn kyro-btn-secondary kyro-btn-md"
138
+ className="px-4 py-2 rounded-lg font-medium text-sm border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] hover:text-[var(--kyro-text-primary)] transition-colors"
117
139
  >
118
140
  {cancelLabel}
119
141
  </button>
120
142
  <button
143
+ type="button"
121
144
  onClick={onConfirm}
122
145
  disabled={loading}
123
- className={`kyro-btn kyro-btn-md ${
124
- variant === "danger" ? "kyro-btn-danger" : "kyro-btn-primary"
146
+ className={`px-4 py-2 rounded-lg font-medium text-sm transition-colors ${
147
+ variant === "danger"
148
+ ? "bg-red-500 text-white hover:bg-red-600"
149
+ : "bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] hover:opacity-90"
125
150
  }`}
126
151
  >
127
152
  {loading ? "Loading..." : confirmLabel}
@@ -129,7 +154,7 @@ export function ConfirmModal({
129
154
  </>
130
155
  }
131
156
  >
132
- <p className="text-gray-600">{message}</p>
157
+ <p className="text-[var(--kyro-text-secondary)]">{message}</p>
133
158
  </Modal>
134
159
  );
135
160
  }
@@ -0,0 +1,94 @@
1
+ import React, { useState } from "react";
2
+ import { createPortal } from "react-dom";
3
+
4
+ interface PromptModalProps {
5
+ open: boolean;
6
+ onClose: () => void;
7
+ onSubmit: (value: string) => void;
8
+ title: string;
9
+ placeholder?: string;
10
+ defaultValue?: string;
11
+ }
12
+
13
+ export function PromptModal({
14
+ open,
15
+ onClose,
16
+ onSubmit,
17
+ title,
18
+ placeholder = "",
19
+ defaultValue = "",
20
+ }: PromptModalProps) {
21
+ const [value, setValue] = useState(defaultValue);
22
+
23
+ const handleSubmit = (e: React.FormEvent) => {
24
+ e.preventDefault();
25
+ if (value.trim()) {
26
+ onSubmit(value.trim());
27
+ setValue("");
28
+ onClose();
29
+ }
30
+ };
31
+
32
+ if (!open) return null;
33
+
34
+ return createPortal(
35
+ <div className="fixed inset-0 z-[9999] flex items-center justify-center">
36
+ <div
37
+ className="absolute inset-0 bg-black/50 backdrop-blur-sm"
38
+ onClick={onClose}
39
+ />
40
+ <div className="relative w-full max-w-md mx-4 bg-[var(--kyro-surface)] rounded-lg shadow-2xl animate-in fade-in zoom-in-95 duration-200 border border-[var(--kyro-border)]">
41
+ <div className="flex items-center justify-between px-6 py-4 border-b border-[var(--kyro-border)]">
42
+ <h2 className="text-lg font-semibold text-[var(--kyro-text-primary)]">
43
+ {title}
44
+ </h2>
45
+ <button
46
+ type="button"
47
+ onClick={onClose}
48
+ className="p-1 text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] rounded-lg hover:bg-[var(--kyro-surface-accent)] transition-colors"
49
+ >
50
+ <svg
51
+ width="20"
52
+ height="20"
53
+ viewBox="0 0 24 24"
54
+ fill="none"
55
+ stroke="currentColor"
56
+ strokeWidth="2"
57
+ >
58
+ <path d="M18 6L6 18M6 6l12 12" />
59
+ </svg>
60
+ </button>
61
+ </div>
62
+ <form onSubmit={handleSubmit}>
63
+ <div className="px-6 py-4">
64
+ <input
65
+ type="text"
66
+ value={value}
67
+ onChange={(e) => setValue(e.target.value)}
68
+ placeholder={placeholder}
69
+ autoFocus
70
+ className="w-full px-3 py-2 border border-[var(--kyro-input-border)] rounded-lg bg-[var(--kyro-input-bg)] text-[var(--kyro-text-primary)] placeholder-[var(--kyro-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--kyro-sidebar-active)] focus:border-transparent"
71
+ />
72
+ </div>
73
+ <div className="flex items-center justify-end gap-3 px-6 py-4 border-t border-[var(--kyro-border)] bg-[var(--kyro-surface-accent)] rounded-b-lg">
74
+ <button
75
+ type="button"
76
+ onClick={onClose}
77
+ className="px-4 py-2 rounded-lg font-medium text-sm border border-[var(--kyro-border)] text-[var(--kyro-text-secondary)] hover:bg-[var(--kyro-surface-accent)] hover:text-[var(--kyro-text-primary)] transition-colors"
78
+ >
79
+ Cancel
80
+ </button>
81
+ <button
82
+ type="submit"
83
+ disabled={!value.trim()}
84
+ className="px-4 py-2 rounded-lg font-medium text-sm bg-[var(--kyro-sidebar-active)] text-[var(--kyro-sidebar-text-active)] hover:opacity-90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
85
+ >
86
+ Confirm
87
+ </button>
88
+ </div>
89
+ </form>
90
+ </div>
91
+ </div>,
92
+ document.body,
93
+ );
94
+ }
@@ -1,11 +1,13 @@
1
- import React, { useEffect, useRef, type ReactNode } from "react";
1
+ import React, { useEffect, useRef, useState, type ReactNode } from "react";
2
+ import { createPortal } from "react-dom";
2
3
 
3
4
  interface SlidePanelProps {
4
5
  open: boolean;
5
6
  onClose: () => void;
6
7
  title: string;
7
8
  children: ReactNode;
8
- width?: "sm" | "md" | "lg";
9
+ width?: "sm" | "md" | "lg" | "xl";
10
+ showOverlay?: boolean;
9
11
  }
10
12
 
11
13
  export function SlidePanel({
@@ -14,8 +16,14 @@ export function SlidePanel({
14
16
  title,
15
17
  children,
16
18
  width = "md",
19
+ showOverlay = false,
17
20
  }: SlidePanelProps) {
18
21
  const panelRef = useRef<HTMLDivElement>(null);
22
+ const [hydrated, setHydrated] = useState(false);
23
+
24
+ useEffect(() => {
25
+ setHydrated(true);
26
+ }, []);
19
27
 
20
28
  useEffect(() => {
21
29
  const handleEscape = (e: KeyboardEvent) => {
@@ -35,28 +43,37 @@ export function SlidePanel({
35
43
 
36
44
  const widthClasses = {
37
45
  sm: "w-[320px]",
38
- md: "w-[480px]",
39
- lg: "w-[640px]",
46
+ md: "w-[400px]",
47
+ lg: "w-[500px]",
48
+ xl: "w-[600px]",
40
49
  };
41
50
 
42
- if (!open) return null;
51
+ if (!open || !hydrated) return null;
43
52
 
44
- return (
45
- <div className="fixed inset-0 z-50">
46
- <div className="absolute inset-0 bg-black/30" onClick={onClose} />
53
+ return createPortal(
54
+ <>
55
+ {showOverlay && (
56
+ <div
57
+ className="fixed inset-0 z-[99998] bg-black/50 backdrop-blur-sm"
58
+ onClick={onClose}
59
+ />
60
+ )}
47
61
  <div
48
62
  ref={panelRef}
49
- className={`absolute right-0 top-0 h-full ${widthClasses[width]} bg-white shadow-2xl flex flex-col animate-in slide-in-from-right duration-300`}
63
+ className={`fixed right-0 top-0 bottom-0 z-[99999] ${widthClasses[width]} bg-[var(--kyro-surface)] border-l border-[var(--kyro-border)] shadow-2xl flex flex-col animate-slideIn`}
50
64
  >
51
- <div className="flex items-center justify-between px-6 py-4 border-b border-gray-200">
52
- <h2 className="text-lg font-semibold text-gray-900">{title}</h2>
65
+ <div className="flex items-center justify-between px-4 py-3 border-b border-[var(--kyro-border)] bg-[var(--kyro-surface)]">
66
+ <h2 className="text-sm font-semibold text-[var(--kyro-text-primary)]">
67
+ {title}
68
+ </h2>
53
69
  <button
70
+ type="button"
54
71
  onClick={onClose}
55
- className="p-1 text-gray-400 hover:text-gray-600 rounded-lg hover:bg-gray-100 transition-colors"
72
+ className="p-1.5 text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)] rounded-lg transition-colors"
56
73
  >
57
74
  <svg
58
- width="20"
59
- height="20"
75
+ width="16"
76
+ height="16"
60
77
  viewBox="0 0 24 24"
61
78
  fill="none"
62
79
  stroke="currentColor"
@@ -66,8 +83,18 @@ export function SlidePanel({
66
83
  </svg>
67
84
  </button>
68
85
  </div>
69
- <div className="flex-1 overflow-y-auto p-6">{children}</div>
86
+ <div className="flex-1 overflow-y-auto p-4">{children}</div>
70
87
  </div>
71
- </div>
88
+ <style>{`
89
+ @keyframes slideIn {
90
+ from { transform: translateX(100%); }
91
+ to { transform: translateX(0); }
92
+ }
93
+ .animate-slideIn {
94
+ animation: slideIn 300ms ease-out;
95
+ }
96
+ `}</style>
97
+ </>,
98
+ document.body,
72
99
  );
73
100
  }
@@ -1,21 +1,42 @@
1
- import { createContext, useContext, type ReactNode } from 'react';
1
+ import {
2
+ createContext,
3
+ useContext,
4
+ useState,
5
+ type ReactNode,
6
+ useCallback,
7
+ } from "react";
2
8
 
3
9
  interface Toast {
4
10
  id: string;
5
- type: 'success' | 'error' | 'info' | 'warning';
11
+ type: "success" | "error" | "info" | "warning";
6
12
  message: string;
7
13
  }
8
14
 
9
15
  interface ToastContextType {
10
16
  toasts: Toast[];
11
- addToast: (type: Toast['type'], message: string) => void;
17
+ addToast: (type: Toast["type"], message: string) => void;
12
18
  removeToast: (id: string) => void;
13
19
  }
14
20
 
15
21
  const ToastContext = createContext<ToastContextType | null>(null);
16
22
 
23
+ export function createToastContext() {
24
+ const [toasts, setToasts] = useState<Toast[]>([]);
25
+
26
+ const addToast = useCallback((type: Toast["type"], message: string) => {
27
+ const id = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
28
+ setToasts((prev) => [...prev, { id, type, message }]);
29
+ }, []);
30
+
31
+ const removeToast = useCallback((id: string) => {
32
+ setToasts((prev) => prev.filter((t) => t.id !== id));
33
+ }, []);
34
+
35
+ return { toasts, addToast, removeToast };
36
+ }
37
+
17
38
  interface ToastProps {
18
- type: Toast['type'];
39
+ type: Toast["type"];
19
40
  message: string;
20
41
  onClose: () => void;
21
42
  }
@@ -23,37 +44,72 @@ interface ToastProps {
23
44
  export function Toast({ type, message, onClose }: ToastProps) {
24
45
  const icons = {
25
46
  success: (
26
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
47
+ <svg
48
+ width="20"
49
+ height="20"
50
+ viewBox="0 0 24 24"
51
+ fill="none"
52
+ stroke="currentColor"
53
+ strokeWidth="2"
54
+ >
27
55
  <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
28
56
  <path d="M22 4L12 14.01l-3-3" />
29
57
  </svg>
30
58
  ),
31
59
  error: (
32
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
60
+ <svg
61
+ width="20"
62
+ height="20"
63
+ viewBox="0 0 24 24"
64
+ fill="none"
65
+ stroke="currentColor"
66
+ strokeWidth="2"
67
+ >
33
68
  <circle cx="12" cy="12" r="10" />
34
69
  <path d="M15 9l-6 6M9 9l6 6" />
35
70
  </svg>
36
71
  ),
37
72
  warning: (
38
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
73
+ <svg
74
+ width="20"
75
+ height="20"
76
+ viewBox="0 0 24 24"
77
+ fill="none"
78
+ stroke="currentColor"
79
+ strokeWidth="2"
80
+ >
39
81
  <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
40
82
  <path d="M12 9v4M12 17h.01" />
41
83
  </svg>
42
84
  ),
43
85
  info: (
44
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
86
+ <svg
87
+ width="20"
88
+ height="20"
89
+ viewBox="0 0 24 24"
90
+ fill="none"
91
+ stroke="currentColor"
92
+ strokeWidth="2"
93
+ >
45
94
  <circle cx="12" cy="12" r="10" />
46
95
  <path d="M12 16v-4M12 8h.01" />
47
96
  </svg>
48
- )
97
+ ),
49
98
  };
50
99
 
51
100
  return (
52
101
  <div className={`kyro-toast kyro-toast-${type}`}>
53
102
  <span className="kyro-toast-icon">{icons[type]}</span>
54
103
  <span className="kyro-toast-message">{message}</span>
55
- <button className="kyro-toast-close" onClick={onClose}>
56
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
104
+ <button type="button" className="kyro-toast-close" onClick={onClose}>
105
+ <svg
106
+ width="16"
107
+ height="16"
108
+ viewBox="0 0 24 24"
109
+ fill="none"
110
+ stroke="currentColor"
111
+ strokeWidth="2"
112
+ >
57
113
  <path d="M18 6L6 18M6 6l12 12" />
58
114
  </svg>
59
115
  </button>
@@ -61,9 +117,19 @@ export function Toast({ type, message, onClose }: ToastProps) {
61
117
  );
62
118
  }
63
119
 
64
- export function ToastProvider({ children }: { children: ReactNode }) {
120
+ export function ToastProvider({
121
+ children,
122
+ toasts = [],
123
+ addToast = () => {},
124
+ removeToast = () => {},
125
+ }: {
126
+ children: ReactNode;
127
+ toasts?: Toast[];
128
+ addToast?: (type: Toast["type"], message: string) => void;
129
+ removeToast?: (id: string) => void;
130
+ }) {
65
131
  return (
66
- <ToastContext.Provider value={{ toasts: [], addToast: () => {}, removeToast: () => {} }}>
132
+ <ToastContext.Provider value={{ toasts, addToast, removeToast }}>
67
133
  {children}
68
134
  </ToastContext.Provider>
69
135
  );
@@ -72,7 +138,7 @@ export function ToastProvider({ children }: { children: ReactNode }) {
72
138
  export function useToast() {
73
139
  const context = useContext(ToastContext);
74
140
  if (!context) {
75
- throw new Error('useToast must be used within a ToastProvider');
141
+ throw new Error("useToast must be used within a ToastProvider");
76
142
  }
77
143
  return context;
78
144
  }
package/src/env.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /// <reference types="astro/client" />
2
+
3
+ declare global {
4
+ namespace App {
5
+ interface Locals {
6
+ user?: {
7
+ id: string;
8
+ email: string;
9
+ role: string;
10
+ tenantId?: string;
11
+ };
12
+ }
13
+ }
14
+ }
15
+
16
+ export {};
package/src/env.ts ADDED
@@ -0,0 +1,20 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+
4
+ const envPath = path.join(process.cwd(), "..", ".env");
5
+ if (fs.existsSync(envPath)) {
6
+ const envContent = fs.readFileSync(envPath, "utf-8");
7
+ envContent.split("\n").forEach((line) => {
8
+ const trimmed = line.trim();
9
+ if (trimmed && !trimmed.startsWith("#")) {
10
+ const eqIndex = trimmed.indexOf("=");
11
+ if (eqIndex > 0) {
12
+ const key = trimmed.substring(0, eqIndex);
13
+ const value = trimmed.substring(eqIndex + 1);
14
+ if (!process.env[key]) {
15
+ process.env[key] = value;
16
+ }
17
+ }
18
+ }
19
+ });
20
+ }
package/src/index.ts CHANGED
@@ -21,7 +21,6 @@ export {
21
21
  type ThemeMode,
22
22
  } from "./components/ThemeProvider";
23
23
  export * from "./components/layout/Header";
24
- export * from "./components/layout/Sidebar";
25
24
  export * from "./components/ui/Button";
26
25
  export * from "./components/ui/Badge";
27
26
  export * from "./components/ui/Spinner";