Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions front/assets/js/account/profile_management.js
Original file line number Diff line number Diff line change
@@ -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 = '';
}
});
}
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,6 +22,8 @@ interface ProjectStatusProps {
nextScreenUrl?: string;
repoConnectionUrl?: string;
csrfToken: string;
skipOnboardingUrl?: string;
projectUrl?: string;
}

interface ConnectionData {
Expand Down Expand Up @@ -67,10 +70,27 @@ export const ProjectStatus = ({
nextScreenUrl,
repoConnectionUrl,
csrfToken,
skipOnboardingUrl,
projectUrl,
}: ProjectStatusProps) => {
const [connectionData, setConnectionData] = useState<ConnectionData | null>(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 {
Expand Down Expand Up @@ -157,7 +177,19 @@ export const ProjectStatus = ({
)}
{isComplete && nextScreenUrl && (
<div className="flex justify-between items-center mt4">
<p className="f6 gray mb0">Next, we&apos;ll configure your build environment settings.</p>
<div className="flex items-center">
<p className="f6 gray mb0 mr3">Next, we&apos;ll configure your build environment settings.</p>
{(skipOnboardingUrl || state.skipOnboardingUrl) && (projectUrl || state.createdProjectName) && (
<a
href="#"
onClick={onSkipOnboarding}
className="f6 link dim gray underline"
title="Skip the onboarding process and go directly to the project (for advanced users)"
>
Skip onboarding
</a>
)}
</div>
<a href={nextScreenUrl} className="btn btn-primary">Continue</a>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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`);
};

Expand All @@ -75,7 +54,7 @@ export const ExistingConfiguration = () => {
<div className="mv3">
<a
href="#"
onClick={(e) => { e.preventDefault(); void handleUseExisting(); }}
onClick={onHandleUseExisting}
className="db f4 mb1"
>
I will use the existing configuration
Expand All @@ -92,7 +71,7 @@ export const ExistingConfiguration = () => {
<div className="mv3">
<a
href="#"
onClick={(e) => { e.preventDefault(); handleCreateNew(); }}
onClick={onHandleCreateNew}
className="db f4 mb1"
>
I want to configure this project from scratch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 (
<div className="pt3 pb5">
<div className="relative mw8 center">
Expand Down Expand Up @@ -206,7 +215,17 @@ export const Projectenvironment = () => {
</div>
<div className="mt3">
<div className="flex justify-between items-center">
<p className="f6 gray mb0">Next, we&apos;ll define your build steps and workflow.</p>
<div className="flex items-center">
<p className="f6 gray mb0 mr1">Next, we&apos;ll define your build steps and workflow. or you can </p>
<a
href="#"
onClick={onSkipOnboarding}
className="f6 link dim gray underline"
title="Skip the onboarding process and go directly to the project"
>
skip onboarding
</a>
</div>
<Tippy
placement="top"
content="Select an agent type to continue"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export interface State {
userProfileUrl?: string;
setupIntegrationUrl: string;
csrfToken: string;
skipOnboardingUrl?: string;
projectUrl?: string;
scopeUrls?: {
github_oauth_token?: Array<{
url: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ interface RepositoryState {
nextIterationName?: string;
isCreatingProject: boolean;
repoConnectionUrl?: string;
createdProjectName?: string;
skipOnboardingUrl?: string;
projectCreationStatus: {
checkUrl?: string;
steps: Array<{
Expand Down Expand Up @@ -56,6 +58,8 @@ type RepositoryAction =
| { type: `START_PROJECT_CREATION`, }
| { 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_URL`, payload: string, }
| {
type: `UPDATE_PROJECT_STATUS`; payload: {
steps: Array<{ id: string, label: string, completed: boolean, }>;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down
42 changes: 42 additions & 0 deletions front/assets/js/project_onboarding/new/utils/skip_onboarding.ts
Original file line number Diff line number Diff line change
@@ -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<void> => {
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;
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down