From 4ebcbc6e04a9d90272f635c153436699f0ee09db Mon Sep 17 00:00:00 2001 From: Keyur Doshi Date: Mon, 10 Nov 2025 03:21:01 +0530 Subject: [PATCH 1/2] Mech signup from UI --- services/web/src/actions/userActions.ts | 15 ++++++ services/web/src/components/signup/signup.css | 39 ++++++++++++++ services/web/src/components/signup/signup.tsx | 53 ++++++++++++++++++- services/web/src/constants/APIConstant.ts | 1 + services/web/src/constants/actionTypes.ts | 1 + services/web/src/constants/constants.ts | 1 + services/web/src/constants/messages.ts | 1 + services/web/src/containers/signup/signup.js | 10 +++- services/web/src/sagas/userSaga.ts | 48 +++++++++++++++++ 9 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 services/web/src/components/signup/signup.css diff --git a/services/web/src/actions/userActions.ts b/services/web/src/actions/userActions.ts index ef5c1003..7de2424d 100644 --- a/services/web/src/actions/userActions.ts +++ b/services/web/src/actions/userActions.ts @@ -39,6 +39,7 @@ interface SignUpPayload extends ActionPayload { name: string; email: string; number: string; + mechanic_code?: string; password: string; } @@ -108,6 +109,20 @@ export const signUpUserAction = ({ }; }; +export const signUpMechanicAction = ({ + name, + email, + number, + mechanic_code, + password, + callback, +}: SignUpPayload) => { + return { + type: actionTypes.SIGN_UP_MECHANIC, + payload: { name, email, number, mechanic_code, password, callback }, + }; +}; + // clear store data and local storage and log user out export const logOutUserAction = ({ callback }: ActionPayload) => { return { diff --git a/services/web/src/components/signup/signup.css b/services/web/src/components/signup/signup.css new file mode 100644 index 00000000..7dcf6e16 --- /dev/null +++ b/services/web/src/components/signup/signup.css @@ -0,0 +1,39 @@ +.user-type-toggle { + display: flex; + gap: 0; + margin-bottom: var(--spacing-xl); + background: var(--bg-primary); + border-radius: 50px; + padding: 4px; + border: 2px solid var(--secondary-color); + box-shadow: var(--shadow-light); + } + + .toggle-button { + flex: 1; + padding: 12px 24px; + border: none; + background: transparent; + color: var(--secondary-color); + font-size: var(--font-size-md); + font-weight: var(--font-weight-semibold); + border-radius: 50px; + cursor: pointer; + transition: all var(--transition-normal); + position: relative; + z-index: 1; + } + + .toggle-button:hover { + background: rgba(114, 46, 209, 0.1); + } + + .toggle-button.active { + background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 100%); + color: var(--text-inverse); + box-shadow: var(--shadow-medium); + } + + .toggle-button:focus { + outline: none; + } \ No newline at end of file diff --git a/services/web/src/components/signup/signup.tsx b/services/web/src/components/signup/signup.tsx index 5a64027c..deff22f9 100644 --- a/services/web/src/components/signup/signup.tsx +++ b/services/web/src/components/signup/signup.tsx @@ -14,7 +14,7 @@ */ import { Button, Form, Input, Card } from "antd"; -import React from "react"; +import React, { useState } from "react"; import { useNavigate } from "react-router-dom"; import { EMAIL_REQUIRED, @@ -24,36 +24,71 @@ import { CONFIRM_PASSWORD, PASSWORD_DO_NOT_MATCH, INVALID_PASSWORD, + MECHANIC_CODE_REQUIRED, } from "../../constants/messages"; import { EMAIL_VALIDATION, NAME_VALIDATION, PHONE_VALIDATION, PASSWORD_VALIDATION, + MECHANIC_CODE_VALIDATION, } from "../../constants/constants"; +import "./signup.css"; interface SignupProps { hasErrored: boolean; errorMessage: string; onFinish: (values: any) => void; + onMechanicFinish: (values: any) => void; } +type UserType = "user" | "mechanic"; + const Signup: React.FC = ({ hasErrored = false, errorMessage = "", onFinish, + onMechanicFinish, }) => { const navigate = useNavigate(); + const [userType, setUserType] = useState("user"); + + const handleUserTypeChange = (type: UserType) => { + setUserType(type); + }; + const handleFormSubmit = (values: any) => { + if (userType === "user") { + onFinish(values); + } else { + onMechanicFinish(values); + } + }; return (
+
+ + +
= ({ > + {userType === "mechanic" && ( + + + + )} ()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; export const PHONE_VALIDATION: RegExp = /^(\+\d{1,2}\s?)?1?-?\.?\s?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/; +export const MECHANIC_CODE_VALIDATION: RegExp = /^MECH_[A-Za-z]+$/; export const PASSWORD_VALIDATION: RegExp = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[#$@!%&*?])[A-Za-z\d#$@!%&*?]{8,16}$/; export const NAME_VALIDATION: RegExp = /^[a-zA-Z ]+$/; diff --git a/services/web/src/constants/messages.ts b/services/web/src/constants/messages.ts index 9f398f3c..43b0eb42 100644 --- a/services/web/src/constants/messages.ts +++ b/services/web/src/constants/messages.ts @@ -21,6 +21,7 @@ export const EMAIL_REQUIRED: string = "Please enter a valid email!"; export const PHONE_NO_REQUIRED: string = "Please enter phone number"; export const INVALID_PHONE: string = "Contact number should only contain digits, (, ), + or spaces."; +export const MECHANIC_CODE_REQUIRED: string = "Please enter a valid mechanic code starting with 'MECH_'!"; export const PASSWORD_REQUIRED: string = "Please enter your password"; export const INVALID_PASSWORD: React.ReactElement = React.createElement( "span", diff --git a/services/web/src/containers/signup/signup.js b/services/web/src/containers/signup/signup.js index 56020a09..e5b17ae2 100644 --- a/services/web/src/containers/signup/signup.js +++ b/services/web/src/containers/signup/signup.js @@ -20,12 +20,12 @@ import { connect } from "react-redux"; import { useNavigate } from "react-router-dom"; import Signup from "../../components/signup/signup"; -import { signUpUserAction } from "../../actions/userActions"; +import { signUpUserAction, signUpMechanicAction } from "../../actions/userActions"; import responseTypes from "../../constants/responseTypes"; import { SUCCESS_MESSAGE } from "../../constants/messages"; const SignupContainer = (props) => { - const { signUpUser } = props; + const { signUpUser, signUpMechanic } = props; const navigate = useNavigate(); const [hasErrored, setHasErrored] = useState(false); @@ -47,22 +47,28 @@ const SignupContainer = (props) => { const onFinish = (values) => { signUpUser({ ...values, callback }); }; + const onMechanicFinish = (values) => { + signUpMechanic({ ...values, callback }); + }; return ( ); }; const mapDispatchToProps = { signUpUser: signUpUserAction, + signUpMechanic: signUpMechanicAction, }; SignupContainer.propTypes = { signUpUser: PropTypes.func, + signUpMechanic: PropTypes.func, }; export default connect(null, mapDispatchToProps)(SignupContainer); diff --git a/services/web/src/sagas/userSaga.ts b/services/web/src/sagas/userSaga.ts index b1629cb9..933e9a0e 100644 --- a/services/web/src/sagas/userSaga.ts +++ b/services/web/src/sagas/userSaga.ts @@ -258,6 +258,53 @@ export function* signUp(action: MyAction): Generator { } } +/** + * Request for new mechanic signup + + * @payload {string} payload.name - User name + * @payload {string} payload.email - User email + * @payload {string} payload.number - User number + * @payload {string} payload.mechanic_code - User mechanic code + * @payload {string} payload.password - User password + * @payload {Function} payload.callback - Callback method + */ +export function* signUpMechanic(action: MyAction): Generator { + const { name, email, number, mechanic_code, password, callback } = action.payload; + let receivedResponse: Partial = {}; + try { + yield put({ type: actionTypes.FETCHING_DATA }); + + const postUrl = APIService.WORKSHOP_SERVICE + requestURLS.SIGNUP_MECHANIC; + const headers = { + "Content-Type": "application/json", + }; + // remove special chars from number + let cleanedNumber = number.replace(/[^0-9+]/g, ""); + console.log("number", cleanedNumber); + const responseJSON = yield fetch(postUrl, { + headers, + method: "POST", + body: JSON.stringify({ + name: name, + email: email, + number: cleanedNumber, + mechanic_code: mechanic_code, + password: password, + }), + }).then((response: Response) => { + receivedResponse = response; + return response.json(); + }); + + yield put({ type: actionTypes.FETCHED_DATA, payload: receivedResponse }); + if (receivedResponse.ok) callback(responseTypes.SUCCESS, responseJSON.message); + else callback(responseTypes.FAILURE, responseJSON.message || SIGN_UP_FAILED); + } catch (e) { + yield put({ type: actionTypes.FETCHED_DATA, payload: receivedResponse }); + callback(responseTypes.FAILURE, SIGN_UP_FAILED); + } +} + /** * Send OTP for forgot password @@ -548,6 +595,7 @@ export function* userActionWatcher() { yield takeLatest(actionTypes.VALIDATE_ACCESS_TOKEN, validateAccessToken); yield takeLatest(actionTypes.UNLOCK_USER, unlock); yield takeLatest(actionTypes.SIGN_UP, signUp); + yield takeLatest(actionTypes.SIGN_UP_MECHANIC, signUpMechanic); yield takeLatest(actionTypes.VERIFY_OTP, verifyOTP); yield takeLatest(actionTypes.FORGOT_PASSWORD, forgotPassword); yield takeLatest(actionTypes.RESET_PASSWORD, resetPassword); From 88ba927dd909fa44f23f6f8901832fcbaba1e5cf Mon Sep 17 00:00:00 2001 From: Keyur Doshi Date: Mon, 10 Nov 2025 03:23:44 +0530 Subject: [PATCH 2/2] lint fix --- services/web/src/components/signup/signup.tsx | 32 +++++++++---------- services/web/src/constants/messages.ts | 3 +- services/web/src/containers/signup/signup.js | 5 ++- services/web/src/sagas/userSaga.ts | 9 ++++-- 4 files changed, 28 insertions(+), 21 deletions(-) diff --git a/services/web/src/components/signup/signup.tsx b/services/web/src/components/signup/signup.tsx index deff22f9..5e8649fe 100644 --- a/services/web/src/components/signup/signup.tsx +++ b/services/web/src/components/signup/signup.tsx @@ -67,22 +67,22 @@ const Signup: React.FC = ({ return (
-
- - -
+
+ + +
{ * @payload {Function} payload.callback - Callback method */ export function* signUpMechanic(action: MyAction): Generator { - const { name, email, number, mechanic_code, password, callback } = action.payload; + const { name, email, number, mechanic_code, password, callback } = + action.payload; let receivedResponse: Partial = {}; try { yield put({ type: actionTypes.FETCHING_DATA }); @@ -297,8 +298,10 @@ export function* signUpMechanic(action: MyAction): Generator { }); yield put({ type: actionTypes.FETCHED_DATA, payload: receivedResponse }); - if (receivedResponse.ok) callback(responseTypes.SUCCESS, responseJSON.message); - else callback(responseTypes.FAILURE, responseJSON.message || SIGN_UP_FAILED); + if (receivedResponse.ok) + callback(responseTypes.SUCCESS, responseJSON.message); + else + callback(responseTypes.FAILURE, responseJSON.message || SIGN_UP_FAILED); } catch (e) { yield put({ type: actionTypes.FETCHED_DATA, payload: receivedResponse }); callback(responseTypes.FAILURE, SIGN_UP_FAILED);