From 4e9b7396f976d980c1df23e59fbb1c2e588c807d Mon Sep 17 00:00:00 2001 From: Amir Hasanbasic Date: Thu, 28 Aug 2025 13:20:50 +0200 Subject: [PATCH 1/5] fix(front): Add skip onboarding button --- .../new/components/project_status.tsx | 33 ++++++++++++++- .../workflow_setup/existing_configuration.tsx | 39 ++++------------- .../workflow_setup/project_environment.tsx | 25 +++++++++-- .../new/stores/create/config.ts | 2 + .../new/stores/create/repository.ts | 18 ++++++++ .../new/utils/skip_onboarding.ts | 42 +++++++++++++++++++ .../project_onboarding_controller.ex | 3 +- 7 files changed, 125 insertions(+), 37 deletions(-) create mode 100644 front/assets/js/project_onboarding/new/utils/skip_onboarding.ts diff --git a/front/assets/js/project_onboarding/new/components/project_status.tsx b/front/assets/js/project_onboarding/new/components/project_status.tsx index 103df2d41..becfd3946 100644 --- a/front/assets/js/project_onboarding/new/components/project_status.tsx +++ b/front/assets/js/project_onboarding/new/components/project_status.tsx @@ -5,6 +5,7 @@ import * as stores from "../stores"; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import { Notice } from "js/notice"; +import { handleSkipOnboarding } from "../utils/skip_onboarding"; interface ProjectStatusProps { isCreatingProject: boolean; @@ -21,6 +22,8 @@ interface ProjectStatusProps { nextScreenUrl?: string; repoConnectionUrl?: string; csrfToken: string; + skipOnboardingUrl?: string; + projectUrl?: string; } interface ConnectionData { @@ -67,10 +70,26 @@ export const ProjectStatus = ({ nextScreenUrl, repoConnectionUrl, csrfToken, + skipOnboardingUrl, + projectUrl, }: ProjectStatusProps) => { const [connectionData, setConnectionData] = useState(null); const { state } = useContext(stores.Create.Repository.Context); + const onSkipOnboarding = () => { + // Use props first, then fallback to context state + const skipUrl = skipOnboardingUrl || state.skipOnboardingUrl; + const projUrl = projectUrl || (state.createdProjectName ? `/projects/${state.createdProjectName}` : undefined); + + if (skipUrl && projUrl) { + void handleSkipOnboarding({ + skipOnboardingUrl: skipUrl, + csrfToken, + projectUrl: projUrl + }); + } + }; + const fetchConnectionData = async () => { if (isComplete && repoConnectionUrl) { try { @@ -157,7 +176,19 @@ export const ProjectStatus = ({ )} {isComplete && nextScreenUrl && (
-

Next, we'll configure your build environment settings.

+
+

Next, we'll configure your build environment settings.

+ {(skipOnboardingUrl || state.skipOnboardingUrl) && (projectUrl || state.createdProjectName) && ( + { e.preventDefault(); onSkipOnboarding(); }} + className="f6 link dim gray underline" + title="Skip the onboarding process and go directly to the project (for advanced users)" + > + Skip onboarding + + )} +
Continue
)} diff --git a/front/assets/js/project_onboarding/new/pages/workflow_setup/existing_configuration.tsx b/front/assets/js/project_onboarding/new/pages/workflow_setup/existing_configuration.tsx index 52d554435..f15e89087 100644 --- a/front/assets/js/project_onboarding/new/pages/workflow_setup/existing_configuration.tsx +++ b/front/assets/js/project_onboarding/new/pages/workflow_setup/existing_configuration.tsx @@ -5,10 +5,8 @@ import * as components from "../../components"; import { useContext, useLayoutEffect } from "preact/hooks"; import { useNavigate } from "react-router-dom"; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import { Notice } from "js/notice"; import { useSteps } from "../../stores/create/steps"; +import { handleSkipOnboarding } from "../../utils/skip_onboarding"; export const ExistingConfiguration = () => { const { state: configState } = useContext(stores.WorkflowSetup.Config.Context); @@ -19,33 +17,12 @@ export const ExistingConfiguration = () => { dispatch([`SET_CURRENT`, `setup-workflow`]); }, []); - const handleUseExisting = async () => { - try { - const response = await fetch(configState.skipOnboardingUrl, { - method: 'POST', - headers: { - 'Content-Type': `application/json`, - 'X-CSRF-Token': configState.csrfToken, - }, - credentials: 'same-origin' - }); - - const data = await response.json(); - - if (data.redirect_to) { - window.location.href = data.redirect_to; - } else { - Notice.error('Error during skip onboarding: Invalid response from server'); - } - } catch (error: unknown) { - const errorMessage = error instanceof Error - ? error.message - : 'Unknown error occurred'; - - Notice.error('Error during skip onboarding: ' + errorMessage); - // Fallback to project URL in case of error - window.location.href = configState.projectUrl; - } + const handleUseExisting = () => { + void handleSkipOnboarding({ + skipOnboardingUrl: configState.skipOnboardingUrl, + csrfToken: configState.csrfToken, + projectUrl: configState.projectUrl + }); }; const handleCreateNew = () => { @@ -75,7 +52,7 @@ export const ExistingConfiguration = () => {
{ e.preventDefault(); void handleUseExisting(); }} + onClick={(e) => { e.preventDefault(); handleUseExisting(); }} className="db f4 mb1" > I will use the existing configuration diff --git a/front/assets/js/project_onboarding/new/pages/workflow_setup/project_environment.tsx b/front/assets/js/project_onboarding/new/pages/workflow_setup/project_environment.tsx index 859394322..c72e0e2b9 100644 --- a/front/assets/js/project_onboarding/new/pages/workflow_setup/project_environment.tsx +++ b/front/assets/js/project_onboarding/new/pages/workflow_setup/project_environment.tsx @@ -5,10 +5,9 @@ import * as stores from "../../stores"; import { useContext, useCallback, useEffect } from "preact/hooks"; import { useNavigate } from "react-router-dom"; import Tippy from "@tippyjs/react"; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import { Notice } from "js/notice"; import { useSteps } from "../../stores/create/steps"; +import { handleSkipOnboarding } from "../../utils/skip_onboarding"; +import { Notice } from "js/notice"; interface AgentCardProps { agent: stores.WorkflowSetup.Config.AgentType | stores.WorkflowSetup.Config.SelfHostedAgentType; @@ -97,6 +96,14 @@ export const Projectenvironment = () => { } }; + const onSkipOnboarding = () => { + void handleSkipOnboarding({ + skipOnboardingUrl: configState.skipOnboardingUrl, + csrfToken: configState.csrfToken, + projectUrl: configState.projectUrl + }); + }; + return (
@@ -206,7 +213,17 @@ export const Projectenvironment = () => {
-

Next, we'll define your build steps and workflow.

+
; @@ -160,6 +164,16 @@ function repositoryReducer(state: RepositoryState, action: RepositoryAction): Re ...state, repoConnectionUrl: action.payload }; + case `SET_CREATED_PROJECT_NAME`: + return { + ...state, + createdProjectName: action.payload + }; + case `SET_SKIP_ONBOARDING`: + return { + ...state, + skipOnboardingUrl: action.payload + }; case `UPDATE_PROJECT_STATUS`: return { ...state, @@ -348,6 +362,10 @@ export function useRepositoryStore(configState: stores.Config.State) { if (data.repo_connection_url) { dispatch({ type: `SET_REPO_CONNECTION_URL`, payload: data.repo_connection_url }); } + if (data.project_name) { + dispatch({ type: `SET_CREATED_PROJECT_NAME`, payload: data.project_name }); + dispatch({ type: `SET_SKIP_ONBOARDING`, payload: data.skip_project_onboarding }); + } void checkProjectStatus(data.check_url as string); } } catch (error) { diff --git a/front/assets/js/project_onboarding/new/utils/skip_onboarding.ts b/front/assets/js/project_onboarding/new/utils/skip_onboarding.ts new file mode 100644 index 000000000..91d58d550 --- /dev/null +++ b/front/assets/js/project_onboarding/new/utils/skip_onboarding.ts @@ -0,0 +1,42 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import { Notice } from "js/notice"; + +interface SkipOnboardingOptions { + skipOnboardingUrl: string; + csrfToken: string; + projectUrl: string; +} + +export const handleSkipOnboarding = async ({ + skipOnboardingUrl, + csrfToken, + projectUrl +}: SkipOnboardingOptions): Promise => { + try { + const response = await fetch(skipOnboardingUrl, { + method: `POST`, + headers: { + 'Content-Type': `application/json`, + 'X-CSRF-Token': csrfToken, + }, + credentials: `same-origin` + }); + + const data = await response.json(); + + if (data.redirect_to) { + window.location.href = data.redirect_to; + } else { + Notice.error(`Error during skip onboarding: Invalid response from server`); + } + } catch (error: unknown) { + const errorMessage = error instanceof Error + ? error.message + : `Unknown error occurred`; + + Notice.error(`Error during skip onboarding: ` + errorMessage); + // Fallback to project URL in case of error + window.location.href = projectUrl; + } +}; \ No newline at end of file diff --git a/front/lib/front_web/controllers/project_onboarding_controller.ex b/front/lib/front_web/controllers/project_onboarding_controller.ex index cc9df56b6..f596e5180 100644 --- a/front/lib/front_web/controllers/project_onboarding_controller.ex +++ b/front/lib/front_web/controllers/project_onboarding_controller.ex @@ -350,7 +350,8 @@ defmodule FrontWeb.ProjectOnboardingController do check_url: project_onboarding_path(conn, :is_ready, project.name), repo_connection_url: project_onboarding_path(conn, :project_repository_status, project.name), - project_name: project.name + project_name: project.name, + skip_project_onboarding: project_onboarding_path(conn, :skip_onboarding, project.name) }) else conn From 554c8073853f42b28ad47720ce895410ab1d7e86 Mon Sep 17 00:00:00 2001 From: Amir Hasanbasic Date: Mon, 8 Sep 2025 10:54:43 +0200 Subject: [PATCH 2/5] fix(front): minor code refactoring based on PR review --- front/assets/js/account/profile_management.js | 51 +++++++++++++++++++ .../new/components/project_status.tsx | 5 +- .../workflow_setup/existing_configuration.tsx | 10 ++-- .../new/stores/create/repository.ts | 6 +-- .../project_onboarding_controller.ex | 2 +- 5 files changed, 64 insertions(+), 10 deletions(-) create mode 100644 front/assets/js/account/profile_management.js diff --git a/front/assets/js/account/profile_management.js b/front/assets/js/account/profile_management.js new file mode 100644 index 000000000..353c4632c --- /dev/null +++ b/front/assets/js/account/profile_management.js @@ -0,0 +1,51 @@ +// Enhanced form validation for email changes +document.addEventListener('DOMContentLoaded', function() { + const emailForm = document.getElementById('email-form'); + + if (emailForm) { + emailForm.addEventListener('submit', function(e) { + const emailInput = emailForm.querySelector('input[type="email"]'); + const currentEmail = emailInput.placeholder; + const newEmail = emailInput.value.trim(); + + // Check if email is different from current + if (newEmail === currentEmail) { + e.preventDefault(); + alert('Please enter a different email address.'); + return false; + } + + // Basic email format validation (HTML5 handles most of this) + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(newEmail)) { + e.preventDefault(); + alert('Please enter a valid email address.'); + return false; + } + + // Check for empty email + if (newEmail.length === 0) { + e.preventDefault(); + alert('Email address cannot be empty.'); + return false; + } + }); + + // Real-time validation feedback + const emailInput = emailForm.querySelector('input[type="email"]'); + if (emailInput) { + emailInput.addEventListener('blur', function() { + const currentEmail = emailInput.placeholder; + const newEmail = emailInput.value.trim(); + + if (newEmail === currentEmail && newEmail.length > 0) { + emailInput.style.borderColor = '#ff6b6b'; + emailInput.title = 'This is your current email address'; + } else { + emailInput.style.borderColor = ''; + emailInput.title = ''; + } + }); + } + } +}); \ No newline at end of file diff --git a/front/assets/js/project_onboarding/new/components/project_status.tsx b/front/assets/js/project_onboarding/new/components/project_status.tsx index becfd3946..75cd53e41 100644 --- a/front/assets/js/project_onboarding/new/components/project_status.tsx +++ b/front/assets/js/project_onboarding/new/components/project_status.tsx @@ -76,7 +76,8 @@ export const ProjectStatus = ({ const [connectionData, setConnectionData] = useState(null); const { state } = useContext(stores.Create.Repository.Context); - const onSkipOnboarding = () => { + const onSkipOnboarding = (e: Event) => { + e.preventDefault(); // Use props first, then fallback to context state const skipUrl = skipOnboardingUrl || state.skipOnboardingUrl; const projUrl = projectUrl || (state.createdProjectName ? `/projects/${state.createdProjectName}` : undefined); @@ -181,7 +182,7 @@ export const ProjectStatus = ({ {(skipOnboardingUrl || state.skipOnboardingUrl) && (projectUrl || state.createdProjectName) && ( { e.preventDefault(); onSkipOnboarding(); }} + onClick={(e) => onSkipOnboarding}} className="f6 link dim gray underline" title="Skip the onboarding process and go directly to the project (for advanced users)" > diff --git a/front/assets/js/project_onboarding/new/pages/workflow_setup/existing_configuration.tsx b/front/assets/js/project_onboarding/new/pages/workflow_setup/existing_configuration.tsx index f15e89087..e917c1013 100644 --- a/front/assets/js/project_onboarding/new/pages/workflow_setup/existing_configuration.tsx +++ b/front/assets/js/project_onboarding/new/pages/workflow_setup/existing_configuration.tsx @@ -17,7 +17,8 @@ export const ExistingConfiguration = () => { dispatch([`SET_CURRENT`, `setup-workflow`]); }, []); - const handleUseExisting = () => { + const onHandleUseExisting = (e: Event) => { + e.preventDefault(); void handleSkipOnboarding({ skipOnboardingUrl: configState.skipOnboardingUrl, csrfToken: configState.csrfToken, @@ -25,7 +26,8 @@ export const ExistingConfiguration = () => { }); }; - const handleCreateNew = () => { + const onHandleCreateNew = (e: Event) => { + e.preventDefault(); navigate(`/environment`); }; @@ -52,7 +54,7 @@ export const ExistingConfiguration = () => {
{ e.preventDefault(); handleUseExisting(); }} + onClick={onHandleUseExisting} className="db f4 mb1" > I will use the existing configuration @@ -69,7 +71,7 @@ export const ExistingConfiguration = () => {
{ e.preventDefault(); handleCreateNew(); }} + onClick={onHandleCreateNew} className="db f4 mb1" > I want to configure this project from scratch diff --git a/front/assets/js/project_onboarding/new/stores/create/repository.ts b/front/assets/js/project_onboarding/new/stores/create/repository.ts index 5aceb2616..2952314da 100644 --- a/front/assets/js/project_onboarding/new/stores/create/repository.ts +++ b/front/assets/js/project_onboarding/new/stores/create/repository.ts @@ -59,7 +59,7 @@ type RepositoryAction = | { type: `SET_PROJECT_CHECK_URL`, payload: string, } | { type: `SET_REPO_CONNECTION_URL`, payload: string, } | { type: `SET_CREATED_PROJECT_NAME`, payload: string, } - | { type: `SET_SKIP_ONBOARDING`, payload: string, } + | { type: `SET_SKIP_ONBOARDING_URL`, payload: string, } | { type: `UPDATE_PROJECT_STATUS`; payload: { steps: Array<{ id: string, label: string, completed: boolean, }>; @@ -169,7 +169,7 @@ function repositoryReducer(state: RepositoryState, action: RepositoryAction): Re ...state, createdProjectName: action.payload }; - case `SET_SKIP_ONBOARDING`: + case `SET_SKIP_ONBOARDING_URL`: return { ...state, skipOnboardingUrl: action.payload @@ -364,7 +364,7 @@ export function useRepositoryStore(configState: stores.Config.State) { } if (data.project_name) { dispatch({ type: `SET_CREATED_PROJECT_NAME`, payload: data.project_name }); - dispatch({ type: `SET_SKIP_ONBOARDING`, payload: data.skip_project_onboarding }); + dispatch({ type: `SET_SKIP_ONBOARDING_URL`, payload: data.skip_project_onboarding_url }); } void checkProjectStatus(data.check_url as string); } diff --git a/front/lib/front_web/controllers/project_onboarding_controller.ex b/front/lib/front_web/controllers/project_onboarding_controller.ex index f596e5180..23bdfcbb7 100644 --- a/front/lib/front_web/controllers/project_onboarding_controller.ex +++ b/front/lib/front_web/controllers/project_onboarding_controller.ex @@ -351,7 +351,7 @@ defmodule FrontWeb.ProjectOnboardingController do repo_connection_url: project_onboarding_path(conn, :project_repository_status, project.name), project_name: project.name, - skip_project_onboarding: project_onboarding_path(conn, :skip_onboarding, project.name) + skip_project_onboarding_url: project_onboarding_path(conn, :skip_onboarding, project.name) }) else conn From e018f75d5679cff5806795bf9a7732807abeedab Mon Sep 17 00:00:00 2001 From: Amir Hasanbasic Date: Mon, 8 Sep 2025 11:51:56 +0200 Subject: [PATCH 3/5] fix(front): ts typo --- .../js/project_onboarding/new/components/project_status.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/assets/js/project_onboarding/new/components/project_status.tsx b/front/assets/js/project_onboarding/new/components/project_status.tsx index 75cd53e41..a38389691 100644 --- a/front/assets/js/project_onboarding/new/components/project_status.tsx +++ b/front/assets/js/project_onboarding/new/components/project_status.tsx @@ -182,7 +182,7 @@ export const ProjectStatus = ({ {(skipOnboardingUrl || state.skipOnboardingUrl) && (projectUrl || state.createdProjectName) && ( onSkipOnboarding}} + onClick={onSkipOnboarding} className="f6 link dim gray underline" title="Skip the onboarding process and go directly to the project (for advanced users)" > From 67d861fd1f56b0e5e28602fc0f87b288c09db7dc Mon Sep 17 00:00:00 2001 From: Amir Hasanbasic Date: Mon, 8 Sep 2025 12:02:12 +0200 Subject: [PATCH 4/5] fix(front): mix format --- .../lib/front_web/controllers/project_onboarding_controller.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/front/lib/front_web/controllers/project_onboarding_controller.ex b/front/lib/front_web/controllers/project_onboarding_controller.ex index e6fe79d92..69146fd18 100644 --- a/front/lib/front_web/controllers/project_onboarding_controller.ex +++ b/front/lib/front_web/controllers/project_onboarding_controller.ex @@ -351,7 +351,8 @@ defmodule FrontWeb.ProjectOnboardingController do repo_connection_url: project_onboarding_path(conn, :project_repository_status, project.name), project_name: project.name, - skip_project_onboarding_url: project_onboarding_path(conn, :skip_onboarding, project.name) + skip_project_onboarding_url: + project_onboarding_path(conn, :skip_onboarding, project.name) }) else conn From 996b318f0f4f65263867226b25c5487d2ffaf69f Mon Sep 17 00:00:00 2001 From: Amir Hasanbasic Date: Mon, 8 Sep 2025 17:53:17 +0200 Subject: [PATCH 5/5] fix(front): remove anonymous function for preventing default behaviour --- .../new/pages/workflow_setup/project_environment.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/front/assets/js/project_onboarding/new/pages/workflow_setup/project_environment.tsx b/front/assets/js/project_onboarding/new/pages/workflow_setup/project_environment.tsx index c72e0e2b9..4aeb276f3 100644 --- a/front/assets/js/project_onboarding/new/pages/workflow_setup/project_environment.tsx +++ b/front/assets/js/project_onboarding/new/pages/workflow_setup/project_environment.tsx @@ -96,7 +96,9 @@ export const Projectenvironment = () => { } }; - const onSkipOnboarding = () => { + const onSkipOnboarding = (e: Event) => { + e.preventDefault(); + void handleSkipOnboarding({ skipOnboardingUrl: configState.skipOnboardingUrl, csrfToken: configState.csrfToken, @@ -217,7 +219,7 @@ export const Projectenvironment = () => {

Next, we'll define your build steps and workflow. or you can

{ e.preventDefault(); onSkipOnboarding(); }} + onClick={onSkipOnboarding} className="f6 link dim gray underline" title="Skip the onboarding process and go directly to the project" >