diff --git a/docs/challengeSolutions.md b/docs/challengeSolutions.md index 9c606fea..c512a623 100644 --- a/docs/challengeSolutions.md +++ b/docs/challengeSolutions.md @@ -14,7 +14,7 @@ Verify the ports of the container by running the following command : `docker ps` #### Detailed solution 1. Login to the application from http://localhost:8888/login -2. From the *Dashboard*, choose *Add a Vehicle* and add the vehicle by providing the VIN and pincode received in Mailhog mailbox after Signup or by reinitiating from *Dashboard* page. +2. From the *Dashboard*, choose *Add Vehicle* and add the vehicle by providing the VIN and pincode received in Mailhog mailbox after Signup or by reinitiating from *Dashboard* page. 3. After the vehicle details are verified successful, the vehicle will get added and then be populated in the *Dashboard* page. 4. Observe the request sent when we click *Refresh Location*. It can be seen that the endpoint is in the format `/identity/api/v2/vehicle//location`. 5. Sensitive information like latitude and longitude are provided back in the response for the endpoint. Send the request to *Repeater* for later purpose. diff --git a/services/identity/src/main/java/com/crapi/constant/UserMessage.java b/services/identity/src/main/java/com/crapi/constant/UserMessage.java index f1eead17..fa1c5c83 100644 --- a/services/identity/src/main/java/com/crapi/constant/UserMessage.java +++ b/services/identity/src/main/java/com/crapi/constant/UserMessage.java @@ -55,10 +55,10 @@ public class UserMessage { public static final String PASSWORD_GOT_RESET = "Password reset successful."; public static final String VEHICLE_MODEL_IS_NOT_AVAILABLE = "Sorry we don't have Vehicle model for this company. Please select other.."; - public static final String VEHICLE_SAVED_SUCCESSFULLY = "Vehicle save successfully.."; + public static final String VEHICLE_SAVED_SUCCESSFULLY = "Vehicle saved successfully!"; public static final String VEHICLE_NOT_FOUND = "Failed to get Vehicles"; public static final String VEHICLE_DETAILS_SENT_TO_EMAIL = - "Your newly purchased Vehicle Details have been sent to you email address. If you have used example.com email, check your email using the MailHog web portal. "; + "Your newly purchased vehicle details have been sent to you email address. Verify them to add your vehicle. [If you have used example.com email, check your email using the MailHog web portal]"; public static final String CHANGE_EMAIL_MESSAGE = "The token has been sent to your email. If you have used example.com email, check your email using the MailHog web portal. "; public static final String CHANGE_EMAIL_OLD_USEREMAIL_NOT_FOUND_MESSAGE = diff --git a/services/identity/src/main/java/com/crapi/controller/VehicleController.java b/services/identity/src/main/java/com/crapi/controller/VehicleController.java index 9d8ce88e..faae362b 100644 --- a/services/identity/src/main/java/com/crapi/controller/VehicleController.java +++ b/services/identity/src/main/java/com/crapi/controller/VehicleController.java @@ -40,6 +40,19 @@ public class VehicleController { @Autowired VehicleOwnershipService vehicleOwnershipService; + /** + * @param request + * @return response of success and failure message creates vehicle and sends details to user + */ + @PostMapping("/vehicle/register_vehicle") + public ResponseEntity registerVehicle(HttpServletRequest request) { + CRAPIResponse registerVehicleResponse = vehicleService.registerVehicle(request); + if (registerVehicleResponse != null && registerVehicleResponse.getStatus() == 200) { + return ResponseEntity.status(HttpStatus.OK).body(registerVehicleResponse); + } + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(registerVehicleResponse); + } + /** * @param vehicleDetails * @return response of success and failure message save vehicle Details for user in database diff --git a/services/identity/src/main/java/com/crapi/service/Impl/UserRegistrationServiceImpl.java b/services/identity/src/main/java/com/crapi/service/Impl/UserRegistrationServiceImpl.java index f0b19aee..5d73e62c 100644 --- a/services/identity/src/main/java/com/crapi/service/Impl/UserRegistrationServiceImpl.java +++ b/services/identity/src/main/java/com/crapi/service/Impl/UserRegistrationServiceImpl.java @@ -80,7 +80,7 @@ public CRAPIResponse registerUser(SignUpForm signUpRequest) { return new CRAPIResponse( UserMessage.NUMBER_ALREADY_REGISTERED + signUpRequest.getNumber(), 403); } - // check Number in database + // Check Email in database if (userRepository.existsByEmail(signUpRequest.getEmail())) { return new CRAPIResponse( UserMessage.EMAIL_ALREADY_REGISTERED + signUpRequest.getEmail(), 403); @@ -107,7 +107,7 @@ public CRAPIResponse registerUser(SignUpForm signUpRequest) { if (vehicleDetails != null) { smtpMailServer.sendMail( user.getEmail(), - MailBody.signupMailBody( + MailBody.newVehicleMailBody( vehicleDetails, (userDetails != null && userDetails.getName() != null ? userDetails.getName() diff --git a/services/identity/src/main/java/com/crapi/service/Impl/VehicleServiceImpl.java b/services/identity/src/main/java/com/crapi/service/Impl/VehicleServiceImpl.java index 9740d27b..a6a6d1fb 100644 --- a/services/identity/src/main/java/com/crapi/service/Impl/VehicleServiceImpl.java +++ b/services/identity/src/main/java/com/crapi/service/Impl/VehicleServiceImpl.java @@ -107,6 +107,35 @@ public VehicleDetails createVehicle() { 500); } + /** + * @param request + * @return CRAPIResponse after creating a new vehicle and sending details to current user + */ + @Transactional + @Override + public CRAPIResponse registerVehicle(HttpServletRequest request) { + User user = null; + UserDetails userDetails = null; + VehicleDetails vehicleDetails = null; + + user = userService.getUserFromToken(request); + if (user == null) { + return new CRAPIResponse(UserMessage.TOKEN_VERIFICATION_MISSING, 401); + } + userDetails = userDetailsRepository.findByUser_id(user.getId()); + vehicleDetails = createVehicle(); + if (vehicleDetails != null) { + smtpMailServer.sendMail( + user.getEmail(), + MailBody.newVehicleMailBody( + vehicleDetails, + (userDetails != null && userDetails.getName() != null ? userDetails.getName() : "")), + "Your New Vehicle In crAPI"); + return new CRAPIResponse(UserMessage.VEHICLE_DETAILS_SENT_TO_EMAIL, 200); + } + return new CRAPIResponse(UserMessage.INTERNAL_SERVER_ERROR, 500); + } + /** * @param request * @return list of vehicle of user @@ -219,7 +248,7 @@ public CRAPIResponse sendVehicleDetails(HttpServletRequest request) { } smtpMailServer.sendMail( user.getEmail(), - MailBody.signupMailBody( + MailBody.newVehicleMailBody( vehicleDetails, (userDetails != null && userDetails.getName() != null ? userDetails.getName() : "")), "Welcome to crAPI"); diff --git a/services/identity/src/main/java/com/crapi/service/VehicleService.java b/services/identity/src/main/java/com/crapi/service/VehicleService.java index 967247fc..c72f0ffb 100644 --- a/services/identity/src/main/java/com/crapi/service/VehicleService.java +++ b/services/identity/src/main/java/com/crapi/service/VehicleService.java @@ -28,6 +28,8 @@ public interface VehicleService { VehicleDetails createVehicle(); + CRAPIResponse registerVehicle(HttpServletRequest request); + List getVehicleDetails(HttpServletRequest request); VehicleLocationResponse getVehicleLocation(UUID carId); diff --git a/services/identity/src/main/java/com/crapi/utils/MailBody.java b/services/identity/src/main/java/com/crapi/utils/MailBody.java index 28090f5c..79267eb2 100644 --- a/services/identity/src/main/java/com/crapi/utils/MailBody.java +++ b/services/identity/src/main/java/com/crapi/utils/MailBody.java @@ -50,13 +50,13 @@ public static String otpMailBody(Otp otp) { * @param name * @return Mail body for user Signup */ - public static String signupMailBody(VehicleDetails vehicleDetails, String name) { + public static String newVehicleMailBody(VehicleDetails vehicleDetails, String name) { String msgBody = "" + "Hi " + name + "," - + "

