diff --git a/frontend/common/services/useEnvironment.ts b/frontend/common/services/useEnvironment.ts index 4a0c76e67f63..40d71733220a 100644 --- a/frontend/common/services/useEnvironment.ts +++ b/frontend/common/services/useEnvironment.ts @@ -6,6 +6,17 @@ export const environmentService = service .enhanceEndpoints({ addTagTypes: ['Environment'] }) .injectEndpoints({ endpoints: (builder) => ({ + createEnvironment: builder.mutation< + Res['environment'], + Req['createEnvironment'] + >({ + invalidatesTags: [{ id: 'LIST', type: 'Environment' }], + query: (body: Req['createEnvironment']) => ({ + body, + method: 'POST', + url: `environments/`, + }), + }), getEnvironment: builder.query({ providesTags: (res) => [{ id: res?.id, type: 'Environment' }], query: (query: Req['getEnvironment']) => ({ @@ -84,6 +95,7 @@ export async function updateEnvironment( // END OF FUNCTION_EXPORTS export const { + useCreateEnvironmentMutation, useGetEnvironmentMetricsQuery, useGetEnvironmentQuery, useGetEnvironmentsQuery, diff --git a/frontend/common/services/useOrganisation.ts b/frontend/common/services/useOrganisation.ts index 82e0f45f79f1..dfb1801108bc 100644 --- a/frontend/common/services/useOrganisation.ts +++ b/frontend/common/services/useOrganisation.ts @@ -6,6 +6,17 @@ export const organisationService = service .enhanceEndpoints({ addTagTypes: ['Organisation'] }) .injectEndpoints({ endpoints: (builder) => ({ + createOrganisation: builder.mutation< + Res['organisation'], + Req['createOrganisation'] + >({ + invalidatesTags: [{ id: 'LIST', type: 'Organisation' }], + query: (body: Req['createOrganisation']) => ({ + body, + method: 'POST', + url: `organisations/`, + }), + }), deleteOrganisation: builder.mutation({ invalidatesTags: [{ id: 'LIST', type: 'Organisation' }], query: ({ id }: Req['deleteOrganisation']) => ({ @@ -85,6 +96,7 @@ export async function getOrganisations( // END OF FUNCTION_EXPORTS export const { + useCreateOrganisationMutation, useDeleteOrganisationMutation, useGetOrganisationQuery, useGetOrganisationsQuery, diff --git a/frontend/common/services/useProject.ts b/frontend/common/services/useProject.ts index 73e4a65c0817..988d7607d6ed 100644 --- a/frontend/common/services/useProject.ts +++ b/frontend/common/services/useProject.ts @@ -7,6 +7,14 @@ export const projectService = service .enhanceEndpoints({ addTagTypes: ['Project'] }) .injectEndpoints({ endpoints: (builder) => ({ + createProject: builder.mutation({ + invalidatesTags: [{ id: 'LIST', type: 'Project' }], + query: (body: Req['createProject']) => ({ + body, + method: 'POST', + url: `projects/`, + }), + }), deleteProject: builder.mutation({ invalidatesTags: [{ id: 'LIST', type: 'Project' }], query: ({ id }: Req['deleteProject']) => ({ @@ -99,6 +107,7 @@ export async function getProject( // END OF FUNCTION_EXPORTS export const { + useCreateProjectMutation, useDeleteProjectMutation, useGetProjectPermissionsQuery, useGetProjectQuery, diff --git a/frontend/common/types/requests.ts b/frontend/common/types/requests.ts index 0677227f4702..b7b8c0f92fd5 100644 --- a/frontend/common/types/requests.ts +++ b/frontend/common/types/requests.ts @@ -172,6 +172,7 @@ export type Req = { }> getOrganisations: {} getOrganisation: { id: number } + createOrganisation: { name: string } updateOrganisation: { id: number; body: UpdateOrganisationBody } deleteOrganisation: { id: number } uploadOrganisationLicence: { @@ -635,6 +636,7 @@ export type Req = { id: string } getProject: { id: number } + createProject: { name: string; organisation: number } updateProject: { id: number; body: UpdateProjectBody } deleteProject: { id: number } migrateProject: { id: number } @@ -682,6 +684,7 @@ export type Req = { feature_id: number group_ids: number[] } + createEnvironment: { name: string; project: number } updateEnvironment: { id: number; body: Environment } createCloneIdentityFeatureStates: { environment_id: string diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 35798233d0d7..df792e6a2358 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -35,7 +35,6 @@ "@rspack/plugin-react-refresh": "^1.6.2", "@sentry/browser": "^7.119.1", "@sentry/webpack-plugin": "2.22.7", - "@slack/web-api": "^6.9.1", "array-find-index": "^1.0.2", "body-parser": "^2.2.2", "bootstrap": "5.2.2", @@ -61,6 +60,7 @@ "jquery": "^3.7.1", "js-cookie": "3.0.7", "lodash": "^4.18.1", + "lottie-react": "^2.4.1", "moment": "2.29.4", "node-fetch": "2.6.7", "polyfill-react-native": "1.0.5", @@ -6040,52 +6040,6 @@ "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@slack/logger": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-3.0.0.tgz", - "integrity": "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA==", - "license": "MIT", - "dependencies": { - "@types/node": ">=12.0.0" - }, - "engines": { - "node": ">= 12.13.0", - "npm": ">= 6.12.0" - } - }, - "node_modules/@slack/types": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.15.0.tgz", - "integrity": "sha512-livb1gyG3J8ATLBJ3KjZfjHpTRz9btY1m5cgNuXxWJbhwRB1Gwb8Ly6XLJm2Sy1W6h+vLgqIHg7IwKrF1C1Szg==", - "license": "MIT", - "engines": { - "node": ">= 12.13.0", - "npm": ">= 6.12.0" - } - }, - "node_modules/@slack/web-api": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-6.13.0.tgz", - "integrity": "sha512-dv65crIgdh9ZYHrevLU6XFHTQwTyDmNqEqzuIrV+Vqe/vgiG6w37oex5ePDU1RGm2IJ90H8iOvHFvzdEO/vB+g==", - "license": "MIT", - "dependencies": { - "@slack/logger": "^3.0.0", - "@slack/types": "^2.11.0", - "@types/is-stream": "^1.1.0", - "@types/node": ">=12.0.0", - "axios": "^1.7.4", - "eventemitter3": "^3.1.0", - "form-data": "^2.5.0", - "is-electron": "2.2.2", - "is-stream": "^1.1.0", - "p-queue": "^6.6.1", - "p-retry": "^4.0.0" - }, - "engines": { - "node": ">= 12.13.0", - "npm": ">= 6.12.0" - } - }, "node_modules/@stencil/core": { "version": "4.36.2", "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.36.2.tgz", @@ -7596,15 +7550,6 @@ "@types/node": "*" } }, - "node_modules/@types/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@types/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -7834,12 +7779,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "license": "MIT" - }, "node_modules/@types/semver": { "version": "7.7.1", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", @@ -9514,12 +9453,6 @@ "node": ">= 0.4" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", @@ -9565,42 +9498,6 @@ "node": ">=4" } }, - "node_modules/axios": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", - "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", - "proxy-from-env": "^2.1.0" - } - }, - "node_modules/axios/node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/axios/node_modules/proxy-from-env": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", - "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/babel-jest": { "version": "30.2.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", @@ -10714,18 +10611,6 @@ "node": ">=0.1.90" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -11624,15 +11509,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -12096,6 +11972,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -12808,12 +12685,6 @@ "node": ">=6" } }, - "node_modules/eventemitter3": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", - "license": "MIT" - }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -13775,23 +13646,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/form-data": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", - "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.35", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -14301,6 +14155,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -15383,12 +15238,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-electron": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", - "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", - "license": "MIT" - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -15685,15 +15534,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-string": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", @@ -17602,6 +17442,25 @@ "loose-envify": "cli.js" } }, + "node_modules/lottie-react": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lottie-react/-/lottie-react-2.4.1.tgz", + "integrity": "sha512-LQrH7jlkigIIv++wIyrOYFLHSKQpEY4zehPicL9bQsrt1rnoKRYCYgpCUe5maqylNtacy58/sQDZTkwMcTRxZw==", + "license": "MIT", + "dependencies": { + "lottie-web": "^5.10.2" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/lottie-web": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/lottie-web/-/lottie-web-5.13.0.tgz", + "integrity": "sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ==", + "license": "MIT" + }, "node_modules/loupe": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", @@ -18988,15 +18847,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -19043,53 +18893,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-queue": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", - "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", - "license": "MIT", - "dependencies": { - "eventemitter3": "^4.0.4", - "p-timeout": "^3.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-queue/node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT" - }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "license": "MIT", - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "license": "MIT", - "dependencies": { - "p-finally": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index a8fdf5f42d69..ede60e126585 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -98,6 +98,7 @@ "jquery": "^3.7.1", "js-cookie": "3.0.7", "lodash": "^4.18.1", + "lottie-react": "^2.4.1", "moment": "2.29.4", "node-fetch": "2.6.7", "polyfill-react-native": "1.0.5", diff --git a/frontend/web/components/App.js b/frontend/web/components/App.js index eba77aab7ab1..e2a584ac5387 100644 --- a/frontend/web/components/App.js +++ b/frontend/web/components/App.js @@ -136,8 +136,15 @@ const App = class extends Component { } if (!AccountStore.getOrganisation() && !invite) { - // If user has no organisation redirect to /create - this.props.history.replace(`/create${query}`) + // If user has no organisation redirect to /create — unless the new + // onboarding flow is enabled, which creates the organisation as its + // first step at /getting-started. + const noOrgDestination = Utils.getFlagsmithHasFeature( + 'onboarding_quickstart_flow', + ) + ? '/getting-started' + : '/create' + this.props.history.replace(`${noOrgDestination}${query}`) return } @@ -225,6 +232,12 @@ const App = class extends Component { const projectId = this.getProjectId(this.props) const environmentId = this.getEnvironmentId(this.props) + // The quickstart onboarding flow is a focused, distraction-free surface — + // suppress the marketing announcement banners while the user is in it. + const isOnboardingFlow = + pathname === '/getting-started' && + Utils.getFlagsmithHasFeature('onboarding_quickstart_flow') + if ( AccountStore.getOrganisation() && AccountStore.getOrganisation().block_access_to_admin && @@ -273,24 +286,36 @@ const App = class extends Component { onLogin={this.onLogin} > {({ isSaving, user }, { twoFactorLogin }) => { - return user && user.twoFactorPrompt ? ( -
- { - this.setState({ error: false }) - twoFactorLogin(this.state.pin, () => { - this.setState({ error: true }) - }) - }} - isLoading={isSaving} - onChange={(e) => - this.setState({ pin: Utils.safeParseEventValue(e) }) - } - /> -
- ) : ( + if (user && user.twoFactorPrompt) { + return ( +
+ { + this.setState({ error: false }) + twoFactorLogin(this.state.pin, () => { + this.setState({ error: true }) + }) + }} + isLoading={isSaving} + onChange={(e) => + this.setState({ pin: Utils.safeParseEventValue(e) }) + } + /> +
+ ) + } + + // Chromeless onboarding: render only the flow — no nav, sidebar, + // or header links — so the customer can't navigate away mid-flow. + // The flow provides its own explicit "Skip — set up manually" + // escape. + if (isOnboardingFlow) { + return
{this.props.children}
+ } + + return (