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 103df2d41..a38389691 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,27 @@ export const ProjectStatus = ({ nextScreenUrl, repoConnectionUrl, csrfToken, + skipOnboardingUrl, + projectUrl, }: ProjectStatusProps) => { const [connectionData, setConnectionData] = useState(null); const { state } = useContext(stores.Create.Repository.Context); + 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); + + if (skipUrl && projUrl) { + void handleSkipOnboarding({ + skipOnboardingUrl: skipUrl, + csrfToken, + projectUrl: projUrl + }); + } + }; + const fetchConnectionData = async () => { if (isComplete && repoConnectionUrl) { try { @@ -157,7 +177,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) && ( + + 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..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 @@ -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,36 +17,17 @@ 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 onHandleUseExisting = (e: Event) => { + e.preventDefault(); + void handleSkipOnboarding({ + skipOnboardingUrl: configState.skipOnboardingUrl, + csrfToken: configState.csrfToken, + projectUrl: configState.projectUrl + }); }; - const handleCreateNew = () => { + const onHandleCreateNew = (e: Event) => { + e.preventDefault(); navigate(`/environment`); }; @@ -75,7 +54,7 @@ export const ExistingConfiguration = () => {
{ e.preventDefault(); void handleUseExisting(); }} + onClick={onHandleUseExisting} className="db f4 mb1" > I will use the existing configuration @@ -92,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/pages/workflow_setup/project_environment.tsx b/front/assets/js/project_onboarding/new/pages/workflow_setup/project_environment.tsx index 859394322..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 @@ -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,16 @@ export const Projectenvironment = () => { } }; + const onSkipOnboarding = (e: Event) => { + e.preventDefault(); + + void handleSkipOnboarding({ + skipOnboardingUrl: configState.skipOnboardingUrl, + csrfToken: configState.csrfToken, + projectUrl: configState.projectUrl + }); + }; + return (
@@ -206,7 +215,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_URL`: + 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_URL`, payload: data.skip_project_onboarding_url }); + } 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 d2ef1c770..69146fd18 100644 --- a/front/lib/front_web/controllers/project_onboarding_controller.ex +++ b/front/lib/front_web/controllers/project_onboarding_controller.ex @@ -350,7 +350,9 @@ 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_url: + project_onboarding_path(conn, :skip_onboarding, project.name) }) else conn