We are glad to have you on-board. Your newly purchased vehiche details are provided below. Please add it on your crAPI dashboard.

" + + "

We are glad to have you on-board with a new vehicle. Your newly purchased vehicle details are provided below. Please add it on your crAPI dashboard.

" + "

Your vehicle information is VIN: " + vehicleDetails.getVin() + " and Pincode: " diff --git a/services/web/src/actions/vehicleActions.ts b/services/web/src/actions/vehicleActions.ts index 6392eef4..02af86f3 100644 --- a/services/web/src/actions/vehicleActions.ts +++ b/services/web/src/actions/vehicleActions.ts @@ -25,6 +25,19 @@ interface GetVehiclesPayload extends ActionPayload { email?: string; } +export const registerVehicleAction = ({ + callback, + accessToken, +}: ActionPayload) => { + return { + type: actionTypes.REGISTER_VEHICLE, + payload: { + accessToken, + callback, + }, + }; +}; + export const verifyVehicleAction = ({ callback, accessToken, diff --git a/services/web/src/components/dashboard/dashboard.tsx b/services/web/src/components/dashboard/dashboard.tsx index 4aff0641..5bc56dd1 100644 --- a/services/web/src/components/dashboard/dashboard.tsx +++ b/services/web/src/components/dashboard/dashboard.tsx @@ -202,20 +202,18 @@ const Dashboard: React.FC = ({ } - size="large" - onClick={handleVerifyVehicleClick} - key="verify-vehicle" - > - Add a Vehicle - , - ] - } + extra={[ + , + ]} /> diff --git a/services/web/src/components/verifyVehicle/verifyVehicle.css b/services/web/src/components/verifyVehicle/verifyVehicle.css new file mode 100644 index 00000000..d40ddf0e --- /dev/null +++ b/services/web/src/components/verifyVehicle/verifyVehicle.css @@ -0,0 +1,56 @@ + .add-vehicle-container .ant-row { + align-items: stretch; + gap: calc(var(--spacing-md) * 2) !important; + } + + .add-vehicle-card { + height: 100%; + width: 100%; + min-height: 320px; + display: flex; + flex-direction: column; + } + + .add-vehicle-card .ant-card-body { + flex: 1; + display: flex; + flex-direction: column; + padding: 16px; + } + + .vehicle-alert { + margin-top: 8px; + font-size: 12px; + } + + .vehicle-alert .ant-alert-message { + font-weight: 600; + margin-bottom: 4px; + } + + .vehicle-alert .ant-alert-description { + font-size: 12px; + line-height: 1.3; + } + + .verify-vehicle-form .ant-form-item { + margin-bottom: 12px; + } + + .verify-vehicle-form .ant-form-item:last-child { + margin-bottom: 0; + } + + @media (max-width: 768px) { + .add-vehicle-card { + min-height: auto; + } + + .add-vehicle-container .ant-col { + margin-bottom: 20px; + } + + .add-vehicle-container .ant-row { + gap: var(--spacing-md); + } + } \ No newline at end of file diff --git a/services/web/src/components/verifyVehicle/verifyVehicle.tsx b/services/web/src/components/verifyVehicle/verifyVehicle.tsx index ccc03616..a650d7dc 100644 --- a/services/web/src/components/verifyVehicle/verifyVehicle.tsx +++ b/services/web/src/components/verifyVehicle/verifyVehicle.tsx @@ -14,7 +14,7 @@ */ import React from "react"; -import { Button, Form, Card, Input } from "antd"; +import { Button, Form, Card, Input, Row, Col, Alert } from "antd"; import { PIN_CODE_REQUIRED, INVALID_PIN_CODE, @@ -22,62 +22,113 @@ import { INVALID_VIN, } from "../../constants/messages"; import { PIN_CODE_VALIDATION, VIN_VALIDATION } from "../../constants/constants"; +import "./verifyVehicle.css"; interface VerifyVehicleProps { hasErrored: boolean; errorMessage: string; onFinish: (values: any) => void; + onRegisterVehicle: () => void; + vehicleRegistered: number; + registrationMessage: string; + vehicles: any[]; } const VerifyVehicle: React.FC = ({ hasErrored, errorMessage, onFinish, + onRegisterVehicle, + vehicleRegistered, + registrationMessage, + vehicles, }) => { return ( -

