insforge 0.3.1

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 (395) hide show
  1. package/.dockerignore +58 -0
  2. package/.env.example +49 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.yml +83 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +11 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.yml +79 -0
  6. package/.github/copilot-instructions.md +147 -0
  7. package/.github/workflows/build-image.yml +65 -0
  8. package/.github/workflows/ci-premerge-check.yml +24 -0
  9. package/.github/workflows/deploy-aws.yml +130 -0
  10. package/.github/workflows/lint-and-format.yml +33 -0
  11. package/.prettierignore +65 -0
  12. package/.prettierrc +9 -0
  13. package/CHANGELOG.md +3 -0
  14. package/CONTRIBUTING.md +126 -0
  15. package/Dockerfile +27 -0
  16. package/GITHUB_OAUTH_SETUP.md +49 -0
  17. package/GOOGLE_OAUTH_SETUP.md +148 -0
  18. package/LICENSE +201 -0
  19. package/README.md +134 -0
  20. package/assets/Dark.svg +23 -0
  21. package/assets/archDiagram.png +0 -0
  22. package/assets/banner.png +0 -0
  23. package/assets/mcpInstallv2.png +0 -0
  24. package/assets/sampleResponse.png +0 -0
  25. package/assets/signin.png +0 -0
  26. package/assets/userflow.png +0 -0
  27. package/backend/migrations/000_create-base-tables.sql +142 -0
  28. package/backend/migrations/001_create-helper-functions.sql +41 -0
  29. package/backend/migrations/002_rename-auth-tables.sql +30 -0
  30. package/backend/migrations/003_create-users-table.sql +56 -0
  31. package/backend/migrations/004_add-reload-postgrest-func.sql +24 -0
  32. package/backend/migrations/005_enable-project-admin-modify-users.sql +30 -0
  33. package/backend/migrations/006_modify-ai-usage-table.sql +25 -0
  34. package/backend/migrations/007_drop-metadata-table.sql +2 -0
  35. package/backend/migrations/008_add-system-tables.sql +77 -0
  36. package/backend/migrations/009_add-function-secrets.sql +24 -0
  37. package/backend/migrations/010_modify-ai-config-modalities.sql +93 -0
  38. package/backend/migrations/011_refactor-secrets-table.sql +15 -0
  39. package/backend/migrations/012_add-storage-uploaded-by.sql +8 -0
  40. package/backend/package.json +75 -0
  41. package/backend/src/api/middleware/auth.ts +240 -0
  42. package/backend/src/api/middleware/error.ts +231 -0
  43. package/backend/src/api/middleware/upload.ts +59 -0
  44. package/backend/src/api/routes/agent.ts +29 -0
  45. package/backend/src/api/routes/ai.ts +472 -0
  46. package/backend/src/api/routes/auth.oauth.ts +482 -0
  47. package/backend/src/api/routes/auth.ts +386 -0
  48. package/backend/src/api/routes/database.advance.ts +275 -0
  49. package/backend/src/api/routes/database.records.ts +246 -0
  50. package/backend/src/api/routes/database.tables.ts +161 -0
  51. package/backend/src/api/routes/docs.ts +66 -0
  52. package/backend/src/api/routes/functions.ts +183 -0
  53. package/backend/src/api/routes/logs.ts +150 -0
  54. package/backend/src/api/routes/metadata.ts +160 -0
  55. package/backend/src/api/routes/openapi.ts +82 -0
  56. package/backend/src/api/routes/secrets.ts +199 -0
  57. package/backend/src/api/routes/storage.ts +547 -0
  58. package/backend/src/api/routes/usage.ts +96 -0
  59. package/backend/src/core/ai/chat.ts +207 -0
  60. package/backend/src/core/ai/client.ts +242 -0
  61. package/backend/src/core/ai/config.ts +187 -0
  62. package/backend/src/core/ai/image.ts +156 -0
  63. package/backend/src/core/ai/model.ts +117 -0
  64. package/backend/src/core/ai/usage.ts +290 -0
  65. package/backend/src/core/auth/auth.ts +781 -0
  66. package/backend/src/core/auth/oauth.ts +398 -0
  67. package/backend/src/core/database/advance.ts +1074 -0
  68. package/backend/src/core/database/manager.ts +178 -0
  69. package/backend/src/core/database/table.ts +772 -0
  70. package/backend/src/core/documentation/agent.ts +689 -0
  71. package/backend/src/core/documentation/openapi.ts +856 -0
  72. package/backend/src/core/functions/functions.ts +310 -0
  73. package/backend/src/core/logs/analytics.ts +76 -0
  74. package/backend/src/core/logs/audit.ts +255 -0
  75. package/backend/src/core/logs/providers/base.provider.ts +83 -0
  76. package/backend/src/core/logs/providers/cloudwatch.provider.ts +510 -0
  77. package/backend/src/core/logs/providers/localdb.provider.ts +246 -0
  78. package/backend/src/core/secrets/encryption.ts +58 -0
  79. package/backend/src/core/secrets/secrets.ts +410 -0
  80. package/backend/src/core/socket/socket.ts +388 -0
  81. package/backend/src/core/socket/types.ts +79 -0
  82. package/backend/src/core/storage/storage.ts +923 -0
  83. package/backend/src/server.ts +288 -0
  84. package/backend/src/types/ai.ts +46 -0
  85. package/backend/src/types/auth.ts +90 -0
  86. package/backend/src/types/database.ts +136 -0
  87. package/backend/src/types/error-constants.ts +86 -0
  88. package/backend/src/types/logs.ts +47 -0
  89. package/backend/src/types/profile.ts +55 -0
  90. package/backend/src/types/storage.ts +23 -0
  91. package/backend/src/utils/cloud-token.ts +39 -0
  92. package/backend/src/utils/constants.ts +1 -0
  93. package/backend/src/utils/environment.ts +35 -0
  94. package/backend/src/utils/helpers.ts +49 -0
  95. package/backend/src/utils/logger.ts +13 -0
  96. package/backend/src/utils/response.ts +62 -0
  97. package/backend/src/utils/seed.ts +205 -0
  98. package/backend/src/utils/sql-parser.ts +63 -0
  99. package/backend/src/utils/uuid.ts +9 -0
  100. package/backend/src/utils/validations.ts +129 -0
  101. package/backend/tests/README.md +134 -0
  102. package/backend/tests/cleanup-all-test-data.sh +231 -0
  103. package/backend/tests/cloud/test-s3-multitenant.sh +132 -0
  104. package/backend/tests/local/comprehensive-curl-tests.sh +156 -0
  105. package/backend/tests/local/test-auth-router.sh +144 -0
  106. package/backend/tests/local/test-database-router.sh +222 -0
  107. package/backend/tests/local/test-e2e.sh +241 -0
  108. package/backend/tests/local/test-fk-errors.sh +97 -0
  109. package/backend/tests/local/test-id-field.sh +201 -0
  110. package/backend/tests/local/test-public-bucket.sh +265 -0
  111. package/backend/tests/local/test-secrets.sh +248 -0
  112. package/backend/tests/local/test-serverless-functions.sh.disabled +325 -0
  113. package/backend/tests/local/test-traditional-rest.sh +209 -0
  114. package/backend/tests/manual/README.md +51 -0
  115. package/backend/tests/manual/create-large-table-simple.sql +11 -0
  116. package/backend/tests/manual/seed-large-table.sql +101 -0
  117. package/backend/tests/manual/setup-large-table-extras.sql +34 -0
  118. package/backend/tests/manual/test-better-auth.sh +303 -0
  119. package/backend/tests/manual/test-bulk-upsert.sh +410 -0
  120. package/backend/tests/manual/test-database-advance.sh +297 -0
  121. package/backend/tests/manual/test-postgrest-stability.sh +192 -0
  122. package/backend/tests/manual/test-rawsql-export-import.sh +412 -0
  123. package/backend/tests/manual/test-universal-storage.sh +264 -0
  124. package/backend/tests/manual/test-users.sql +18 -0
  125. package/backend/tests/run-all-tests.sh +140 -0
  126. package/backend/tests/setup.ts +22 -0
  127. package/backend/tests/test-config.sh +303 -0
  128. package/backend/tsconfig.json +23 -0
  129. package/backend/tsup.config.ts +18 -0
  130. package/backend/vitest.config.ts +22 -0
  131. package/docker-compose.prod.yml +145 -0
  132. package/docker-compose.yml +167 -0
  133. package/docker-init/db/db-init.sql +125 -0
  134. package/docker-init/db/jwt.sql +5 -0
  135. package/docker-init/db/logs.sql +9 -0
  136. package/docker-init/db/postgresql.conf +17 -0
  137. package/docs/deprecated/insforge-auth-api.md +215 -0
  138. package/docs/deprecated/insforge-auth-sdk.md +100 -0
  139. package/docs/deprecated/insforge-db-api.md +359 -0
  140. package/docs/deprecated/insforge-db-sdk.md +140 -0
  141. package/docs/deprecated/insforge-debug-sdk.md +157 -0
  142. package/docs/deprecated/insforge-debug.md +65 -0
  143. package/docs/deprecated/insforge-instructions.md +124 -0
  144. package/docs/deprecated/insforge-project.md +118 -0
  145. package/docs/deprecated/insforge-storage-api.md +279 -0
  146. package/docs/deprecated/insforge-storage-sdk.md +159 -0
  147. package/docs/insforge-instructions-sdk.md +407 -0
  148. package/eslint.config.js +317 -0
  149. package/examples/oauth/frontend-oauth-example.html +251 -0
  150. package/examples/response-examples.md +444 -0
  151. package/frontend/README.md +112 -0
  152. package/frontend/components.json +17 -0
  153. package/frontend/index.html +13 -0
  154. package/frontend/package.json +63 -0
  155. package/frontend/public/favicon.ico +0 -0
  156. package/frontend/src/App.tsx +106 -0
  157. package/frontend/src/assets/icons/checkbox_checked.svg +6 -0
  158. package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -0
  159. package/frontend/src/assets/icons/checked.svg +3 -0
  160. package/frontend/src/assets/icons/error.svg +3 -0
  161. package/frontend/src/assets/icons/pencil.svg +4 -0
  162. package/frontend/src/assets/icons/refresh.svg +4 -0
  163. package/frontend/src/assets/icons/step_active.svg +3 -0
  164. package/frontend/src/assets/icons/step_inactive.svg +11 -0
  165. package/frontend/src/assets/icons/warning.svg +3 -0
  166. package/frontend/src/assets/logos/amazon.svg +1 -0
  167. package/frontend/src/assets/logos/claude_code.svg +3 -0
  168. package/frontend/src/assets/logos/cline.svg +6 -0
  169. package/frontend/src/assets/logos/cursor.svg +20 -0
  170. package/frontend/src/assets/logos/discord.svg +9 -0
  171. package/frontend/src/assets/logos/gemini.svg +19 -0
  172. package/frontend/src/assets/logos/github.svg +5 -0
  173. package/frontend/src/assets/logos/google.svg +13 -0
  174. package/frontend/src/assets/logos/grok.svg +10 -0
  175. package/frontend/src/assets/logos/insforge_dark.svg +15 -0
  176. package/frontend/src/assets/logos/insforge_light.svg +15 -0
  177. package/frontend/src/assets/logos/openai.svg +10 -0
  178. package/frontend/src/assets/logos/roo_code.svg +9 -0
  179. package/frontend/src/assets/logos/trae.svg +3 -0
  180. package/frontend/src/assets/logos/windsurf.svg +10 -0
  181. package/frontend/src/components/ButtonWithLoading.tsx +27 -0
  182. package/frontend/src/components/Checkbox.tsx +61 -0
  183. package/frontend/src/components/CodeBlock.tsx +32 -0
  184. package/frontend/src/components/ConfirmDialog.tsx +96 -0
  185. package/frontend/src/components/CopyButton.tsx +69 -0
  186. package/frontend/src/components/DeleteActionButton.tsx +42 -0
  187. package/frontend/src/components/EmptyState.tsx +41 -0
  188. package/frontend/src/components/ErrorState.tsx +35 -0
  189. package/frontend/src/components/FeatureSidebar.tsx +126 -0
  190. package/frontend/src/components/FeatureSidebarItem.tsx +101 -0
  191. package/frontend/src/components/JsonHighlight.tsx +61 -0
  192. package/frontend/src/components/LoadingState.tsx +16 -0
  193. package/frontend/src/components/PaginationControls.tsx +54 -0
  194. package/frontend/src/components/PromptDialog.tsx +68 -0
  195. package/frontend/src/components/SearchInput.tsx +90 -0
  196. package/frontend/src/components/SelectionClearButton.tsx +26 -0
  197. package/frontend/src/components/Stepper.tsx +139 -0
  198. package/frontend/src/components/ThemeToggle.tsx +58 -0
  199. package/frontend/src/components/TypeBadge.tsx +20 -0
  200. package/frontend/src/components/datagrid/DataGrid.tsx +264 -0
  201. package/frontend/src/components/datagrid/DefaultCellRenderer.tsx +114 -0
  202. package/frontend/src/components/datagrid/IdCell.tsx +44 -0
  203. package/frontend/src/components/datagrid/SortableHeader.tsx +74 -0
  204. package/frontend/src/components/datagrid/cell-editors/BooleanCellEditor.tsx +54 -0
  205. package/frontend/src/components/datagrid/cell-editors/DateCellEditor.tsx +483 -0
  206. package/frontend/src/components/datagrid/cell-editors/JsonCellEditor.tsx +362 -0
  207. package/frontend/src/components/datagrid/cell-editors/TextCellEditor.tsx +38 -0
  208. package/frontend/src/components/datagrid/cell-editors/index.ts +14 -0
  209. package/frontend/src/components/datagrid/cell-editors/types.ts +43 -0
  210. package/frontend/src/components/datagrid/datagridTypes.tsx +72 -0
  211. package/frontend/src/components/datagrid/index.tsx +20 -0
  212. package/frontend/src/components/index.ts +39 -0
  213. package/frontend/src/components/layout/AppHeader.tsx +146 -0
  214. package/frontend/src/components/layout/AppSidebar.tsx +190 -0
  215. package/frontend/src/components/layout/CloudLayout.tsx +95 -0
  216. package/frontend/src/components/layout/Layout.tsx +43 -0
  217. package/frontend/src/components/radix/Alert.tsx +45 -0
  218. package/frontend/src/components/radix/AlertDialog.tsx +115 -0
  219. package/frontend/src/components/radix/Avatar.tsx +45 -0
  220. package/frontend/src/components/radix/Badge.tsx +33 -0
  221. package/frontend/src/components/radix/Button.tsx +50 -0
  222. package/frontend/src/components/radix/Card.tsx +58 -0
  223. package/frontend/src/components/radix/Dialog.tsx +98 -0
  224. package/frontend/src/components/radix/DropdownMenu.tsx +185 -0
  225. package/frontend/src/components/radix/Form.tsx +167 -0
  226. package/frontend/src/components/radix/Input.tsx +22 -0
  227. package/frontend/src/components/radix/Label.tsx +19 -0
  228. package/frontend/src/components/radix/Popover.tsx +29 -0
  229. package/frontend/src/components/radix/ScrollArea.tsx +44 -0
  230. package/frontend/src/components/radix/Select.tsx +151 -0
  231. package/frontend/src/components/radix/Separator.tsx +26 -0
  232. package/frontend/src/components/radix/Sheet.tsx +119 -0
  233. package/frontend/src/components/radix/Skeleton.tsx +7 -0
  234. package/frontend/src/components/radix/Switch.tsx +29 -0
  235. package/frontend/src/components/radix/Tabs.tsx +50 -0
  236. package/frontend/src/components/radix/Textarea.tsx +21 -0
  237. package/frontend/src/components/radix/Tooltip.tsx +28 -0
  238. package/frontend/src/features/ai/components/AIConfigCard.tsx +154 -0
  239. package/frontend/src/features/ai/components/AIConfigDialog.tsx +76 -0
  240. package/frontend/src/features/ai/components/AIConfigForm.tsx +222 -0
  241. package/frontend/src/features/ai/components/AIEmptyState.tsx +18 -0
  242. package/frontend/src/features/ai/components/fields/ModalityField.tsx +87 -0
  243. package/frontend/src/features/ai/components/fields/ModelSelectionField.tsx +134 -0
  244. package/frontend/src/features/ai/components/fields/SystemPromptField.tsx +33 -0
  245. package/frontend/src/features/ai/helpers.ts +155 -0
  246. package/frontend/src/features/ai/hooks/useAIConfigs.ts +221 -0
  247. package/frontend/src/features/ai/hooks/useAIUsage.ts +77 -0
  248. package/frontend/src/features/ai/page/AIPage.tsx +178 -0
  249. package/frontend/src/features/ai/services/ai.service.ts +148 -0
  250. package/frontend/src/features/auth/components/AddOAuthDialog.tsx +106 -0
  251. package/frontend/src/features/auth/components/AuthMethodTab.tsx +238 -0
  252. package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +303 -0
  253. package/frontend/src/features/auth/components/OAuthEmptyState.tsx +15 -0
  254. package/frontend/src/features/auth/components/UserFormDialog.tsx +248 -0
  255. package/frontend/src/features/auth/components/UsersDataGrid.tsx +183 -0
  256. package/frontend/src/features/auth/components/UsersTab.tsx +114 -0
  257. package/frontend/src/features/auth/hooks/useOAuthConfig.ts +129 -0
  258. package/frontend/src/features/auth/hooks/useUsers.ts +57 -0
  259. package/frontend/src/features/auth/index.ts +9 -0
  260. package/frontend/src/features/auth/page/AuthenticationPage.tsx +169 -0
  261. package/frontend/src/features/auth/services/auth.service.ts +112 -0
  262. package/frontend/src/features/auth/services/oauth.service.ts +49 -0
  263. package/frontend/src/features/dashboard/page/DashboardPage.tsx +194 -0
  264. package/frontend/src/features/database/components/ColumnTypeSelect.tsx +64 -0
  265. package/frontend/src/features/database/components/DatabaseDataGrid.tsx +282 -0
  266. package/frontend/src/features/database/components/ForeignKeyCell.tsx +187 -0
  267. package/frontend/src/features/database/components/ForeignKeyPopover.tsx +378 -0
  268. package/frontend/src/features/database/components/LinkRecordModal.tsx +288 -0
  269. package/frontend/src/features/database/components/RecordFormDialog.tsx +164 -0
  270. package/frontend/src/features/database/components/RecordFormField.tsx +568 -0
  271. package/frontend/src/features/database/components/TableEmptyState.tsx +21 -0
  272. package/frontend/src/features/database/components/TableForm.tsx +656 -0
  273. package/frontend/src/features/database/components/TableFormColumn.tsx +137 -0
  274. package/frontend/src/features/database/components/TableListSkeleton.tsx +9 -0
  275. package/frontend/src/features/database/components/TableSidebar.tsx +47 -0
  276. package/frontend/src/features/database/constants.ts +26 -0
  277. package/frontend/src/features/database/helpers.ts +125 -0
  278. package/frontend/src/features/database/hooks/UseLinkModal.tsx +78 -0
  279. package/frontend/src/features/database/index.ts +12 -0
  280. package/frontend/src/features/database/page/DatabasePage.tsx +626 -0
  281. package/frontend/src/features/database/schema.ts +25 -0
  282. package/frontend/src/features/database/services/database.service.ts +216 -0
  283. package/frontend/src/features/functions/components/FunctionEmptyState.tsx +15 -0
  284. package/frontend/src/features/functions/components/FunctionRow.tsx +71 -0
  285. package/frontend/src/features/functions/components/FunctionViewer.tsx +46 -0
  286. package/frontend/src/features/functions/components/FunctionsContent.tsx +88 -0
  287. package/frontend/src/features/functions/components/FunctionsSidebar.tsx +56 -0
  288. package/frontend/src/features/functions/components/SecretEmptyState.tsx +23 -0
  289. package/frontend/src/features/functions/components/SecretRow.tsx +68 -0
  290. package/frontend/src/features/functions/components/SecretsContent.tsx +120 -0
  291. package/frontend/src/features/functions/hooks/useFunctions.ts +106 -0
  292. package/frontend/src/features/functions/page/FunctionsPage.tsx +28 -0
  293. package/frontend/src/features/functions/services/functions.service.ts +48 -0
  294. package/frontend/src/features/login/components/AuthErrorBoundary.tsx +87 -0
  295. package/frontend/src/features/login/components/PrivateRoute.tsx +24 -0
  296. package/frontend/src/features/login/page/CloudLoginPage.tsx +93 -0
  297. package/frontend/src/features/login/page/LoginPage.tsx +174 -0
  298. package/frontend/src/features/logs/components/AnalyticsLogsTable.tsx +313 -0
  299. package/frontend/src/features/logs/components/LogsTable.tsx +199 -0
  300. package/frontend/src/features/logs/hooks/useAuditLogs.ts +39 -0
  301. package/frontend/src/features/logs/index.ts +5 -0
  302. package/frontend/src/features/logs/page/AnalyticsLogsPage.tsx +530 -0
  303. package/frontend/src/features/logs/page/AuditsPage.tsx +192 -0
  304. package/frontend/src/features/logs/services/log.service.ts +171 -0
  305. package/frontend/src/features/metadata/hooks/useMetadata.ts +53 -0
  306. package/frontend/src/features/metadata/index.ts +0 -0
  307. package/frontend/src/features/metadata/page/MetadataPage.tsx +136 -0
  308. package/frontend/src/features/metadata/services/metadata.service.ts +17 -0
  309. package/frontend/src/features/onboard/components/CompletionCard.tsx +41 -0
  310. package/frontend/src/features/onboard/components/OnboardButton.tsx +84 -0
  311. package/frontend/src/features/onboard/components/StepContent.tsx +91 -0
  312. package/frontend/src/features/onboard/components/TestConnectionStep.tsx +53 -0
  313. package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +35 -0
  314. package/frontend/src/features/onboard/components/mcp/McpInstallation.tsx +144 -0
  315. package/frontend/src/features/onboard/components/mcp/index.ts +4 -0
  316. package/frontend/src/features/onboard/components/mcp/mcp-helper.tsx +98 -0
  317. package/frontend/src/features/onboard/index.ts +3 -0
  318. package/frontend/src/features/onboard/page/OnBoardPage.tsx +104 -0
  319. package/frontend/src/features/onboard/types.ts +8 -0
  320. package/frontend/src/features/secrets/hooks/useSecrets.ts +139 -0
  321. package/frontend/src/features/secrets/services/secrets.service.ts +57 -0
  322. package/frontend/src/features/storage/components/BucketEmptyState.tsx +19 -0
  323. package/frontend/src/features/storage/components/BucketFormDialog.tsx +194 -0
  324. package/frontend/src/features/storage/components/BucketListSkeleton.tsx +17 -0
  325. package/frontend/src/features/storage/components/FilePreviewDialog.tsx +287 -0
  326. package/frontend/src/features/storage/components/StorageDataGrid.tsx +239 -0
  327. package/frontend/src/features/storage/components/StorageManager.tsx +236 -0
  328. package/frontend/src/features/storage/components/StorageSidebar.tsx +44 -0
  329. package/frontend/src/features/storage/components/UploadToast.tsx +46 -0
  330. package/frontend/src/features/storage/index.ts +3 -0
  331. package/frontend/src/features/storage/page/StoragePage.tsx +553 -0
  332. package/frontend/src/features/storage/services/storage.service.ts +144 -0
  333. package/frontend/src/features/visualizer/components/AuthNode.tsx +107 -0
  334. package/frontend/src/features/visualizer/components/BucketNode.tsx +34 -0
  335. package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +359 -0
  336. package/frontend/src/features/visualizer/components/TableNode.tsx +152 -0
  337. package/frontend/src/features/visualizer/components/VisualizerSkeleton.tsx +24 -0
  338. package/frontend/src/features/visualizer/components/index.ts +5 -0
  339. package/frontend/src/features/visualizer/page/VisualizerPage.tsx +127 -0
  340. package/frontend/src/index.css +248 -0
  341. package/frontend/src/lib/api/client.ts +163 -0
  342. package/frontend/src/lib/contexts/AuthContext.tsx +157 -0
  343. package/frontend/src/lib/contexts/OnboardStepContext.tsx +68 -0
  344. package/frontend/src/lib/contexts/SocketContext.tsx +303 -0
  345. package/frontend/src/lib/contexts/ThemeContext.tsx +125 -0
  346. package/frontend/src/lib/hooks/useAuth.ts +4 -0
  347. package/frontend/src/lib/hooks/useConfirm.ts +55 -0
  348. package/frontend/src/lib/hooks/useInterval.ts +27 -0
  349. package/frontend/src/lib/hooks/useMediaQuery.ts +59 -0
  350. package/frontend/src/lib/hooks/useOnboardingCompletion.ts +29 -0
  351. package/frontend/src/lib/hooks/usePagination.ts +27 -0
  352. package/frontend/src/lib/hooks/useTimeout.ts +27 -0
  353. package/frontend/src/lib/hooks/useToast.tsx +229 -0
  354. package/frontend/src/lib/utils/constants.ts +38 -0
  355. package/frontend/src/lib/utils/utils.ts +165 -0
  356. package/frontend/src/lib/utils/validation-schemas.ts +126 -0
  357. package/frontend/src/main.tsx +16 -0
  358. package/frontend/src/rdg.css +194 -0
  359. package/frontend/src/vite-env.d.ts +12 -0
  360. package/frontend/tailwind.config.js +97 -0
  361. package/frontend/tsconfig.json +26 -0
  362. package/frontend/tsconfig.node.json +10 -0
  363. package/frontend/vite.config.ts +37 -0
  364. package/frontend/vitest.config.ts +36 -0
  365. package/functions/deno.json +25 -0
  366. package/functions/server.ts +290 -0
  367. package/functions/worker-template.js +126 -0
  368. package/openapi/ai.yaml +689 -0
  369. package/openapi/auth.yaml +563 -0
  370. package/openapi/functions.yaml +476 -0
  371. package/openapi/health.yaml +30 -0
  372. package/openapi/logs.yaml +224 -0
  373. package/openapi/metadata.yaml +178 -0
  374. package/openapi/records.yaml +382 -0
  375. package/openapi/secrets.yaml +371 -0
  376. package/openapi/storage.yaml +876 -0
  377. package/openapi/tables.yaml +464 -0
  378. package/package.json +88 -0
  379. package/shared-schemas/package.json +31 -0
  380. package/shared-schemas/src/ai-api.schema.ts +167 -0
  381. package/shared-schemas/src/ai.schema.ts +54 -0
  382. package/shared-schemas/src/auth-api.schema.ts +193 -0
  383. package/shared-schemas/src/auth.schema.ts +94 -0
  384. package/shared-schemas/src/database-api.schema.ts +259 -0
  385. package/shared-schemas/src/database.schema.ts +69 -0
  386. package/shared-schemas/src/functions-api.schema.ts +25 -0
  387. package/shared-schemas/src/functions.schema.ts +16 -0
  388. package/shared-schemas/src/index.ts +13 -0
  389. package/shared-schemas/src/logs-api.schema.ts +49 -0
  390. package/shared-schemas/src/logs.schema.ts +14 -0
  391. package/shared-schemas/src/metadata.schema.ts +56 -0
  392. package/shared-schemas/src/storage-api.schema.ts +65 -0
  393. package/shared-schemas/src/storage.schema.ts +19 -0
  394. package/shared-schemas/tsconfig.json +21 -0
  395. package/tsconfig.json +8 -0
