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,68 @@
1
+ import React, { createContext, useContext, useState, useEffect } from 'react';
2
+ import { OnboardStep, TOTAL_STEPS } from '@/features/onboard/types';
3
+
4
+ interface OnboardContextValue {
5
+ currentStep: OnboardStep;
6
+ updateStep: (step: OnboardStep) => void;
7
+ getCurrentDescription: () => string;
8
+ totalSteps: number;
9
+ }
10
+
11
+ const ONBOARD_STORAGE_KEY = 'insforge_onboard_step';
12
+ export const STEP_DESCRIPTIONS = [
13
+ 'Install Node.js',
14
+ 'Install InsForge',
15
+ 'Test the Connection',
16
+ 'Start Using InsForge',
17
+ ] as const;
18
+
19
+ const OnboardContext = createContext<OnboardContextValue | null>(null);
20
+
21
+ export function OnboardStepProvider({ children }: { children: React.ReactNode }) {
22
+ const [currentStep, setCurrentStep] = useState<OnboardStep>(() => {
23
+ const saved = localStorage.getItem(ONBOARD_STORAGE_KEY);
24
+ const n = saved ? parseInt(saved, 10) : NaN;
25
+ return n >= OnboardStep.INSTALL_NODEJS && n <= OnboardStep.FINAL_SETUP
26
+ ? (n as OnboardStep)
27
+ : OnboardStep.INSTALL_NODEJS;
28
+ });
29
+
30
+ useEffect(() => {
31
+ const onStorage = (e: StorageEvent) => {
32
+ if (e.key === ONBOARD_STORAGE_KEY && e.newValue) {
33
+ const n = parseInt(e.newValue, 10) as OnboardStep;
34
+ if (n >= OnboardStep.INSTALL_NODEJS && n <= OnboardStep.FINAL_SETUP) {
35
+ setCurrentStep(n);
36
+ }
37
+ }
38
+ };
39
+ window.addEventListener('storage', onStorage);
40
+ return () => window.removeEventListener('storage', onStorage);
41
+ }, []);
42
+
43
+ const updateStep = (step: OnboardStep) => {
44
+ if (step < OnboardStep.INSTALL_NODEJS || step > OnboardStep.FINAL_SETUP) {
45
+ return;
46
+ }
47
+ setCurrentStep(step);
48
+ localStorage.setItem(ONBOARD_STORAGE_KEY, step.toString());
49
+ };
50
+
51
+ const getCurrentDescription = () => STEP_DESCRIPTIONS[currentStep - 1];
52
+
53
+ return (
54
+ <OnboardContext.Provider
55
+ value={{ currentStep, updateStep, getCurrentDescription, totalSteps: TOTAL_STEPS }}
56
+ >
57
+ {children}
58
+ </OnboardContext.Provider>
59
+ );
60
+ }
61
+
62
+ export function useOnboardStep() {
63
+ const ctx = useContext(OnboardContext);
64
+ if (!ctx) {
65
+ throw new Error('useOnboardStep must be used within an OnboardProvider');
66
+ }
67
+ return ctx;
68
+ }
@@ -0,0 +1,303 @@
1
+ import {
2
+ createContext,
3
+ useContext,
4
+ useEffect,
5
+ useRef,
6
+ useState,
7
+ useCallback,
8
+ ReactNode,
9
+ useMemo,
10
+ } from 'react';
11
+ import { io, Socket } from 'socket.io-client';
12
+ import { apiClient } from '@/lib/api/client';
13
+ import { useAuth } from '@/lib/contexts/AuthContext';
14
+
15
+ // ============================================================================
16
+ // Types & Enums
17
+ // ============================================================================
18
+
19
+ /**
20
+ * Server-to-client event types
21
+ */
22
+ export enum ServerEvents {
23
+ NOTIFICATION = 'notification',
24
+ DATA_UPDATE = 'data:update',
25
+ MCP_CONNECTED = 'mcp:connected',
26
+ }
27
+
28
+ /**
29
+ * Client-to-server event types
30
+ */
31
+ export enum ClientEvents {
32
+ SUBSCRIBE = 'subscribe',
33
+ UNSUBSCRIBE = 'unsubscribe',
34
+ }
35
+
36
+ /**
37
+ * Base message structure for all socket communications
38
+ */
39
+ export interface SocketMessage<T = unknown> {
40
+ type: string;
41
+ payload?: T;
42
+ timestamp: number;
43
+ id?: string;
44
+ }
45
+
46
+ // ============================================================================
47
+ // Payload Types
48
+ // ============================================================================
49
+
50
+ export interface NotificationPayload {
51
+ level: 'info' | 'warning' | 'error' | 'success';
52
+ title: string;
53
+ message: string;
54
+ }
55
+
56
+ export enum DataUpdateResourceType {
57
+ METADATA = 'metadata',
58
+ DATABASE_SCHEMA = 'database_schema',
59
+ TABLE_SCHEMA = 'table_schema',
60
+ STORAGE_SCHEMA = 'storage_schema',
61
+ BUCKET_SCHEMA = 'bucket_schema',
62
+ OAUTH_SCHEMA = 'oauth_schmea',
63
+ }
64
+
65
+ export interface DataUpdatePayload {
66
+ resource: DataUpdateResourceType;
67
+ action: 'created' | 'updated' | 'deleted';
68
+ data: unknown;
69
+ }
70
+
71
+ // ============================================================================
72
+ // Context Types
73
+ // ============================================================================
74
+
75
+ interface SocketState {
76
+ isConnected: boolean;
77
+ connectionError: string | null;
78
+ socketId: string | null;
79
+ }
80
+
81
+ interface SocketActions {
82
+ connect: (token: string | null) => void;
83
+ disconnect: () => void;
84
+ subscribe: (channel: string, filters?: Record<string, unknown>) => void;
85
+ unsubscribe: (channel: string) => void;
86
+ emit: (event: ClientEvents, data?: unknown) => void;
87
+ }
88
+
89
+ interface SocketContextValue extends SocketState, SocketActions {
90
+ socket: Socket | null;
91
+ }
92
+
93
+ // ============================================================================
94
+ // Context & Provider
95
+ // ============================================================================
96
+
97
+ const SocketContext = createContext<SocketContextValue | null>(null);
98
+
99
+ interface SocketProviderProps {
100
+ children: ReactNode;
101
+ }
102
+
103
+ /**
104
+ * Socket.IO Provider - Manages WebSocket connection for the entire application
105
+ */
106
+ export function SocketProvider({ children }: SocketProviderProps) {
107
+ // Get authentication state
108
+ const { isAuthenticated } = useAuth();
109
+ // State
110
+ const [state, setState] = useState<SocketState>({
111
+ isConnected: false,
112
+ connectionError: null,
113
+ socketId: null,
114
+ });
115
+
116
+ // Refs
117
+ const socketRef = useRef<Socket | null>(null);
118
+ const subscriptionsRef = useRef<Set<string>>(new Set());
119
+
120
+ /**
121
+ * Update state helper
122
+ */
123
+ const updateState = useCallback((updates: Partial<SocketState>) => {
124
+ setState((prev) => ({ ...prev, ...updates }));
125
+ }, []);
126
+
127
+ /**
128
+ * Create and configure socket connection
129
+ */
130
+ const createSocket = useCallback(
131
+ (token: string): Socket => {
132
+ const socket = io('/', {
133
+ auth: {
134
+ token,
135
+ },
136
+ });
137
+
138
+ // Core connection events
139
+ socket.on('connect', () => {
140
+ updateState({
141
+ isConnected: true,
142
+ connectionError: null,
143
+ socketId: socket.id || null,
144
+ });
145
+ });
146
+
147
+ socket.on('disconnect', (reason) => {
148
+ updateState({
149
+ isConnected: false,
150
+ socketId: null,
151
+ connectionError: `Disconnected: ${reason}`,
152
+ });
153
+ });
154
+
155
+ socket.on('connect_error', (error) => {
156
+ updateState({
157
+ connectionError: `Connection failed: ${error.message}`,
158
+ isConnected: false,
159
+ });
160
+ });
161
+
162
+ socket.on('error', (error) => {
163
+ updateState({ connectionError: error?.message || 'Unknown error' });
164
+ });
165
+
166
+ socket.on('reconnect', () => {
167
+ updateState({
168
+ isConnected: true,
169
+ connectionError: null,
170
+ });
171
+
172
+ // Re-subscribe to channels after reconnection
173
+ subscriptionsRef.current.forEach((channel) => {
174
+ socket.emit(ClientEvents.SUBSCRIBE, { channel });
175
+ });
176
+ });
177
+
178
+ return socket;
179
+ },
180
+ [updateState]
181
+ );
182
+
183
+ /**
184
+ * Connect to socket server
185
+ */
186
+ const connect = useCallback(
187
+ (token: string | null) => {
188
+ // Don't connect without a token
189
+ if (!token) {
190
+ return;
191
+ }
192
+
193
+ // Don't reconnect if already connected with the same token
194
+ if (socketRef.current?.connected) {
195
+ return;
196
+ }
197
+
198
+ try {
199
+ const socket = createSocket(token);
200
+ socketRef.current = socket;
201
+ } catch (error) {
202
+ console.error('Socket connection error:', error);
203
+ updateState({ connectionError: 'Failed to establish connection' });
204
+ }
205
+ },
206
+ [createSocket, updateState]
207
+ );
208
+
209
+ /**
210
+ * Disconnect from socket server
211
+ */
212
+ const disconnect = useCallback(() => {
213
+ if (socketRef.current) {
214
+ socketRef.current.disconnect();
215
+ socketRef.current = null;
216
+ }
217
+
218
+ updateState({
219
+ isConnected: false,
220
+ connectionError: null,
221
+ socketId: null,
222
+ });
223
+
224
+ subscriptionsRef.current.clear();
225
+ }, [updateState]);
226
+
227
+ /**
228
+ * Subscribe to a channel
229
+ */
230
+ const subscribe = useCallback((channel: string, filters?: Record<string, unknown>) => {
231
+ if (!socketRef.current?.connected) {
232
+ return;
233
+ }
234
+
235
+ socketRef.current.emit(ClientEvents.SUBSCRIBE, { channel, filters });
236
+ subscriptionsRef.current.add(channel);
237
+ }, []);
238
+
239
+ /**
240
+ * Unsubscribe from a channel
241
+ */
242
+ const unsubscribe = useCallback((channel: string) => {
243
+ if (!socketRef.current?.connected) {
244
+ return;
245
+ }
246
+
247
+ socketRef.current.emit(ClientEvents.UNSUBSCRIBE, { channel });
248
+ subscriptionsRef.current.delete(channel);
249
+ }, []);
250
+
251
+ /**
252
+ * Emit event to server
253
+ */
254
+ const emit = useCallback((event: ClientEvents, data?: unknown) => {
255
+ socketRef.current?.emit(event, data);
256
+ }, []);
257
+
258
+ // Monitor authentication state and token changes
259
+ useEffect(() => {
260
+ const token = apiClient.getToken();
261
+
262
+ if (isAuthenticated && token) {
263
+ // Connect when authenticated with a valid token
264
+ connect(token);
265
+ } else {
266
+ // Disconnect when not authenticated or no token
267
+ disconnect();
268
+ }
269
+ }, [isAuthenticated, connect, disconnect]);
270
+
271
+ // Clean up on unmount
272
+ useEffect(() => {
273
+ return () => {
274
+ disconnect();
275
+ };
276
+ }, [disconnect]);
277
+
278
+ // Context value
279
+ const contextValue = useMemo<SocketContextValue>(
280
+ () => ({
281
+ // State
282
+ socket: socketRef.current,
283
+ ...state,
284
+ // Actions
285
+ connect,
286
+ disconnect,
287
+ subscribe,
288
+ unsubscribe,
289
+ emit,
290
+ }),
291
+ [state, connect, disconnect, subscribe, unsubscribe, emit]
292
+ );
293
+
294
+ return <SocketContext.Provider value={contextValue}>{children}</SocketContext.Provider>;
295
+ }
296
+
297
+ export function useSocket() {
298
+ const context = useContext(SocketContext);
299
+ if (!context) {
300
+ throw new Error('useSocketContext must be used within a SocketProvider');
301
+ }
302
+ return context;
303
+ }
@@ -0,0 +1,125 @@
1
+ import React, { createContext, useContext, useEffect, useState } from 'react';
2
+
3
+ type Theme = 'light' | 'dark' | 'system';
4
+ type ResolvedTheme = 'light' | 'dark';
5
+
6
+ interface ThemeContextType {
7
+ theme: Theme;
8
+ resolvedTheme: ResolvedTheme;
9
+ setTheme: (theme: Theme) => void;
10
+ toggleTheme: () => void;
11
+ }
12
+
13
+ const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
14
+
15
+ const STORAGE_KEY = 'insforge-theme';
16
+
17
+ function getSystemTheme(): ResolvedTheme {
18
+ if (typeof window === 'undefined') {
19
+ return 'light';
20
+ }
21
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
22
+ }
23
+
24
+ function resolveTheme(theme: Theme): ResolvedTheme {
25
+ if (theme === 'system') {
26
+ return getSystemTheme();
27
+ }
28
+ return theme;
29
+ }
30
+
31
+ interface ThemeProviderProps {
32
+ children: React.ReactNode;
33
+ forcedTheme?: ResolvedTheme;
34
+ storageKey?: string;
35
+ }
36
+
37
+ export function ThemeProvider({
38
+ children,
39
+ forcedTheme,
40
+ storageKey = STORAGE_KEY,
41
+ }: ThemeProviderProps) {
42
+ const [theme, setThemeState] = useState<Theme>(() => {
43
+ if (forcedTheme) {
44
+ return forcedTheme;
45
+ }
46
+
47
+ if (typeof window !== 'undefined') {
48
+ const stored = localStorage.getItem(storageKey);
49
+ if (stored === 'light' || stored === 'dark' || stored === 'system') {
50
+ return stored;
51
+ }
52
+ }
53
+ return 'system';
54
+ });
55
+
56
+ const resolvedTheme = forcedTheme || resolveTheme(theme);
57
+
58
+ useEffect(() => {
59
+ const root = window.document.documentElement;
60
+
61
+ // Remove both classes first
62
+ root.classList.remove('light', 'dark');
63
+
64
+ // Add the resolved theme class
65
+ root.classList.add(resolvedTheme);
66
+
67
+ // Update meta theme-color for mobile browsers
68
+ const metaThemeColor = document.querySelector('meta[name="theme-color"]');
69
+ if (metaThemeColor) {
70
+ metaThemeColor.setAttribute('content', resolvedTheme === 'dark' ? '#0a0a0a' : '#ffffff');
71
+ }
72
+ }, [resolvedTheme]);
73
+
74
+ useEffect(() => {
75
+ if (forcedTheme) {
76
+ return;
77
+ }
78
+
79
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
80
+
81
+ const handleChange = () => {
82
+ if (theme === 'system') {
83
+ const root = window.document.documentElement;
84
+ const newResolvedTheme = getSystemTheme();
85
+ root.classList.remove('light', 'dark');
86
+ root.classList.add(newResolvedTheme);
87
+ }
88
+ };
89
+
90
+ mediaQuery.addEventListener('change', handleChange);
91
+ return () => mediaQuery.removeEventListener('change', handleChange);
92
+ }, [theme, forcedTheme]);
93
+
94
+ const setTheme = (newTheme: Theme) => {
95
+ if (forcedTheme) {
96
+ return;
97
+ }
98
+
99
+ setThemeState(newTheme);
100
+ localStorage.setItem(storageKey, newTheme);
101
+ };
102
+
103
+ const toggleTheme = () => {
104
+ if (forcedTheme) {
105
+ return;
106
+ }
107
+
108
+ const newTheme = resolvedTheme === 'light' ? 'dark' : 'light';
109
+ setTheme(newTheme);
110
+ };
111
+
112
+ return (
113
+ <ThemeContext.Provider value={{ theme, resolvedTheme, setTheme, toggleTheme }}>
114
+ {children}
115
+ </ThemeContext.Provider>
116
+ );
117
+ }
118
+
119
+ export function useTheme() {
120
+ const context = useContext(ThemeContext);
121
+ if (!context) {
122
+ throw new Error('useTheme must be used within a ThemeProvider');
123
+ }
124
+ return context;
125
+ }
@@ -0,0 +1,4 @@
1
+ // Re-export the useAuth hook from AuthContext for backwards compatibility
2
+ export { useAuth } from '@/lib/contexts/AuthContext';
3
+
4
+ // Additional auth-related hooks can be added here if needed
@@ -0,0 +1,55 @@
1
+ import { useState } from 'react';
2
+
3
+ interface ConfirmOptions {
4
+ title: string;
5
+ description: string;
6
+ confirmText?: string;
7
+ cancelText?: string;
8
+ destructive?: boolean;
9
+ }
10
+
11
+ export function useConfirm() {
12
+ const [isOpen, setIsOpen] = useState(false);
13
+ const [options, setOptions] = useState<ConfirmOptions>({
14
+ title: '',
15
+ description: '',
16
+ });
17
+ const [resolvePromise, setResolvePromise] = useState<((value: boolean) => void) | null>(null);
18
+
19
+ const confirm = (confirmOptions: ConfirmOptions): Promise<boolean> => {
20
+ setOptions(confirmOptions);
21
+ setIsOpen(true);
22
+
23
+ return new Promise<boolean>((resolve) => {
24
+ setResolvePromise(() => resolve);
25
+ });
26
+ };
27
+
28
+ const handleConfirm = () => {
29
+ resolvePromise?.(true);
30
+ setIsOpen(false);
31
+ };
32
+
33
+ const handleCancel = () => {
34
+ resolvePromise?.(false);
35
+ setIsOpen(false);
36
+ };
37
+
38
+ return {
39
+ confirm,
40
+ confirmDialogProps: {
41
+ open: isOpen,
42
+ onOpenChange: (open: boolean) => {
43
+ if (!open) {
44
+ handleCancel();
45
+ }
46
+ },
47
+ title: options.title,
48
+ description: options.description,
49
+ confirmText: options.confirmText,
50
+ cancelText: options.cancelText,
51
+ destructive: options.destructive,
52
+ onConfirm: handleConfirm,
53
+ },
54
+ };
55
+ }
@@ -0,0 +1,27 @@
1
+ import { useEffect, useLayoutEffect, useRef } from 'react';
2
+
3
+ export function useInterval(callback: () => void, delay: number | null) {
4
+ const savedCallback = useRef(callback);
5
+
6
+ // Remember the latest callback if it changes.
7
+ useLayoutEffect(() => {
8
+ savedCallback.current = callback;
9
+ }, [callback]);
10
+
11
+ // Set up the interval.
12
+ useEffect(() => {
13
+ // Don't schedule if no delay is specified.
14
+ // Note: 0 is a valid value for delay.
15
+ if (delay === null) {
16
+ return;
17
+ }
18
+
19
+ const id = setInterval(() => {
20
+ savedCallback.current();
21
+ }, delay);
22
+
23
+ return () => {
24
+ clearInterval(id);
25
+ };
26
+ }, [delay]);
27
+ }
@@ -0,0 +1,59 @@
1
+ import { useCallback, useLayoutEffect, useState } from 'react';
2
+
3
+ type UseMediaQueryOptions = {
4
+ defaultValue?: boolean;
5
+ initializeWithValue?: boolean;
6
+ };
7
+
8
+ const IS_SERVER = typeof window === 'undefined';
9
+
10
+ export function useMediaQuery(
11
+ query: string,
12
+ { defaultValue = false, initializeWithValue = true }: UseMediaQueryOptions = {}
13
+ ): boolean {
14
+ const getMatches = useCallback(
15
+ (query: string): boolean => {
16
+ if (IS_SERVER) {
17
+ return defaultValue;
18
+ }
19
+ return window.matchMedia(query).matches;
20
+ },
21
+ [defaultValue]
22
+ );
23
+
24
+ const [matches, setMatches] = useState<boolean>(() => {
25
+ if (initializeWithValue) {
26
+ return getMatches(query);
27
+ }
28
+ return defaultValue;
29
+ });
30
+
31
+ // Handles the change event of the media query.
32
+ const handleChange = useCallback(() => {
33
+ setMatches(getMatches(query));
34
+ }, [getMatches, query]);
35
+
36
+ useLayoutEffect(() => {
37
+ const matchMedia = window.matchMedia(query);
38
+
39
+ // Triggered at the first client-side load and if query changes
40
+ handleChange();
41
+
42
+ // Use deprecated `addListener` and `removeListener` to support Safari < 14
43
+ if (matchMedia.addListener) {
44
+ matchMedia.addListener(handleChange);
45
+ } else {
46
+ matchMedia.addEventListener('change', handleChange);
47
+ }
48
+
49
+ return () => {
50
+ if (matchMedia.removeListener) {
51
+ matchMedia.removeListener(handleChange);
52
+ } else {
53
+ matchMedia.removeEventListener('change', handleChange);
54
+ }
55
+ };
56
+ }, [handleChange, query]);
57
+
58
+ return matches;
59
+ }
@@ -0,0 +1,29 @@
1
+ import { useMemo } from 'react';
2
+
3
+ const ONBOARDING_COMPLETION_KEY = 'insforge_onboarding_completed';
4
+
5
+ export function useOnboardingCompletion() {
6
+ const isCompleted = useMemo<boolean>(() => {
7
+ const stored = localStorage.getItem(ONBOARDING_COMPLETION_KEY);
8
+ return stored === 'true';
9
+ }, []);
10
+
11
+ const markAsCompleted = () => {
12
+ localStorage.setItem(ONBOARDING_COMPLETION_KEY, 'true');
13
+ };
14
+
15
+ const resetCompletion = () => {
16
+ localStorage.setItem(ONBOARDING_COMPLETION_KEY, 'false');
17
+ };
18
+
19
+ return {
20
+ isCompleted,
21
+ markAsCompleted,
22
+ resetCompletion,
23
+ };
24
+ }
25
+
26
+ // Export utility function for direct usage
27
+ export const markOnboardingAsCompleted = () => {
28
+ localStorage.setItem(ONBOARDING_COMPLETION_KEY, 'true');
29
+ };
@@ -0,0 +1,27 @@
1
+ import { useState, useMemo } from 'react';
2
+
3
+ export function usePagination<T>(data: T[], defaultPageSize = 50) {
4
+ const [currentPage, setCurrentPage] = useState(1);
5
+ const [pageSize] = useState(defaultPageSize);
6
+
7
+ const paginatedData = useMemo(() => {
8
+ const startIndex = (currentPage - 1) * pageSize;
9
+ const endIndex = startIndex + pageSize;
10
+ return data.slice(startIndex, endIndex);
11
+ }, [data, currentPage, pageSize]);
12
+
13
+ const totalPages = Math.ceil(data.length / pageSize);
14
+ const startRecord = (currentPage - 1) * pageSize + 1;
15
+ const endRecord = Math.min(currentPage * pageSize, data.length);
16
+
17
+ return {
18
+ currentPage,
19
+ setCurrentPage,
20
+ pageSize,
21
+ paginatedData,
22
+ totalPages,
23
+ startRecord,
24
+ endRecord,
25
+ totalRecords: data.length,
26
+ };
27
+ }