- -
- - - - + + {vehicles.length > 0 && ( + + + + {registrationMessage && ( + + )} + + + )} + + + - - - - {hasErrored &&
{errorMessage}
} - -
-
-
+
+ + + + + + + + {hasErrored && ( +
{errorMessage}
+ )} + +
+
+ + +
); }; diff --git a/services/web/src/constants/APIConstant.ts b/services/web/src/constants/APIConstant.ts index f4036cb8..000d97e7 100644 --- a/services/web/src/constants/APIConstant.ts +++ b/services/web/src/constants/APIConstant.ts @@ -42,6 +42,7 @@ export const requestURLS: RequestURLSType = { FORGOT_PASSWORD: "api/auth/forget-password", VERIFY_OTP: "api/auth/v3/check-otp", LOGIN_TOKEN: "api/auth/v4.0/user/login-with-token", + REGISTER_VEHICLE: "api/v2/vehicle/register_vehicle", ADD_VEHICLE: "api/v2/vehicle/add_vehicle", GET_VEHICLES: "api/v2/vehicle/vehicles", RESEND_MAIL: "api/v2/vehicle/resend_email", diff --git a/services/web/src/constants/actionTypes.ts b/services/web/src/constants/actionTypes.ts index 04381374..8080dcfe 100644 --- a/services/web/src/constants/actionTypes.ts +++ b/services/web/src/constants/actionTypes.ts @@ -42,6 +42,7 @@ const actionTypes = { GET_VEHICLE_SERVICES: "GET_VEHICLE_SERVICES", GET_SERVICE_REPORT: "GET_SERVICE_REPORT", RESEND_MAIL: "RESEND_MAIL", + REGISTER_VEHICLE: "REGISTER_VEHICLE", VERIFY_VEHICLE: "VERIFY_VEHICLE", GET_VEHICLES: "GET_VEHICLES", FETCHED_VEHICLES: "FETCHED_VEHICLES", diff --git a/services/web/src/constants/messages.ts b/services/web/src/constants/messages.ts index c723b96b..9f398f3c 100644 --- a/services/web/src/constants/messages.ts +++ b/services/web/src/constants/messages.ts @@ -106,6 +106,8 @@ export const EMAIL_NOT_CHANGED: string = "Could not change email id"; export const NO_SERVICES: string = "Could not get mechanic details"; export const EMAIL_NOT_SENT: string = "Could not resend mail"; export const VEHICLE_NOT_ADDED: string = "Internal Server error! Wrong VIN!"; +export const VEHICLE_NOT_REGISTERED: string = + "Could not register vehicle. Try again!"; export const NO_VEHICLES: string = "Could not get vehicles"; export const NO_MECHANICS: string = "Could not get mechanic details"; export const SERVICE_REQUEST_SENT: string = diff --git a/services/web/src/containers/verifyVehicle/verifyVehicle.js b/services/web/src/containers/verifyVehicle/verifyVehicle.js index bab604c9..39ad5d84 100644 --- a/services/web/src/containers/verifyVehicle/verifyVehicle.js +++ b/services/web/src/containers/verifyVehicle/verifyVehicle.js @@ -18,7 +18,10 @@ import React from "react"; import PropTypes from "prop-types"; import { connect } from "react-redux"; import { Modal } from "antd"; -import { verifyVehicleAction } from "../../actions/vehicleActions"; +import { + registerVehicleAction, + verifyVehicleAction, +} from "../../actions/vehicleActions"; import VerifyVehicle from "../../components/verifyVehicle/verifyVehicle"; import responseTypes from "../../constants/responseTypes"; import { SUCCESS_MESSAGE } from "../../constants/messages"; @@ -26,12 +29,27 @@ import { useNavigate } from "react-router-dom"; const VerifyVehicleContainer = (props) => { const navigate = useNavigate(); - const { verifyVehicle } = props; + const { registerVehicle, verifyVehicle } = props; + const { accessToken, vehicles } = props; const [hasErrored, setHasErrored] = React.useState(false); const [errorMessage, setErrorMessage] = React.useState(""); + const [vehicleRegistered, setVehicleRegistered] = React.useState(0); + const [registrationMessage, setRegistrationMessage] = React.useState(""); - const { accessToken } = props; + const onRegisterVehicle = () => { + setVehicleRegistered(1); + const callback = (res, data) => { + if (res === responseTypes.SUCCESS) { + setVehicleRegistered(2); + setRegistrationMessage(data); + } else { + setVehicleRegistered(0); + setRegistrationMessage(data); + } + }; + registerVehicle({ callback, accessToken }); + }; const onFinish = (values) => { const callback = (res, data) => { @@ -58,21 +76,31 @@ const VerifyVehicleContainer = (props) => { onFinish={onFinish} hasErrored={hasErrored} errorMessage={errorMessage} + onRegisterVehicle={onRegisterVehicle} + vehicleRegistered={vehicleRegistered} + registrationMessage={registrationMessage} + vehicles={vehicles} /> ); }; -const mapStateToProps = ({ userReducer: { accessToken } }) => { - return { accessToken }; +const mapStateToProps = ({ + userReducer: { accessToken }, + vehicleReducer: { vehicles }, +}) => { + return { accessToken, vehicles }; }; const mapDispatchToProps = { + registerVehicle: registerVehicleAction, verifyVehicle: verifyVehicleAction, }; VerifyVehicleContainer.propTypes = { accessToken: PropTypes.string, + registerVehicle: PropTypes.func, verifyVehicle: PropTypes.func, + vehicles: PropTypes.array, }; export default connect( diff --git a/services/web/src/sagas/vehicleSaga.ts b/services/web/src/sagas/vehicleSaga.ts index c423d571..22680645 100644 --- a/services/web/src/sagas/vehicleSaga.ts +++ b/services/web/src/sagas/vehicleSaga.ts @@ -27,6 +27,7 @@ import { SERVICE_REQUEST_NOT_SENT, LOC_NOT_REFRESHED, NO_SERVICES, + VEHICLE_NOT_REGISTERED, } from "../constants/messages"; interface ReceivedResponse extends Response { @@ -70,6 +71,42 @@ export function* resendMail(action: MyAction): Generator { } } +/** + * register a new vehicle + * @payload { accessToken, callback } payload + * accessToken: access token of the user + * callback : callback method + */ +export function* registerVehicle(action: MyAction): Generator { + const { accessToken, callback } = action.payload; + let recievedResponse: ReceivedResponse = {} as ReceivedResponse; + try { + yield put({ type: actionTypes.FETCHING_DATA }); + const postUrl = APIService.IDENTITY_SERVICE + requestURLS.REGISTER_VEHICLE; + const headers = { + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, + }; + const responseJson = yield fetch(postUrl, { + headers, + method: "POST", + }).then((response: Response) => { + recievedResponse = response as ReceivedResponse; + return response.json(); + }); + + yield put({ type: actionTypes.FETCHED_DATA, payload: recievedResponse }); + if (recievedResponse.ok) { + callback(responseTypes.SUCCESS, responseJson.message); + } else { + callback(responseTypes.FAILURE, VEHICLE_NOT_REGISTERED); + } + } catch (e) { + yield put({ type: actionTypes.FETCHED_DATA, payload: recievedResponse }); + callback(responseTypes.FAILURE, VEHICLE_NOT_REGISTERED); + } +} + /** * verify vehicle details entered by user and add this vehicle to this uder * @payload { pincode, vehicleNumber, accessToken, callback} payload @@ -393,6 +430,7 @@ export function* getServiceReport(action: MyAction): Generator { export function* vehicleActionWatcher(): Generator { yield takeLatest(actionTypes.RESEND_MAIL, resendMail); + yield takeLatest(actionTypes.REGISTER_VEHICLE, registerVehicle); yield takeLatest(actionTypes.VERIFY_VEHICLE, verifyVehicle); yield takeLatest(actionTypes.GET_VEHICLES, getVehicles); yield takeLatest(actionTypes.GET_MECHANICS, getMechanics);