diff --git a/backend/app/controllers/surveyors_controller.rb b/backend/app/controllers/surveyors_controller.rb index 63d7b16f..7a49b7f4 100644 --- a/backend/app/controllers/surveyors_controller.rb +++ b/backend/app/controllers/surveyors_controller.rb @@ -67,6 +67,6 @@ def surveyor_params end def search_params - params.permit(:city, :zipcode, :state, :role, :status) + params.permit(:city, :zipcode, :state, :role, :status, :email) end end diff --git a/frontend/front/src/api/apiSlice.js b/frontend/front/src/api/apiSlice.js index ae7cdc34..be55317a 100644 --- a/frontend/front/src/api/apiSlice.js +++ b/frontend/front/src/api/apiSlice.js @@ -62,10 +62,10 @@ export const apiSlice = createApi({ invalidatesTags: ["Home"], }), updateHome: builder.mutation({ - query: (home) => ({ - url: `/homes/${home.id}`, + query: ({ id, body }) => ({ + url: `homes/${id}`, method: "PUT", - body: home, + body, }), invalidatesTags: (result, error, arg) => [{ type: "Home", id: arg.id }], }), @@ -99,6 +99,11 @@ export const apiSlice = createApi({ query: (id) => `/surveyors/${id}`, providesTags: (result, error, arg) => [{ type: "Surveyor", id: arg }], }), + getSurveyorByEmail: builder.query({ + query: (email) => `/surveyors?email=${email}`, + transformResponse: (res) => res ? res[0] : null, + providesTags: (result, error, arg) => [{ type: "Surveyor", id: arg }], + }), createSurveyor: builder.mutation({ query: (surveyor) => ({ url: `/surveyors`, @@ -108,10 +113,10 @@ export const apiSlice = createApi({ invalidatesTags: ["Surveyor"], }), updateSurveyor: builder.mutation({ - query: (surveyor) => ({ - url: `/surveyors/${surveyor.id}`, + query: ({ id, body }) => ({ + url: `/surveyors/${id}`, method: "PUT", - body: surveyor, + body, }), invalidatesTags: (result, error, arg) => [ { type: "Surveyor", id: arg.id }, @@ -151,10 +156,10 @@ export const apiSlice = createApi({ invalidatesTags: ["Survey"], }), updateSurvey: builder.mutation({ - query: (survey) => ({ - url: `/surveys/${survey.id}`, + query: ({ id, body }) => ({ + url: `/surveys/${id}`, method: "PUT", - body: survey, + body, }), invalidatesTags: (result, error, arg) => [{ type: "Survey", id: arg.id }], }), @@ -189,13 +194,11 @@ export const apiSlice = createApi({ invalidatesTags: ["SurveyVisit"], }), updateSurveyVisit: builder.mutation({ - query: ({ id, body }) => { - return { - url: `/survey_visits/${id}`, - method: "PUT", - body, - }; - }, + query: ({ id, body }) => ({ + url: `survey_visits/${id}`, + method: "PUT", + body, + }), invalidatesTags: (result, error, arg) => [ { type: "SurveyVisit", id: arg.id }, ], @@ -231,13 +234,11 @@ export const apiSlice = createApi({ invalidatesTags: ["SurveyResponse"], }), updateSurveyResponse: builder.mutation({ - query: ({ id, body }) => { - return { - url: `/survey_responses/${id}`, - method: "PUT", - body, - }; - }, + query: ({ id, body }) => ({ + url: `/survey_responses/${id}`, + method: "PUT", + body, + }), invalidatesTags: (result, error, arg) => [ { type: "SurveyResponse", id: arg.id }, ], @@ -271,13 +272,11 @@ export const apiSlice = createApi({ invalidatesTags: ["SurveyAnswer"], }), updateSurveyAnswer: builder.mutation({ - query: ({ id, body }) => { - return { - url: `/survey_answers/${id}`, - method: "PUT", - body, - }; - }, + query: ({ id, body }) => ({ + url: `/survey_answers/${id}`, + method: "PUT", + body, + }), invalidatesTags: (result, error, arg) => [ { type: "SurveyAnswer", id: arg.id }, ], @@ -313,10 +312,10 @@ export const apiSlice = createApi({ invalidatesTags: ["Assignment"], }), updateAssignment: builder.mutation({ - query: (assignment) => ({ - url: `/assignments/${assignment.id}`, + query: ({ id, body }) => ({ + url: `/assignments/${id}`, method: "PUT", - body: assignment, + body, }), invalidatesTags: (result, error, arg) => [ { type: "Assignment", id: arg.id }, @@ -365,6 +364,7 @@ export const { useCreateSurveyorMutation, useGetSurveyorQuery, useGetSurveyorsQuery, + useGetSurveyorByEmailQuery, // useGetSurveyorsByAssignmentIdQuery, // Home diff --git a/frontend/front/src/components/SurveyComponent/HeatPumpPhoneField.js b/frontend/front/src/components/SurveyComponent/HeatPumpPhoneField.js index 04a3264c..2f7846d2 100644 --- a/frontend/front/src/components/SurveyComponent/HeatPumpPhoneField.js +++ b/frontend/front/src/components/SurveyComponent/HeatPumpPhoneField.js @@ -2,10 +2,16 @@ import React from "react"; import { Controller, useController } from "react-hook-form"; import { TextField } from "@mui/material"; -export const HeatPumpPhoneField = ({ name, control, label, disabled }) => { +export const HeatPumpPhoneField = ({ + name, + control, + label, + disabled, + ...props +}) => { const { formState } = useController({ name, control }); const mainFieldError = formState.errors[name]; - + return ( { )} /> diff --git a/frontend/front/src/components/SurveyComponent/HeatPumpTextField.js b/frontend/front/src/components/SurveyComponent/HeatPumpTextField.js index c40e5e7c..6c13281c 100644 --- a/frontend/front/src/components/SurveyComponent/HeatPumpTextField.js +++ b/frontend/front/src/components/SurveyComponent/HeatPumpTextField.js @@ -8,6 +8,7 @@ export const HeatPumpTextField = ({ label, disabled, required, + ...props }) => { const { formState } = useController({ name, control }); @@ -17,7 +18,7 @@ export const HeatPumpTextField = ({ control={control} rules={ required && { - required: { value: true, message: "This field is required!" }, + required: { value: true, message: required }, } } render={({ field }) => ( @@ -31,6 +32,7 @@ export const HeatPumpTextField = ({ } disabled={disabled} {...field} + {...props} /> )} /> diff --git a/frontend/front/src/features/account/accountSlice.js b/frontend/front/src/features/account/accountSlice.js deleted file mode 100644 index e926a513..00000000 --- a/frontend/front/src/features/account/accountSlice.js +++ /dev/null @@ -1,34 +0,0 @@ -import { createSlice } from "@reduxjs/toolkit"; - -const initialState = { - firstName: "John", - lastName: "Smith", - email: "jsmith@example.com", - address: "12345 John Smith Way", - phoneNumber: "1234567890", -}; - -const accountSlice = createSlice({ - name: "account", - initialState, - reducers: { - setAccount: (state, action) => { - state.firstName = action.payload.firstName; - state.lastName = action.payload.lastName; - state.email = action.payload.email; - state.address = action.payload.address; - state.phoneNumber = action.payload.phoneNumber; - }, - setLogOut: (state) => { - state.firstName = null; - state.lastName = null; - state.email = null; - state.address = null; - state.phoneNumber = null; - }, - }, -}); - -export const { setAccount, setLogOut } = accountSlice.actions; - -export default accountSlice.reducer; diff --git a/frontend/front/src/features/login/loginSlice.js b/frontend/front/src/features/login/loginSlice.js index 1b2a5786..fbca2835 100644 --- a/frontend/front/src/features/login/loginSlice.js +++ b/frontend/front/src/features/login/loginSlice.js @@ -7,9 +7,11 @@ import { createSelector, createSlice } from "@reduxjs/toolkit"; import { apiSlice } from "../../api/apiSlice"; +const initialState = { user: { id: null, email: null }, token: null }; + const loginSlice = createSlice({ name: "login", - initialState: { user: null, token: null }, + initialState, reducers: { setLoginInfo: (state, action) => { state.user = action.payload.user; @@ -21,8 +23,9 @@ const loginSlice = createSlice({ builder.addMatcher( apiSlice.endpoints.loginUser.matchFulfilled, (state, { payload, meta }) => { - const token = - meta.baseQueryMeta.response.headers.get(AUTHORIZATION_HEADER); + const token = meta.baseQueryMeta.response.headers.get( + AUTHORIZATION_HEADER + ); state.token = token; state.user = decodeJwt(token); if (token) { @@ -32,8 +35,7 @@ const loginSlice = createSlice({ ); // update state whenever user logs out successfully builder.addMatcher(apiSlice.endpoints.logoutUser.matchPending, (state) => { - state.user = null; - state.token = null; + state = initialState localStorage.removeItem(AUTH_TOKEN_LOCAL_STORAGE_KEY); }); }, diff --git a/frontend/front/src/pages/Admin/nav/Nav.js b/frontend/front/src/pages/Admin/nav/Nav.js index 14fda5c5..e779339a 100644 --- a/frontend/front/src/pages/Admin/nav/Nav.js +++ b/frontend/front/src/pages/Admin/nav/Nav.js @@ -8,12 +8,17 @@ import { useTheme, } from "@mui/material"; -import { Link } from "react-router-dom"; -import React from "react"; +import { Link, useNavigate } from "react-router-dom"; +import React, { useCallback } from "react"; import { useLogoutUserMutation } from "../../../api/apiSlice"; const Nav = () => { + const navigate = useNavigate(); const [logout] = useLogoutUserMutation(); + const handleLogout = useCallback(async () => { + await logout(); + navigate("/"); + }); // const { title } = useSelector((state) => state.nav); const theme = useTheme(); @@ -27,7 +32,7 @@ const Nav = () => { ADMIN - diff --git a/frontend/front/src/pages/Surveyor/account/Account.js b/frontend/front/src/pages/Surveyor/account/Account.js index 4707ac87..f94a6b40 100644 --- a/frontend/front/src/pages/Surveyor/account/Account.js +++ b/frontend/front/src/pages/Surveyor/account/Account.js @@ -3,42 +3,63 @@ import React from "react"; import AccountDetail from "./AccountDetail"; import { Link } from "react-router-dom"; import { useSelector } from "react-redux"; +import { selectCurrentUser } from "../../../features/login/loginSlice"; +import { useGetSurveyorByEmailQuery } from "../../../api/apiSlice"; +import Loader from "../../../components/Loader"; +import CustomSnackbar from "../../../components/CustomSnackbar"; const Account = () => { - const { firstName, lastName, email, address, phoneNumber } = useSelector( - (state) => state.account - ); + const { email } = useSelector(selectCurrentUser); + + const { + data: accountData, + isError: isAccountDataError, + isLoading: isAccountDataLoading, + } = useGetSurveyorByEmailQuery(email); + return ( - - - - - My Account - - - - - - - - - - - - - - - - - - - - + {isAccountDataLoading ? ( + + ) : isAccountDataError ? ( + + ) : ( + <> + + + + + My Account + + + + + + + + + + + + + + + + + + + + + + )} ); }; diff --git a/frontend/front/src/pages/Surveyor/account/edit/EditAccount.js b/frontend/front/src/pages/Surveyor/account/edit/EditAccount.js index 8834b592..e3c97cff 100644 --- a/frontend/front/src/pages/Surveyor/account/edit/EditAccount.js +++ b/frontend/front/src/pages/Surveyor/account/edit/EditAccount.js @@ -1,205 +1,191 @@ -import React from "react"; +import React, { useCallback } from "react"; import Button from "@mui/material/Button"; -import { Grid, Paper, TextField, Box } from "@mui/material"; +import { Grid, Paper, Box } from "@mui/material"; import { useForm } from "react-hook-form"; -import { useDispatch, useSelector } from "react-redux"; -import { setAccount } from "../../../../features/account/accountSlice"; +import { useSelector } from "react-redux"; import { useNavigate, Link } from "react-router-dom"; +import { selectCurrentUser } from "../../../../features/login/loginSlice"; +import { + useGetSurveyorQuery, + useUpdateSurveyorMutation, +} from "../../../../api/apiSlice"; +import Loader from "../../../../components/Loader"; +import CustomSnackbar from "../../../../components/CustomSnackbar"; +import { HeatPumpPhoneField } from "../../../../components/SurveyComponent/HeatPumpPhoneField"; +import { HeatPumpTextField } from "../../../../components/SurveyComponent/HeatPumpTextField"; + +// Styles +const verticalMargin = { margin: "10px 0" }; +const paperStyle = { + padding: 40, + display: "flex", + flexDirection: "column", + width: 280, + margin: "20px auto", +}; const EditAccount = () => { - const dispatch = useDispatch(); const navigate = useNavigate(); + // Get account data + const { id } = useSelector(selectCurrentUser); + // RTK Query const { - register, - handleSubmit, - formState: { errors }, - reset, - } = useForm(); - - const { firstName, lastName, email, address, phoneNumber } = useSelector( - (state) => state.account - ); - - const paperStyle = { - padding: 40, + data: { + firstname: firstName, + lastname: lastName, + email, + street_address: address, + phone: phoneNumber, + }, + isError: isAccountDataError, + isLoading: isAccountDataLoading, + } = useGetSurveyorQuery(id); - display: "flex", - flexDirection: "column", - width: 280, - margin: "20px auto", - }; + const [ + updateAccount, + { + data: accountUpdateResult, + isError: isUpdateAccountError, + isLoading: isUpdatingAccount, + }, + ] = useUpdateSurveyorMutation(); - const btnstyle = { margin: "10px 0" }; + // react-hook-form + const { handleSubmit, control, reset } = useForm({ + defaultValues: { + firstName, + lastName, + email, + address, + phoneNumber: phoneNumber, + }, + }); - const errorStyles = { - color: "rgb(239 68 68 / 1)", - fontSize: "0.875rem", - lineHeight: "1.25rem", - }; + // Action handling + const handleUpdateAccount = useCallback( + async (values) => { + const updatedAccount = await updateAccount({ + id, + body: { + surveyor: { + firstname: values.firstName, + lastname: values.lastName, + phone: values.phoneNumber, + email: values.email, + street_address: values.address, + }, + }, + }); + return updatedAccount; + }, + [updateAccount, id] + ); - async function EditAccountForms(values) { + async function editAccountForms(values) { if (!values) return; - - dispatch( - setAccount({ - firstName: values.firstName, - lastName: values.lastName, - email: values.email, - address: values.address, - phoneNumber: values.phoneNumber, - }) - ); - + handleUpdateAccount(values); reset(); - navigate("../account"); } return ( - - -

Update Account Details?

-
+ {isAccountDataLoading || isUpdatingAccount ? ( + + ) : isAccountDataError ? ( + + ) : isUpdateAccountError ? ( + + ) : ( + + +

Update Account Details?

+
-
- - {errors?.firstName?.message} - - {errors?.lastName?.message} - ()[\]\\.,;:\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,}))$/, - message: "Please enter a valid email", - }, - })} - /> - {errors?.email?.message} - - {errors?.address?.message} - - {errors?.phoneNumber?.message} + + + + + + - - - -
+ + + +
+ )}
); }; diff --git a/frontend/front/src/pages/Surveyor/nav/Nav.js b/frontend/front/src/pages/Surveyor/nav/Nav.js index a06296ed..6c13d2ae 100644 --- a/frontend/front/src/pages/Surveyor/nav/Nav.js +++ b/frontend/front/src/pages/Surveyor/nav/Nav.js @@ -1,12 +1,17 @@ import { AppBar, Box, Button, Toolbar, Typography } from "@mui/material"; import LeftDrawer from "./LeftDrawer"; -import { Link } from "react-router-dom"; -import React from "react"; +import { Link, useNavigate } from "react-router-dom"; +import React, { useCallback } from "react"; import { useLogoutUserMutation } from "../../../api/apiSlice"; const Nav = () => { + const navigate = useNavigate(); const [logout] = useLogoutUserMutation(); + const handleLogout = useCallback(async () => { + await logout(); + navigate("/"); + }); return ( @@ -35,7 +40,7 @@ const Nav = () => { - diff --git a/frontend/front/src/redux/store.js b/frontend/front/src/redux/store.js index eb5c9174..11d73fff 100644 --- a/frontend/front/src/redux/store.js +++ b/frontend/front/src/redux/store.js @@ -1,4 +1,3 @@ -import accountReducer from "../features/account/accountSlice"; import { apiSlice } from "../api/apiSlice"; import { configureStore } from "@reduxjs/toolkit"; import contactReducer from "../features/contact/contactSlice"; @@ -9,7 +8,6 @@ import surveyorReducer from "../features/surveyor/surveyorSlice"; export const createStore = (options) => configureStore({ reducer: { - account: accountReducer, contact: contactReducer, nav: navReducer, login: loginReducer,