@@ -0,0 +1,120 @@
1
+ import { useState } from 'react';
2
+ import { Button } from '@/components/radix/Button';
3
+ import { Input } from '@/components/radix/Input';
4
+ import { Skeleton } from '@/components/radix/Skeleton';
5
+ import { SearchInput } from '@/components/SearchInput';
6
+ import { ConfirmDialog } from '@/components/ConfirmDialog';
7
+ import { SecretRow } from './SecretRow';
8
+ import SecretEmptyState from './SecretEmptyState';
9
+ import { useSecrets } from '@/features/secrets/hooks/useSecrets';
10
+
11
+ export function SecretsContent() {
12
+ const [newSecretKey, setNewSecretKey] = useState('');
13
+ const [newSecretValue, setNewSecretValue] = useState('');
14
+
15
+ const {
16
+ filteredSecrets,
17
+ searchQuery,
18
+ setSearchQuery,
19
+ isLoading: loading,
20
+ createSecret,
21
+ deleteSecret,
22
+ confirmDialogProps,
23
+ } = useSecrets();
24
+
25
+ const handleSaveNewSecret = async () => {
26
+ const success = await createSecret(newSecretKey, newSecretValue);
27
+ if (success) {
28
+ setNewSecretKey('');
29
+ setNewSecretValue('');
30
+ }
31
+ };
32
+
33
+ return (
34
+ <>
35
+ <div className="flex flex-col gap-6 p-4">
36
+ {/* Header */}
37
+ <p className="h-7 text-xl text-zinc-950 dark:text-white">Secrets</p>
38
+
39
+ {/* Add New Secret Portal */}
40
+ <div className="bg-white dark:bg-[#333333] rounded-[8px]">
41
+ <div className="p-6 border-b border-gray-200 dark:border-neutral-700">
42
+ <p className="text-base text-zinc-950 dark:text-white">Add New Secret</p>
43
+ </div>
44
+ <div className="p-6 flex gap-6 items-end">
45
+ <div className="flex-1">
46
+ <label className="block text-sm text-zinc-950 dark:text-neutral-50 mb-2">Key</label>
47
+ <Input
48
+ placeholder="e.g CLIENT_KEY"
49
+ value={newSecretKey}
50
+ onChange={(e) => setNewSecretKey(e.target.value)}
51
+ className="shadow-none w-full dark:bg-neutral-900 dark:text-white dark:placeholder:text-neutral-400 dark:border-neutral-700"
52
+ />
53
+ </div>
54
+ <div className="flex-1">
55
+ <label className="block text-sm text-zinc-950 dark:text-neutral-50 mb-2">Value</label>
56
+ <Input
57
+ placeholder="e.g 1234567890"
58
+ type="text"
59
+ value={newSecretValue}
60
+ onChange={(e) => setNewSecretValue(e.target.value)}
61
+ className="shadow-none w-full dark:bg-neutral-900 dark:text-white dark:placeholder:text-neutral-400 dark:border-neutral-700"
62
+ />
63
+ </div>
64
+ <Button
65
+ onClick={() => void handleSaveNewSecret()}
66
+ className="bg-emerald-300 hover:bg-emerald-400 text-black px-3 py-2 w-20 h-9 rounded"
67
+ disabled={!newSecretKey.trim() || !newSecretValue.trim()}
68
+ >
69
+ Save
70
+ </Button>
71
+ </div>
72
+ </div>
73
+
74
+ {/* Search Bar */}
75
+ <SearchInput
76
+ placeholder="Search secret"
77
+ value={searchQuery}
78
+ onChange={setSearchQuery}
79
+ className="max-w-70 dark:bg-neutral-900 dark:border-neutral-700"
80
+ />
81
+
82
+ {/* Secrets Table */}
83
+ <div className="flex flex-col gap-2">
84
+ {/* Table Header */}
85
+ <div className="grid grid-cols-12 px-3 text-sm text-muted-foreground dark:text-neutral-400">
86
+ <div className="col-span-8 py-1 px-3">Name</div>
87
+ {/* <div className="col-span-5 py-1 px-3">Digest</div> */}
88
+ <div className="col-span-3 py-1 px-3">Updated at</div>
89
+ <div className="col-span-1 py-1 px-3" />
90
+ </div>
91
+
92
+ {loading ? (
93
+ <>
94
+ {[...Array(4)].map((_, i) => (
95
+ <Skeleton key={i} className="h-14 rounded-[8px] cols-span-full" />
96
+ ))}
97
+ </>
98
+ ) : filteredSecrets.length >= 1 ? (
99
+ <>
100
+ {filteredSecrets.map((secret) => (
101
+ <SecretRow
102
+ key={secret.id}
103
+ secret={secret}
104
+ onDelete={() => void deleteSecret(secret)}
105
+ className="cols-span-full"
106
+ />
107
+ ))}
108
+ </>
109
+ ) : (
110
+ <div className="cols-span-full">
111
+ <SecretEmptyState searchQuery={searchQuery} />
112
+ </div>
113
+ )}
114
+ </div>
115
+ </div>
116
+
117
+ <ConfirmDialog {...confirmDialogProps} />
118
+ </>
119
+ );
120
+ }
@@ -0,0 +1,106 @@
1
+ import { useState, useCallback, useMemo } from 'react';
2
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
3
+ import { functionsService, type EdgeFunction } from '../services/functions.service';
4
+ import { useToast } from '@/lib/hooks/useToast';
5
+
6
+ export function useFunctions() {
7
+ const queryClient = useQueryClient();
8
+ const { showToast } = useToast();
9
+ const [selectedFunction, setSelectedFunction] = useState<EdgeFunction | null>(null);
10
+
11
+ // Query to fetch all functions
12
+ const {
13
+ data: functionsData,
14
+ isLoading,
15
+ error,
16
+ refetch,
17
+ } = useQuery({
18
+ queryKey: ['functions'],
19
+ queryFn: () => functionsService.listFunctions(),
20
+ staleTime: 2 * 60 * 1000, // Cache for 2 minutes
21
+ });
22
+
23
+ // Extract functions and runtime status from response
24
+ const functions = useMemo(() => functionsData?.functions || [], [functionsData]);
25
+ const runtimeStatus = useMemo(() => functionsData?.runtime?.status || 'running', [functionsData]);
26
+
27
+ // Function to fetch and set selected function details
28
+ const selectFunction = useCallback(
29
+ async (func: EdgeFunction) => {
30
+ try {
31
+ const data = await functionsService.getFunctionBySlug(func.slug);
32
+ setSelectedFunction(data);
33
+ } catch (error) {
34
+ console.error('Failed to fetch function details:', error);
35
+ const errorMessage =
36
+ error instanceof Error ? error.message : 'Failed to load function details';
37
+ showToast(errorMessage, 'error');
38
+ }
39
+ },
40
+ [showToast]
41
+ );
42
+
43
+ // Function to clear selected function (back to list)
44
+ const clearSelection = useCallback(() => {
45
+ setSelectedFunction(null);
46
+ }, []);
47
+
48
+ // Delete function mutation (for future use)
49
+ const deleteFunctionMutation = useMutation({
50
+ mutationFn: (slug: string) => functionsService.deleteFunction(slug),
51
+ onSuccess: (_, slug) => {
52
+ void queryClient.invalidateQueries({ queryKey: ['functions'] });
53
+ showToast('Function deleted successfully', 'success');
54
+ // Clear selection if deleted function was selected
55
+ if (selectedFunction && selectedFunction.slug === slug) {
56
+ setSelectedFunction(null);
57
+ }
58
+ },
59
+ onError: (error: Error) => {
60
+ const errorMessage = error instanceof Error ? error.message : 'Failed to delete function';
61
+ showToast(errorMessage, 'error');
62
+ },
63
+ });
64
+
65
+ // Helper to check if a function is selected
66
+ const isViewingDetail = selectedFunction !== null;
67
+
68
+ // Only show functions if runtime is available
69
+ const displayFunctions = useMemo(
70
+ () => (runtimeStatus === 'running' ? functions : []),
71
+ [functions, runtimeStatus]
72
+ );
73
+
74
+ return {
75
+ // Data
76
+ functions: displayFunctions,
77
+ functionsCount: displayFunctions.length,
78
+ selectedFunction,
79
+ isViewingDetail,
80
+
81
+ // Runtime status
82
+ runtimeStatus,
83
+ isRuntimeAvailable: runtimeStatus === 'running',
84
+
85
+ // Loading states
86
+ isLoading,
87
+ isDeleting: deleteFunctionMutation.isPending,
88
+
89
+ // Error
90
+ error,
91
+
92
+ // Actions
93
+ selectFunction,
94
+ clearSelection,
95
+ deleteFunction: deleteFunctionMutation.mutate,
96
+ refetch,
97
+
98
+ // Helpers
99
+ getFunctionBySlug: useCallback(
100
+ (slug: string): EdgeFunction | undefined => {
101
+ return displayFunctions.find((func) => func.slug === slug);
102
+ },
103
+ [displayFunctions]
104
+ ),
105
+ };
106
+ }
@@ -0,0 +1,28 @@
1
+ import { useState, useEffect } from 'react';
2
+ import { FunctionsSidebar } from '@/features/functions/components/FunctionsSidebar';
3
+ import { FunctionsContent } from '@/features/functions/components/FunctionsContent';
4
+ import { SecretsContent } from '@/features/functions/components/SecretsContent';
5
+
6
+ export default function FunctionsPage() {
7
+ // Load selected section from localStorage on mount
8
+ const [selectedSection, setSelectedSection] = useState<'functions' | 'secrets'>(() => {
9
+ return (
10
+ (localStorage.getItem('selectedFunctionSection') as 'functions' | 'secrets') || 'functions'
11
+ );
12
+ });
13
+
14
+ // Save selected section to localStorage when it changes
15
+ useEffect(() => {
16
+ localStorage.setItem('selectedFunctionSection', selectedSection);
17
+ }, [selectedSection]);
18
+
19
+ return (
20
+ <div className="h-full flex">
21
+ <FunctionsSidebar selectedSection={selectedSection} onSectionSelect={setSelectedSection} />
22
+
23
+ <div className="flex-1 flex flex-col overflow-hidden">
24
+ {selectedSection === 'functions' ? <FunctionsContent /> : <SecretsContent />}
25
+ </div>
26
+ </div>
27
+ );
28
+ }
@@ -0,0 +1,48 @@
1
+ import { apiClient } from '@/lib/api/client';
2
+
3
+ export interface EdgeFunction {
4
+ id: string;
5
+ slug: string;
6
+ name: string;
7
+ description?: string;
8
+ code?: string;
9
+ status: 'draft' | 'active' | 'error';
10
+ created_at: string;
11
+ updated_at: string;
12
+ deployed_at?: string;
13
+ }
14
+
15
+ export interface FunctionsResponse {
16
+ functions: EdgeFunction[];
17
+ runtime: {
18
+ status: 'running' | 'unavailable';
19
+ };
20
+ }
21
+
22
+ export class FunctionsService {
23
+ async listFunctions(): Promise<FunctionsResponse> {
24
+ const data = await apiClient.request('/functions', {
25
+ headers: apiClient.withAccessToken(),
26
+ });
27
+
28
+ return {
29
+ functions: Array.isArray(data.functions) ? data.functions : [],
30
+ runtime: data.runtime || { status: 'unavailable' },
31
+ };
32
+ }
33
+
34
+ async getFunctionBySlug(slug: string): Promise<EdgeFunction> {
35
+ return apiClient.request(`/functions/${slug}`, {
36
+ headers: apiClient.withAccessToken(),
37
+ });
38
+ }
39
+
40
+ async deleteFunction(slug: string): Promise<void> {
41
+ return apiClient.request(`/functions/${slug}`, {
42
+ method: 'DELETE',
43
+ headers: apiClient.withAccessToken(),
44
+ });
45
+ }
46
+ }
47
+
48
+ export const functionsService = new FunctionsService();
@@ -0,0 +1,87 @@
1
+ import { Component, ReactNode } from 'react';
2
+ import { AlertCircle } from 'lucide-react';
3
+ import { Button } from '@/components/radix/Button';
4
+ import { Alert, AlertDescription, AlertTitle } from '@/components/radix/Alert';
5
+
6
+ interface Props {
7
+ children: ReactNode;
8
+ fallback?: ReactNode;
9
+ }
10
+
11
+ interface State {
12
+ hasError: boolean;
13
+ error: Error | null;
14
+ }
15
+
16
+ export class AuthErrorBoundary extends Component<Props, State> {
17
+ public state: State = {
18
+ hasError: false,
19
+ error: null,
20
+ };
21
+
22
+ public static getDerivedStateFromError(error: Error): State {
23
+ return { hasError: true, error };
24
+ }
25
+
26
+ public componentDidCatch(error: Error) {
27
+ console.error(error);
28
+ }
29
+
30
+ private handleReset = () => {
31
+ this.setState({ hasError: false, error: null });
32
+ // Clear auth token and reload to trigger re-authentication
33
+ localStorage.removeItem('insforge_token');
34
+ window.location.href = '/';
35
+ };
36
+
37
+ public render() {
38
+ if (this.state.hasError) {
39
+ if (this.props.fallback) {
40
+ return this.props.fallback;
41
+ }
42
+
43
+ const isAuthError =
44
+ this.state.error?.message?.includes('auth') ||
45
+ this.state.error?.message?.includes('401') ||
46
+ this.state.error?.message?.includes('403');
47
+
48
+ return (
49
+ <div className="min-h-screen bg-gray-50 flex items-center justify-center p-4">
50
+ <div className="max-w-md w-full space-y-4">
51
+ <Alert variant="destructive">
52
+ <AlertCircle className="h-4 w-4" />
53
+ <AlertTitle>
54
+ {isAuthError ? 'Authentication Error' : 'Something went wrong'}
55
+ </AlertTitle>
56
+ <AlertDescription>
57
+ {isAuthError
58
+ ? 'There was a problem with your authentication. Please try logging in again.'
59
+ : 'An unexpected error occurred. Please try refreshing the page.'}
60
+ </AlertDescription>
61
+ </Alert>
62
+
63
+ <div className="flex gap-3">
64
+ <Button onClick={this.handleReset} className="flex-1">
65
+ {isAuthError ? 'Login Again' : 'Refresh Page'}
66
+ </Button>
67
+ <Button variant="outline" onClick={() => window.location.reload()} className="flex-1">
68
+ Reload Page
69
+ </Button>
70
+ </div>
71
+
72
+ {process.env.NODE_ENV === 'development' && (
73
+ <details className="mt-4 p-4 bg-gray-100 rounded-md">
74
+ <summary className="cursor-pointer text-sm font-medium">
75
+ Error Details (Development Only)
76
+ </summary>
77
+ <pre className="mt-2 text-xs overflow-auto">{this.state.error?.stack}</pre>
78
+ </details>
79
+ )}
80
+ </div>
81
+ </div>
82
+ );
83
+ }
84
+
85
+ return this.props.children;
86
+ }
87
+ }
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+ import { Navigate } from 'react-router-dom';
3
+ import { useAuth } from '@/lib/contexts/AuthContext';
4
+ import { LoadingState } from '@/components/LoadingState';
5
+ import { cn } from '@/lib/utils/utils';
6
+
7
+ interface PrivateRouteProps {
8
+ classname: string;
9
+ children: React.ReactNode;
10
+ }
11
+
12
+ export const PrivateRoute: React.FC<PrivateRouteProps> = ({ classname, children }) => {
13
+ const { isAuthenticated, isLoading } = useAuth();
14
+
15
+ if (isLoading) {
16
+ return (
17
+ <div className={cn('min-h-screen flex items-center justify-center', classname)}>
18
+ <LoadingState />
19
+ </div>
20
+ );
21
+ }
22
+
23
+ return isAuthenticated ? <>{children}</> : <Navigate to="/dashboard/login" replace />;
24
+ };
@@ -0,0 +1,93 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { useNavigate, useSearchParams } from 'react-router-dom';
3
+ import { LockIcon } from 'lucide-react';
4
+ import { useAuth } from '@/lib/contexts/AuthContext';
5
+
6
+ export default function CloudLoginPage() {
7
+ const navigate = useNavigate();
8
+ const [searchParams, setSearchParams] = useSearchParams();
9
+ const { loginWithAuthorizationCode, isAuthenticated } = useAuth();
10
+ const [authError, setAuthError] = useState<string | null>(null);
11
+
12
+ useEffect(() => {
13
+ if (isAuthenticated) {
14
+ void navigate('/cloud/dashboard', { replace: true });
15
+ }
16
+ }, [isAuthenticated, navigate]);
17
+
18
+ // Handle authorization token exchange
19
+ useEffect(() => {
20
+ const authorizationCode = searchParams.get('authorizationCode');
21
+
22
+ if (authorizationCode) {
23
+ setAuthError(null);
24
+ // Exchange the authorization code for an access token
25
+ loginWithAuthorizationCode(authorizationCode)
26
+ .then((success) => {
27
+ if (success) {
28
+ // Notify parent of success
29
+ if (window.parent !== window) {
30
+ window.parent.postMessage(
31
+ {
32
+ type: 'AUTH_SUCCESS',
33
+ },
34
+ '*'
35
+ );
36
+ }
37
+ } else {
38
+ setAuthError('The authorization code may have expired or already been used.');
39
+ if (window.parent !== window) {
40
+ window.parent.postMessage(
41
+ {
42
+ type: 'AUTH_ERROR',
43
+ message: 'Authorization code validation failed',
44
+ },
45
+ '*'
46
+ );
47
+ }
48
+ }
49
+ })
50
+ .catch((error) => {
51
+ console.error('Authorization code exchange failed:', error);
52
+ setAuthError('The authorization code may have expired or already been used.');
53
+ if (window.parent !== window) {
54
+ window.parent.postMessage(
55
+ {
56
+ type: 'AUTH_ERROR',
57
+ message: 'Authorization code validation failed',
58
+ },
59
+ '*'
60
+ );
61
+ }
62
+ });
63
+ } else {
64
+ setAuthError('No authorization code provided.');
65
+ }
66
+ }, [searchParams, setSearchParams, loginWithAuthorizationCode, navigate]);
67
+
68
+ // Show error state if authentication failed
69
+ if (authError) {
70
+ return (
71
+ <div className="min-h-screen bg-neutral-800 flex items-center justify-center px-4">
72
+ <div className="text-center text-white">
73
+ <LockIcon className="h-12 w-12 mx-auto mb-4 text-red-400" />
74
+ <h2 className="text-xl font-semibold mb-2">Authentication Failed</h2>
75
+ <p className="text-gray-400 text-sm max-w-md">{authError}</p>
76
+ </div>
77
+ </div>
78
+ );
79
+ }
80
+
81
+ // Show authenticating state
82
+ return (
83
+ <div className="min-h-screen bg-neutral-800 flex items-center justify-center px-4">
84
+ <div className="text-center">
85
+ <div className="animate-spin mb-4">
86
+ <LockIcon className="h-12 w-12 text-white mx-auto" />
87
+ </div>
88
+ <h2 className="text-xl font-semibold text-white mb-2">Authenticating...</h2>
89
+ <p className="text-sm text-gray-400">Please wait while we verify your identity</p>
90
+ </div>
91
+ </div>
92
+ );
93
+ }
@@ -0,0 +1,174 @@
1
+ import { useEffect, useCallback, useState } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { useForm } from 'react-hook-form';
4
+ import { zodResolver } from '@hookform/resolvers/zod';
5
+ import { Lock, Mail } from 'lucide-react';
6
+ import {
7
+ Card,
8
+ CardContent,
9
+ CardDescription,
10
+ CardFooter,
11
+ CardHeader,
12
+ CardTitle,
13
+ } from '@/components/radix/Card';
14
+ import {
15
+ Form,
16
+ FormControl,
17
+ FormField,
18
+ FormItem,
19
+ FormLabel,
20
+ FormMessage,
21
+ } from '@/components/radix/Form';
22
+ import { Input } from '@/components/radix/Input';
23
+ import { ButtonWithLoading } from '@/components/ButtonWithLoading';
24
+ import { Alert, AlertDescription } from '@/components/radix/Alert';
25
+ import { useAuth } from '@/lib/contexts/AuthContext';
26
+ import { useOnboardingCompletion } from '@/lib/hooks/useOnboardingCompletion';
27
+ import { loginFormSchema, LoginFormData } from '@/lib/utils/validation-schemas';
28
+
29
+ export default function LoginPage() {
30
+ const navigate = useNavigate();
31
+ const { loginWithPassword, isAuthenticated } = useAuth();
32
+ const { isCompleted } = useOnboardingCompletion();
33
+ const [isSubmitting, setIsSubmitting] = useState(false);
34
+ const [submitError, setSubmitError] = useState<string | null>(null);
35
+
36
+ // Determine where to redirect based on onboarding completion status
37
+ const getRedirectPath = useCallback(() => {
38
+ return isCompleted ? '/dashboard' : '/dashboard/onboard';
39
+ }, [isCompleted]);
40
+
41
+ const form = useForm<LoginFormData>({
42
+ resolver: zodResolver(loginFormSchema),
43
+ defaultValues: {
44
+ email: 'admin@example.com',
45
+ password: 'change-this-password',
46
+ },
47
+ });
48
+
49
+ const onSubmit = async (data: LoginFormData) => {
50
+ setIsSubmitting(true);
51
+ setSubmitError(null);
52
+
53
+ try {
54
+ const success = await loginWithPassword(data.email, data.password);
55
+
56
+ if (success) {
57
+ void navigate(getRedirectPath(), { replace: true });
58
+ } else {
59
+ throw new Error('Invalid email or password');
60
+ }
61
+ } catch (error) {
62
+ const errorMessage = error instanceof Error ? error.message : 'An error occurred';
63
+ setSubmitError(errorMessage);
64
+ } finally {
65
+ setIsSubmitting(false);
66
+ }
67
+ };
68
+
69
+ useEffect(() => {
70
+ if (isAuthenticated) {
71
+ void navigate(getRedirectPath(), { replace: true });
72
+ }
73
+ }, [isAuthenticated, navigate, getRedirectPath]);
74
+
75
+ return (
76
+ <div className="min-h-screen bg-gray-50 dark:bg-neutral-900 flex items-center justify-center px-4 sm:px-6 lg:px-8">
77
+ <div className="w-full max-w-md">
78
+ {/* Logo and Title */}
79
+ <div className="text-center mb-8">
80
+ <div className="inline-flex items-center justify-center w-16 h-16 bg-black dark:bg-emerald-300 rounded-lg mb-4">
81
+ <Lock className="h-8 w-8 text-white dark:text-black" />
82
+ </div>
83
+ <h1 className="text-2xl font-bold tracking-tight dark:text-white">Insforge Admin</h1>
84
+ <p className="text-sm text-muted-foreground mt-2">Sign in to access your dashboard</p>
85
+ </div>
86
+
87
+ {/* Login Card */}
88
+ <Card>
89
+ <Form {...form}>
90
+ <form onSubmit={(e) => void form.handleSubmit(onSubmit)(e)}>
91
+ <CardHeader>
92
+ <CardTitle>Sign In</CardTitle>
93
+ <CardDescription>Enter your admin credentials to continue</CardDescription>
94
+ </CardHeader>
95
+ <CardContent className="space-y-4">
96
+ <FormField
97
+ control={form.control}
98
+ name="email"
99
+ render={({ field }) => (
100
+ <FormItem>
101
+ <FormLabel>Email</FormLabel>
102
+ <FormControl>
103
+ <div className="relative">
104
+ <Mail className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
105
+ <Input
106
+ {...field}
107
+ type="email"
108
+ placeholder="admin@example.com"
109
+ className="pl-10"
110
+ autoComplete="email"
111
+ />
112
+ </div>
113
+ </FormControl>
114
+ <FormMessage />
115
+ </FormItem>
116
+ )}
117
+ />
118
+
119
+ <FormField
120
+ control={form.control}
121
+ name="password"
122
+ render={({ field }) => (
123
+ <FormItem>
124
+ <FormLabel>Password</FormLabel>
125
+ <FormControl>
126
+ <div className="relative">
127
+ <Lock className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
128
+ <Input
129
+ {...field}
130
+ type="password"
131
+ placeholder="Enter your password"
132
+ className="pl-10"
133
+ autoComplete="current-password"
134
+ />
135
+ </div>
136
+ </FormControl>
137
+ <FormMessage />
138
+ </FormItem>
139
+ )}
140
+ />
141
+
142
+ {submitError && (
143
+ <Alert variant="destructive">
144
+ <AlertDescription>{submitError}</AlertDescription>
145
+ </Alert>
146
+ )}
147
+ </CardContent>
148
+ <CardFooter className="flex flex-col space-y-4">
149
+ <ButtonWithLoading
150
+ type="submit"
151
+ className="w-full"
152
+ loading={isSubmitting}
153
+ disabled={isSubmitting}
154
+ >
155
+ Sign in
156
+ </ButtonWithLoading>
157
+ <p className="text-xs text-center text-muted-foreground">
158
+ Use the credentials configured in your .env file
159
+ </p>
160
+ </CardFooter>
161
+ </form>
162
+ </Form>
163
+ </Card>
164
+
165
+ {/* Footer */}
166
+ <div className="mt-8 text-center">
167
+ <p className="text-xs text-muted-foreground">
168
+ Insforge - Self-hosted Backend as a Service
169
+ </p>
170
+ </div>
171
+ </div>
172
+ </div>
173
+ );
174
+ }