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.
- package/.dockerignore +58 -0
- package/.env.example +49 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +83 -0
- package/.github/ISSUE_TEMPLATE/config.yml +11 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +79 -0
- package/.github/copilot-instructions.md +147 -0
- package/.github/workflows/build-image.yml +65 -0
- package/.github/workflows/ci-premerge-check.yml +24 -0
- package/.github/workflows/deploy-aws.yml +130 -0
- package/.github/workflows/lint-and-format.yml +33 -0
- package/.prettierignore +65 -0
- package/.prettierrc +9 -0
- package/CHANGELOG.md +3 -0
- package/CONTRIBUTING.md +126 -0
- package/Dockerfile +27 -0
- package/GITHUB_OAUTH_SETUP.md +49 -0
- package/GOOGLE_OAUTH_SETUP.md +148 -0
- package/LICENSE +201 -0
- package/README.md +134 -0
- package/assets/Dark.svg +23 -0
- package/assets/archDiagram.png +0 -0
- package/assets/banner.png +0 -0
- package/assets/mcpInstallv2.png +0 -0
- package/assets/sampleResponse.png +0 -0
- package/assets/signin.png +0 -0
- package/assets/userflow.png +0 -0
- package/backend/migrations/000_create-base-tables.sql +142 -0
- package/backend/migrations/001_create-helper-functions.sql +41 -0
- package/backend/migrations/002_rename-auth-tables.sql +30 -0
- package/backend/migrations/003_create-users-table.sql +56 -0
- package/backend/migrations/004_add-reload-postgrest-func.sql +24 -0
- package/backend/migrations/005_enable-project-admin-modify-users.sql +30 -0
- package/backend/migrations/006_modify-ai-usage-table.sql +25 -0
- package/backend/migrations/007_drop-metadata-table.sql +2 -0
- package/backend/migrations/008_add-system-tables.sql +77 -0
- package/backend/migrations/009_add-function-secrets.sql +24 -0
- package/backend/migrations/010_modify-ai-config-modalities.sql +93 -0
- package/backend/migrations/011_refactor-secrets-table.sql +15 -0
- package/backend/migrations/012_add-storage-uploaded-by.sql +8 -0
- package/backend/package.json +75 -0
- package/backend/src/api/middleware/auth.ts +240 -0
- package/backend/src/api/middleware/error.ts +231 -0
- package/backend/src/api/middleware/upload.ts +59 -0
- package/backend/src/api/routes/agent.ts +29 -0
- package/backend/src/api/routes/ai.ts +472 -0
- package/backend/src/api/routes/auth.oauth.ts +482 -0
- package/backend/src/api/routes/auth.ts +386 -0
- package/backend/src/api/routes/database.advance.ts +275 -0
- package/backend/src/api/routes/database.records.ts +246 -0
- package/backend/src/api/routes/database.tables.ts +161 -0
- package/backend/src/api/routes/docs.ts +66 -0
- package/backend/src/api/routes/functions.ts +183 -0
- package/backend/src/api/routes/logs.ts +150 -0
- package/backend/src/api/routes/metadata.ts +160 -0
- package/backend/src/api/routes/openapi.ts +82 -0
- package/backend/src/api/routes/secrets.ts +199 -0
- package/backend/src/api/routes/storage.ts +547 -0
- package/backend/src/api/routes/usage.ts +96 -0
- package/backend/src/core/ai/chat.ts +207 -0
- package/backend/src/core/ai/client.ts +242 -0
- package/backend/src/core/ai/config.ts +187 -0
- package/backend/src/core/ai/image.ts +156 -0
- package/backend/src/core/ai/model.ts +117 -0
- package/backend/src/core/ai/usage.ts +290 -0
- package/backend/src/core/auth/auth.ts +781 -0
- package/backend/src/core/auth/oauth.ts +398 -0
- package/backend/src/core/database/advance.ts +1074 -0
- package/backend/src/core/database/manager.ts +178 -0
- package/backend/src/core/database/table.ts +772 -0
- package/backend/src/core/documentation/agent.ts +689 -0
- package/backend/src/core/documentation/openapi.ts +856 -0
- package/backend/src/core/functions/functions.ts +310 -0
- package/backend/src/core/logs/analytics.ts +76 -0
- package/backend/src/core/logs/audit.ts +255 -0
- package/backend/src/core/logs/providers/base.provider.ts +83 -0
- package/backend/src/core/logs/providers/cloudwatch.provider.ts +510 -0
- package/backend/src/core/logs/providers/localdb.provider.ts +246 -0
- package/backend/src/core/secrets/encryption.ts +58 -0
- package/backend/src/core/secrets/secrets.ts +410 -0
- package/backend/src/core/socket/socket.ts +388 -0
- package/backend/src/core/socket/types.ts +79 -0
- package/backend/src/core/storage/storage.ts +923 -0
- package/backend/src/server.ts +288 -0
- package/backend/src/types/ai.ts +46 -0
- package/backend/src/types/auth.ts +90 -0
- package/backend/src/types/database.ts +136 -0
- package/backend/src/types/error-constants.ts +86 -0
- package/backend/src/types/logs.ts +47 -0
- package/backend/src/types/profile.ts +55 -0
- package/backend/src/types/storage.ts +23 -0
- package/backend/src/utils/cloud-token.ts +39 -0
- package/backend/src/utils/constants.ts +1 -0
- package/backend/src/utils/environment.ts +35 -0
- package/backend/src/utils/helpers.ts +49 -0
- package/backend/src/utils/logger.ts +13 -0
- package/backend/src/utils/response.ts +62 -0
- package/backend/src/utils/seed.ts +205 -0
- package/backend/src/utils/sql-parser.ts +63 -0
- package/backend/src/utils/uuid.ts +9 -0
- package/backend/src/utils/validations.ts +129 -0
- package/backend/tests/README.md +134 -0
- package/backend/tests/cleanup-all-test-data.sh +231 -0
- package/backend/tests/cloud/test-s3-multitenant.sh +132 -0
- package/backend/tests/local/comprehensive-curl-tests.sh +156 -0
- package/backend/tests/local/test-auth-router.sh +144 -0
- package/backend/tests/local/test-database-router.sh +222 -0
- package/backend/tests/local/test-e2e.sh +241 -0
- package/backend/tests/local/test-fk-errors.sh +97 -0
- package/backend/tests/local/test-id-field.sh +201 -0
- package/backend/tests/local/test-public-bucket.sh +265 -0
- package/backend/tests/local/test-secrets.sh +248 -0
- package/backend/tests/local/test-serverless-functions.sh.disabled +325 -0
- package/backend/tests/local/test-traditional-rest.sh +209 -0
- package/backend/tests/manual/README.md +51 -0
- package/backend/tests/manual/create-large-table-simple.sql +11 -0
- package/backend/tests/manual/seed-large-table.sql +101 -0
- package/backend/tests/manual/setup-large-table-extras.sql +34 -0
- package/backend/tests/manual/test-better-auth.sh +303 -0
- package/backend/tests/manual/test-bulk-upsert.sh +410 -0
- package/backend/tests/manual/test-database-advance.sh +297 -0
- package/backend/tests/manual/test-postgrest-stability.sh +192 -0
- package/backend/tests/manual/test-rawsql-export-import.sh +412 -0
- package/backend/tests/manual/test-universal-storage.sh +264 -0
- package/backend/tests/manual/test-users.sql +18 -0
- package/backend/tests/run-all-tests.sh +140 -0
- package/backend/tests/setup.ts +22 -0
- package/backend/tests/test-config.sh +303 -0
- package/backend/tsconfig.json +23 -0
- package/backend/tsup.config.ts +18 -0
- package/backend/vitest.config.ts +22 -0
- package/docker-compose.prod.yml +145 -0
- package/docker-compose.yml +167 -0
- package/docker-init/db/db-init.sql +125 -0
- package/docker-init/db/jwt.sql +5 -0
- package/docker-init/db/logs.sql +9 -0
- package/docker-init/db/postgresql.conf +17 -0
- package/docs/deprecated/insforge-auth-api.md +215 -0
- package/docs/deprecated/insforge-auth-sdk.md +100 -0
- package/docs/deprecated/insforge-db-api.md +359 -0
- package/docs/deprecated/insforge-db-sdk.md +140 -0
- package/docs/deprecated/insforge-debug-sdk.md +157 -0
- package/docs/deprecated/insforge-debug.md +65 -0
- package/docs/deprecated/insforge-instructions.md +124 -0
- package/docs/deprecated/insforge-project.md +118 -0
- package/docs/deprecated/insforge-storage-api.md +279 -0
- package/docs/deprecated/insforge-storage-sdk.md +159 -0
- package/docs/insforge-instructions-sdk.md +407 -0
- package/eslint.config.js +317 -0
- package/examples/oauth/frontend-oauth-example.html +251 -0
- package/examples/response-examples.md +444 -0
- package/frontend/README.md +112 -0
- package/frontend/components.json +17 -0
- package/frontend/index.html +13 -0
- package/frontend/package.json +63 -0
- package/frontend/public/favicon.ico +0 -0
- package/frontend/src/App.tsx +106 -0
- package/frontend/src/assets/icons/checkbox_checked.svg +6 -0
- package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -0
- package/frontend/src/assets/icons/checked.svg +3 -0
- package/frontend/src/assets/icons/error.svg +3 -0
- package/frontend/src/assets/icons/pencil.svg +4 -0
- package/frontend/src/assets/icons/refresh.svg +4 -0
- package/frontend/src/assets/icons/step_active.svg +3 -0
- package/frontend/src/assets/icons/step_inactive.svg +11 -0
- package/frontend/src/assets/icons/warning.svg +3 -0
- package/frontend/src/assets/logos/amazon.svg +1 -0
- package/frontend/src/assets/logos/claude_code.svg +3 -0
- package/frontend/src/assets/logos/cline.svg +6 -0
- package/frontend/src/assets/logos/cursor.svg +20 -0
- package/frontend/src/assets/logos/discord.svg +9 -0
- package/frontend/src/assets/logos/gemini.svg +19 -0
- package/frontend/src/assets/logos/github.svg +5 -0
- package/frontend/src/assets/logos/google.svg +13 -0
- package/frontend/src/assets/logos/grok.svg +10 -0
- package/frontend/src/assets/logos/insforge_dark.svg +15 -0
- package/frontend/src/assets/logos/insforge_light.svg +15 -0
- package/frontend/src/assets/logos/openai.svg +10 -0
- package/frontend/src/assets/logos/roo_code.svg +9 -0
- package/frontend/src/assets/logos/trae.svg +3 -0
- package/frontend/src/assets/logos/windsurf.svg +10 -0
- package/frontend/src/components/ButtonWithLoading.tsx +27 -0
- package/frontend/src/components/Checkbox.tsx +61 -0
- package/frontend/src/components/CodeBlock.tsx +32 -0
- package/frontend/src/components/ConfirmDialog.tsx +96 -0
- package/frontend/src/components/CopyButton.tsx +69 -0
- package/frontend/src/components/DeleteActionButton.tsx +42 -0
- package/frontend/src/components/EmptyState.tsx +41 -0
- package/frontend/src/components/ErrorState.tsx +35 -0
- package/frontend/src/components/FeatureSidebar.tsx +126 -0
- package/frontend/src/components/FeatureSidebarItem.tsx +101 -0
- package/frontend/src/components/JsonHighlight.tsx +61 -0
- package/frontend/src/components/LoadingState.tsx +16 -0
- package/frontend/src/components/PaginationControls.tsx +54 -0
- package/frontend/src/components/PromptDialog.tsx +68 -0
- package/frontend/src/components/SearchInput.tsx +90 -0
- package/frontend/src/components/SelectionClearButton.tsx +26 -0
- package/frontend/src/components/Stepper.tsx +139 -0
- package/frontend/src/components/ThemeToggle.tsx +58 -0
- package/frontend/src/components/TypeBadge.tsx +20 -0
- package/frontend/src/components/datagrid/DataGrid.tsx +264 -0
- package/frontend/src/components/datagrid/DefaultCellRenderer.tsx +114 -0
- package/frontend/src/components/datagrid/IdCell.tsx +44 -0
- package/frontend/src/components/datagrid/SortableHeader.tsx +74 -0
- package/frontend/src/components/datagrid/cell-editors/BooleanCellEditor.tsx +54 -0
- package/frontend/src/components/datagrid/cell-editors/DateCellEditor.tsx +483 -0
- package/frontend/src/components/datagrid/cell-editors/JsonCellEditor.tsx +362 -0
- package/frontend/src/components/datagrid/cell-editors/TextCellEditor.tsx +38 -0
- package/frontend/src/components/datagrid/cell-editors/index.ts +14 -0
- package/frontend/src/components/datagrid/cell-editors/types.ts +43 -0
- package/frontend/src/components/datagrid/datagridTypes.tsx +72 -0
- package/frontend/src/components/datagrid/index.tsx +20 -0
- package/frontend/src/components/index.ts +39 -0
- package/frontend/src/components/layout/AppHeader.tsx +146 -0
- package/frontend/src/components/layout/AppSidebar.tsx +190 -0
- package/frontend/src/components/layout/CloudLayout.tsx +95 -0
- package/frontend/src/components/layout/Layout.tsx +43 -0
- package/frontend/src/components/radix/Alert.tsx +45 -0
- package/frontend/src/components/radix/AlertDialog.tsx +115 -0
- package/frontend/src/components/radix/Avatar.tsx +45 -0
- package/frontend/src/components/radix/Badge.tsx +33 -0
- package/frontend/src/components/radix/Button.tsx +50 -0
- package/frontend/src/components/radix/Card.tsx +58 -0
- package/frontend/src/components/radix/Dialog.tsx +98 -0
- package/frontend/src/components/radix/DropdownMenu.tsx +185 -0
- package/frontend/src/components/radix/Form.tsx +167 -0
- package/frontend/src/components/radix/Input.tsx +22 -0
- package/frontend/src/components/radix/Label.tsx +19 -0
- package/frontend/src/components/radix/Popover.tsx +29 -0
- package/frontend/src/components/radix/ScrollArea.tsx +44 -0
- package/frontend/src/components/radix/Select.tsx +151 -0
- package/frontend/src/components/radix/Separator.tsx +26 -0
- package/frontend/src/components/radix/Sheet.tsx +119 -0
- package/frontend/src/components/radix/Skeleton.tsx +7 -0
- package/frontend/src/components/radix/Switch.tsx +29 -0
- package/frontend/src/components/radix/Tabs.tsx +50 -0
- package/frontend/src/components/radix/Textarea.tsx +21 -0
- package/frontend/src/components/radix/Tooltip.tsx +28 -0
- package/frontend/src/features/ai/components/AIConfigCard.tsx +154 -0
- package/frontend/src/features/ai/components/AIConfigDialog.tsx +76 -0
- package/frontend/src/features/ai/components/AIConfigForm.tsx +222 -0
- package/frontend/src/features/ai/components/AIEmptyState.tsx +18 -0
- package/frontend/src/features/ai/components/fields/ModalityField.tsx +87 -0
- package/frontend/src/features/ai/components/fields/ModelSelectionField.tsx +134 -0
- package/frontend/src/features/ai/components/fields/SystemPromptField.tsx +33 -0
- package/frontend/src/features/ai/helpers.ts +155 -0
- package/frontend/src/features/ai/hooks/useAIConfigs.ts +221 -0
- package/frontend/src/features/ai/hooks/useAIUsage.ts +77 -0
- package/frontend/src/features/ai/page/AIPage.tsx +178 -0
- package/frontend/src/features/ai/services/ai.service.ts +148 -0
- package/frontend/src/features/auth/components/AddOAuthDialog.tsx +106 -0
- package/frontend/src/features/auth/components/AuthMethodTab.tsx +238 -0
- package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +303 -0
- package/frontend/src/features/auth/components/OAuthEmptyState.tsx +15 -0
- package/frontend/src/features/auth/components/UserFormDialog.tsx +248 -0
- package/frontend/src/features/auth/components/UsersDataGrid.tsx +183 -0
- package/frontend/src/features/auth/components/UsersTab.tsx +114 -0
- package/frontend/src/features/auth/hooks/useOAuthConfig.ts +129 -0
- package/frontend/src/features/auth/hooks/useUsers.ts +57 -0
- package/frontend/src/features/auth/index.ts +9 -0
- package/frontend/src/features/auth/page/AuthenticationPage.tsx +169 -0
- package/frontend/src/features/auth/services/auth.service.ts +112 -0
- package/frontend/src/features/auth/services/oauth.service.ts +49 -0
- package/frontend/src/features/dashboard/page/DashboardPage.tsx +194 -0
- package/frontend/src/features/database/components/ColumnTypeSelect.tsx +64 -0
- package/frontend/src/features/database/components/DatabaseDataGrid.tsx +282 -0
- package/frontend/src/features/database/components/ForeignKeyCell.tsx +187 -0
- package/frontend/src/features/database/components/ForeignKeyPopover.tsx +378 -0
- package/frontend/src/features/database/components/LinkRecordModal.tsx +288 -0
- package/frontend/src/features/database/components/RecordFormDialog.tsx +164 -0
- package/frontend/src/features/database/components/RecordFormField.tsx +568 -0
- package/frontend/src/features/database/components/TableEmptyState.tsx +21 -0
- package/frontend/src/features/database/components/TableForm.tsx +656 -0
- package/frontend/src/features/database/components/TableFormColumn.tsx +137 -0
- package/frontend/src/features/database/components/TableListSkeleton.tsx +9 -0
- package/frontend/src/features/database/components/TableSidebar.tsx +47 -0
- package/frontend/src/features/database/constants.ts +26 -0
- package/frontend/src/features/database/helpers.ts +125 -0
- package/frontend/src/features/database/hooks/UseLinkModal.tsx +78 -0
- package/frontend/src/features/database/index.ts +12 -0
- package/frontend/src/features/database/page/DatabasePage.tsx +626 -0
- package/frontend/src/features/database/schema.ts +25 -0
- package/frontend/src/features/database/services/database.service.ts +216 -0
- package/frontend/src/features/functions/components/FunctionEmptyState.tsx +15 -0
- package/frontend/src/features/functions/components/FunctionRow.tsx +71 -0
- package/frontend/src/features/functions/components/FunctionViewer.tsx +46 -0
- package/frontend/src/features/functions/components/FunctionsContent.tsx +88 -0
- package/frontend/src/features/functions/components/FunctionsSidebar.tsx +56 -0
- package/frontend/src/features/functions/components/SecretEmptyState.tsx +23 -0
- package/frontend/src/features/functions/components/SecretRow.tsx +68 -0
- package/frontend/src/features/functions/components/SecretsContent.tsx +120 -0
- package/frontend/src/features/functions/hooks/useFunctions.ts +106 -0
- package/frontend/src/features/functions/page/FunctionsPage.tsx +28 -0
- package/frontend/src/features/functions/services/functions.service.ts +48 -0
- package/frontend/src/features/login/components/AuthErrorBoundary.tsx +87 -0
- package/frontend/src/features/login/components/PrivateRoute.tsx +24 -0
- package/frontend/src/features/login/page/CloudLoginPage.tsx +93 -0
- package/frontend/src/features/login/page/LoginPage.tsx +174 -0
- package/frontend/src/features/logs/components/AnalyticsLogsTable.tsx +313 -0
- package/frontend/src/features/logs/components/LogsTable.tsx +199 -0
- package/frontend/src/features/logs/hooks/useAuditLogs.ts +39 -0
- package/frontend/src/features/logs/index.ts +5 -0
- package/frontend/src/features/logs/page/AnalyticsLogsPage.tsx +530 -0
- package/frontend/src/features/logs/page/AuditsPage.tsx +192 -0
- package/frontend/src/features/logs/services/log.service.ts +171 -0
- package/frontend/src/features/metadata/hooks/useMetadata.ts +53 -0
- package/frontend/src/features/metadata/index.ts +0 -0
- package/frontend/src/features/metadata/page/MetadataPage.tsx +136 -0
- package/frontend/src/features/metadata/services/metadata.service.ts +17 -0
- package/frontend/src/features/onboard/components/CompletionCard.tsx +41 -0
- package/frontend/src/features/onboard/components/OnboardButton.tsx +84 -0
- package/frontend/src/features/onboard/components/StepContent.tsx +91 -0
- package/frontend/src/features/onboard/components/TestConnectionStep.tsx +53 -0
- package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +35 -0
- package/frontend/src/features/onboard/components/mcp/McpInstallation.tsx +144 -0
- package/frontend/src/features/onboard/components/mcp/index.ts +4 -0
- package/frontend/src/features/onboard/components/mcp/mcp-helper.tsx +98 -0
- package/frontend/src/features/onboard/index.ts +3 -0
- package/frontend/src/features/onboard/page/OnBoardPage.tsx +104 -0
- package/frontend/src/features/onboard/types.ts +8 -0
- package/frontend/src/features/secrets/hooks/useSecrets.ts +139 -0
- package/frontend/src/features/secrets/services/secrets.service.ts +57 -0
- package/frontend/src/features/storage/components/BucketEmptyState.tsx +19 -0
- package/frontend/src/features/storage/components/BucketFormDialog.tsx +194 -0
- package/frontend/src/features/storage/components/BucketListSkeleton.tsx +17 -0
- package/frontend/src/features/storage/components/FilePreviewDialog.tsx +287 -0
- package/frontend/src/features/storage/components/StorageDataGrid.tsx +239 -0
- package/frontend/src/features/storage/components/StorageManager.tsx +236 -0
- package/frontend/src/features/storage/components/StorageSidebar.tsx +44 -0
- package/frontend/src/features/storage/components/UploadToast.tsx +46 -0
- package/frontend/src/features/storage/index.ts +3 -0
- package/frontend/src/features/storage/page/StoragePage.tsx +553 -0
- package/frontend/src/features/storage/services/storage.service.ts +144 -0
- package/frontend/src/features/visualizer/components/AuthNode.tsx +107 -0
- package/frontend/src/features/visualizer/components/BucketNode.tsx +34 -0
- package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +359 -0
- package/frontend/src/features/visualizer/components/TableNode.tsx +152 -0
- package/frontend/src/features/visualizer/components/VisualizerSkeleton.tsx +24 -0
- package/frontend/src/features/visualizer/components/index.ts +5 -0
- package/frontend/src/features/visualizer/page/VisualizerPage.tsx +127 -0
- package/frontend/src/index.css +248 -0
- package/frontend/src/lib/api/client.ts +163 -0
- package/frontend/src/lib/contexts/AuthContext.tsx +157 -0
- package/frontend/src/lib/contexts/OnboardStepContext.tsx +68 -0
- package/frontend/src/lib/contexts/SocketContext.tsx +303 -0
- package/frontend/src/lib/contexts/ThemeContext.tsx +125 -0
- package/frontend/src/lib/hooks/useAuth.ts +4 -0
- package/frontend/src/lib/hooks/useConfirm.ts +55 -0
- package/frontend/src/lib/hooks/useInterval.ts +27 -0
- package/frontend/src/lib/hooks/useMediaQuery.ts +59 -0
- package/frontend/src/lib/hooks/useOnboardingCompletion.ts +29 -0
- package/frontend/src/lib/hooks/usePagination.ts +27 -0
- package/frontend/src/lib/hooks/useTimeout.ts +27 -0
- package/frontend/src/lib/hooks/useToast.tsx +229 -0
- package/frontend/src/lib/utils/constants.ts +38 -0
- package/frontend/src/lib/utils/utils.ts +165 -0
- package/frontend/src/lib/utils/validation-schemas.ts +126 -0
- package/frontend/src/main.tsx +16 -0
- package/frontend/src/rdg.css +194 -0
- package/frontend/src/vite-env.d.ts +12 -0
- package/frontend/tailwind.config.js +97 -0
- package/frontend/tsconfig.json +26 -0
- package/frontend/tsconfig.node.json +10 -0
- package/frontend/vite.config.ts +37 -0
- package/frontend/vitest.config.ts +36 -0
- package/functions/deno.json +25 -0
- package/functions/server.ts +290 -0
- package/functions/worker-template.js +126 -0
- package/openapi/ai.yaml +689 -0
- package/openapi/auth.yaml +563 -0
- package/openapi/functions.yaml +476 -0
- package/openapi/health.yaml +30 -0
- package/openapi/logs.yaml +224 -0
- package/openapi/metadata.yaml +178 -0
- package/openapi/records.yaml +382 -0
- package/openapi/secrets.yaml +371 -0
- package/openapi/storage.yaml +876 -0
- package/openapi/tables.yaml +464 -0
- package/package.json +88 -0
- package/shared-schemas/package.json +31 -0
- package/shared-schemas/src/ai-api.schema.ts +167 -0
- package/shared-schemas/src/ai.schema.ts +54 -0
- package/shared-schemas/src/auth-api.schema.ts +193 -0
- package/shared-schemas/src/auth.schema.ts +94 -0
- package/shared-schemas/src/database-api.schema.ts +259 -0
- package/shared-schemas/src/database.schema.ts +69 -0
- package/shared-schemas/src/functions-api.schema.ts +25 -0
- package/shared-schemas/src/functions.schema.ts +16 -0
- package/shared-schemas/src/index.ts +13 -0
- package/shared-schemas/src/logs-api.schema.ts +49 -0
- package/shared-schemas/src/logs.schema.ts +14 -0
- package/shared-schemas/src/metadata.schema.ts +56 -0
- package/shared-schemas/src/storage-api.schema.ts +65 -0
- package/shared-schemas/src/storage.schema.ts +19 -0
- package/shared-schemas/tsconfig.json +21 -0
- 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
|
+
}
|