Merge branch 'develop' of https://gitlab.com/witel3/loan-facilities-expert into feature/passenger_boss_design

This commit is contained in:
2023-07-13 10:55:02 +03:30
15 changed files with 522 additions and 57 deletions

View File

@@ -23,7 +23,8 @@
"passenger-boss": "رئیس مسافر",
"transportation-assistance": "معاونت حمل و نقل",
"manager": "مدیر کل",
"change-password": "تغییر رمز عبور"
"change-password": "تغییر رمز عبور",
"edit-profile": "ویرایش پروفایل"
},
"Authorization": {
"typography_your_login_is_valid_and_you_do_not_need_to_login_again": "شما دسترسی لازم به این صفحه را دارید نیاز به ورود مجدد نیست.",
@@ -45,8 +46,13 @@
},
"Dashboard": {
"dashboard_page": "داشبورد",
<<<<<<< HEAD
"passenger_boss": "رئیس مسافر",
"change_password": "تغییر رمز عبور"
=======
"change_password": "تغییر رمز عبور",
"edit_profile": "ویرایش پروفایل"
>>>>>>> 135f6d1e2f3495abe68e3ca758870023dcd85d76
},
"MuiDatePicker": {
"date_picker_birthday": "تاریخ"
@@ -64,5 +70,27 @@
"SubmitButton": {
"button_while_submit": "در حال ثبت",
"button_submit": "ثبت"
},
"UpdateProfile": {
"error_invalid_email": "ایمیل نامعتبر است",
"typography_edit_profile": "ویرایش پروفایل",
"text_field_email": "ایمیل",
"text_field_enter_your_password": "رمز عبور خود را وارد کنید",
"text_field_username": "نام کاربری",
"text_field_name": "نام",
"text_field_phone_number": "شماره تماس",
"text_field_position": "سمت",
"text_field_province_name": "نام استان",
"error_message_required": "اجباری !"
},
"notifications": {
"code": "کد",
"error": "خطا",
"warning": "خطر",
"success": "موفق",
"error_static_text": "عملیات شما با خطا مواجه شد",
"warning_static_text": "خطا سرور",
"success_static_text": "عملیات شما با موفقیت انجام شد",
"pending": "در حال انجام..."
}
}

View File

@@ -28,7 +28,7 @@ const DashboardChangePasswordComponent = () => {
const handleSubmit = (values, { setSubmitting, resetForm }) => {
toast.dismiss();
const pendingToast = toast("Pending...", {
const pendingToast = toast(t("notifications.pending"), {
position: directionApp === "ltr" ? "top-left" : "top-right",
autoClose: false,
closeOnClick: false,

View File

@@ -0,0 +1,299 @@
import StyledForm from "@/core/components/StyledForm";
import CenterLayout from "@/layouts/CenterLayout";
import DashboardLayouts from "@/layouts/dashboardLayouts";
import useUser from "@/lib/app/hooks/useUser";
import {
Box,
Button,
Container,
Grid,
Paper,
Stack,
TextField,
Typography,
} from "@mui/material";
import * as Yup from "yup";
import { Field, Formik } from "formik";
import { useTranslations } from "next-intl";
import AvatarUpload from "@/core/components/AvatarUpload";
import axios from "axios";
import { UPDATE_PROFILE, UPDATE_AVATAR } from "@/core/data/apiRoutes";
import useDirection from "@/lib/app/hooks/useDirection";
import Notifications from "@/core/components/notifications";
import ImageResizer from "@/core/components/ImageConvertor";
const DashboardEditProfile = () => {
const t = useTranslations();
const { user, token, getUser, changeUser } = useUser();
console.log(user);
const { directionApp } = useDirection();
const editAvatar = async (avatar) => {
try {
const formData = new FormData();
if (avatar != null) {
var resizedAvatar;
resizedAvatar = await ImageResizer(avatar);
formData.append("avatar", resizedAvatar);
}
await axios.post(UPDATE_AVATAR, formData, {
headers: {
authorization: `Bearer ${token}`,
"Content-Type": "multipart/form-data",
},
});
} catch (error) {
Notifications(directionApp, error.response, t);
throw error;
}
};
const handleSubmit = (values, { setSubmitting }) => {
// const formData = new FormData();
// formData.append("email", values.expert_email);
// if (values.change_avatar !== false) {
// editAvatar(values.expert_avatar)
// .then(() => {
// return axios.post(UPDATE_PROFILE, formData, {
// headers: {
// authorization: `Bearer ${token}`,
// "Content-Type": "multipart/form-data",
// },
// });
// })
// .then((response) => {
// Notifications(directionApp, response, t);
// getUser((data) => {
// changeUser(data);
// });
// })
// .catch((error) => {
// Notifications(directionApp, error.response, t);
// })
// .finally(() => {
// setSubmitting(false);
// });
// } else {
// axios
// .post(UPDATE_PROFILE, formData, {
// headers: {
// authorization: `Bearer ${token}`,
// "Content-Type": "multipart/form-data",
// },
// })
// .then((response) => {
// Notifications(directionApp, response, t);
// getUser((data) => {
// changeUser(data);
// });
// })
// .catch((error) => {
// Notifications(directionApp, error.response, t);
// })
// .finally(() => {
// setSubmitting(false);
// });
// }
};
const initialValues = {
expert_avatar: null,
username: user.username,
name: user.name,
email: user.email,
province_name: user.province_name,
position: user.position,
};
const validationSchema = Yup.object().shape({
// username: Yup.string().required(t("UpdateProfile.error_message_required")),
// name: Yup.string().required(t("UpdateProfile.error_message_required")),
// email: Yup.string()
// .email(t("UpdateProfile.error_invalid_email"))
// .required(t("UpdateProfile.error_message_required")),
// province_name: Yup.string().required(
// t("UpdateProfile.error_message_required")
// ),
// position: Yup.string().required(t("UpdateProfile.error_message_required")),
});
return (
<DashboardLayouts>
<CenterLayout>
<Container maxWidth="sm">
<Formik
initialValues={initialValues}
onSubmit={handleSubmit}
validationSchema={validationSchema}
>
{(props) => (
<StyledForm
onSubmit={(e) => {
e.preventDefault();
props.handleSubmit();
}}
>
<Paper elevation={0}>
<Stack spacing={3} sx={{ p: 5 }} component="div">
<Typography margin={2} variant="h4" textAlign="center">
{t("UpdateProfile.typography_edit_profile")}
</Typography>
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
}}
>
<AvatarUpload
user={user}
setFieldValue={props.setFieldValue}
valueAvatar="expert_avatar"
changeFlag="change_avatar"
/>
</Box>
<Grid container spacing={2} sx={{ pb: 2 }}>
<Grid item xs={12} sm={6}>
<Field
as={TextField}
name="username"
label={t("UpdateProfile.text_field_username")}
variant="outlined"
margin="normal"
size="small"
disabled={true}
error={
props.touched.username && props.errors.username
? true
: false
}
helperText={
props.touched.username
? props.errors.username
: null
}
sx={{
width: "100%",
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Field
as={TextField}
name="name"
label={t("UpdateProfile.text_field_name")}
variant="outlined"
margin="normal"
size="small"
disabled={true}
error={
props.touched.name && props.errors.name
? true
: false
}
helperText={
props.touched.name ? props.errors.name : null
}
sx={{
width: "100%",
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Field
as={TextField}
name="email"
label={t("UpdateProfile.text_field_email")}
variant="outlined"
margin="normal"
size="small"
disabled={true}
error={
props.touched.email && props.errors.email
? true
: false
}
helperText={
props.touched.email ? props.errors.email : null
}
sx={{
width: "100%",
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Field
as={TextField}
name="province_name"
label={t("UpdateProfile.text_field_province_name")}
variant="outlined"
margin="normal"
size="small"
disabled={true}
error={
props.touched.province_name &&
props.errors.province_name
? true
: false
}
helperText={
props.touched.province_name
? props.errors.province_name
: null
}
sx={{
width: "100%",
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Field
as={TextField}
name="position"
label={t("UpdateProfile.text_field_position")}
variant="outlined"
margin="normal"
size="small"
disabled={true}
error={
props.touched.position && props.errors.position
? true
: false
}
helperText={
props.touched.position
? props.errors.position
: null
}
sx={{
width: "100%",
}}
/>
</Grid>
</Grid>
{/* <Button
type="submit"
fullWidth
variant="contained"
color="primary"
size="large"
disabled={props.isSubmitting}
display="none"
>
{props.isSubmitting
? t("SubmitButton.button_while_submit")
: t("SubmitButton.button_submit")}
</Button> */}
</Stack>
</Paper>
</StyledForm>
)}
</Formik>
</Container>
</CenterLayout>
</DashboardLayouts>
);
};
export default DashboardEditProfile;

View File

@@ -2,7 +2,7 @@ import LinkRouting from "@/core/components/LinkRouting";
// import Notifications from "@/core/components/notifications";
import PasswordField from "@/core/components/PasswordField";
import StyledForm from "@/core/components/StyledForm";
// import { GET_USER_TOKEN } from "@/core/data/apiRoutes";
import { GET_USER_TOKEN } from "@/core/data/apiRoutes";
import CenterLayout from "@/layouts/CenterLayout";
import FullPageLayout from "@/layouts/FullPageLayout";
import useDirection from "@/lib/app/hooks/useDirection";
@@ -17,7 +17,7 @@ import {
TextField,
Typography,
} from "@mui/material";
// import axios from "axios";
import axios from "axios";
import { Field, Formik } from "formik";
import { useTranslations } from "next-intl";
import Image from "next/image";
@@ -33,21 +33,23 @@ const LoginComponent = () => {
const searchParams = useSearchParams();
const backUrlDecodedPath = searchParams.get("back_url");
// formik properties
// const handleSubmit = async (values, props) => {
// await axios
// .post(GET_USER_TOKEN, {
// username: values.username,
// password: values.password,
// })
// .then(function (response) {
// setToken(response.data.token);
// })
// .catch(function (error) {
// Notifications(directionApp, error.response, t);
// props.setSubmitting(false);
// });
// };
//formik properties
const handleSubmit = async (values, props) => {
await axios
.post(GET_USER_TOKEN, {
username: values.username,
password: values.password,
})
.then(function (response) {
console.log(response);
setToken(response.data.token);
})
.catch(function (error) {
// Notifications(directionApp, error.response, t);
console.log(error);
props.setSubmitting(false);
});
};
const initialValues = {
username: "",
password: "",
@@ -64,7 +66,7 @@ const LoginComponent = () => {
<Paper elevation={0}>
<Formik
initialValues={initialValues}
// onSubmit={handleSubmit}
onSubmit={handleSubmit}
validationSchema={validationSchema}
>
{(props) => (

View File

@@ -0,0 +1,128 @@
import { Avatar, Box, TextField } from "@mui/material";
import { useState } from "react";
import DeleteIcon from "@mui/icons-material/Delete";
import AddIcon from "@mui/icons-material/Add";
const AvatarUpload = ({ user, setFieldValue, valueAvatar, changeFlag }) => {
const [selectedImage, setSelectedImage] = useState(user.expert_avatar);
const [isHovered, setIsHovered] = useState(false);
const handleImageChange = (event) => {
const newImage = event.target?.files?.[0];
if (newImage) {
setSelectedImage(URL.createObjectURL(newImage));
setFieldValue(valueAvatar, newImage);
setFieldValue(changeFlag, true);
} else {
setSelectedImage("");
setFieldValue(valueAvatar, null);
}
};
const handleDeleteImage = () => {
setSelectedImage("");
setFieldValue(valueAvatar, null);
setFieldValue(changeFlag, true);
};
const handleMouseEnter = () => {
setIsHovered(true);
};
const handleMouseLeave = () => {
setIsHovered(false);
};
return (
<Box
sx={{
display: "inline-block",
position: "relative",
}}
>
<Box
component="label"
htmlFor="avatar-upload"
sx={{
display: "inline-block",
width: "fit-content",
height: "fit-content",
cursor: "pointer",
}}
>
<Box
component="div"
className={`avatar-container ${isHovered ? "hovered" : ""}`}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
sx={{
position: "relative",
width: 150,
height: 150,
borderRadius: "50%",
overflow: "hidden",
transition: "transform 0.3s ease-in-out",
}}
>
<Avatar
alt="User Avatar"
src={selectedImage}
sx={{
width: 150,
height: 150,
cursor: "pointer",
position: "relative",
}}
/>
{isHovered && (
<Box
component="div"
className="avatar-overlay"
sx={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
backgroundColor: "rgba(0, 0, 0, 0.6)",
display: "flex",
alignItems: "center",
justifyContent: "center",
transition: "transform 0.3s ease-in-out",
transform: `scale(${isHovered ? 1 : 0})`,
}}
>
{selectedImage ? (
<DeleteIcon
sx={{
color: "#fff",
width: 35,
height: 35,
}}
onClick={handleDeleteImage}
/>
) : (
<AddIcon
sx={{
color: "#fff",
width: 35,
height: 35,
}}
/>
)}
</Box>
)}
</Box>
</Box>
<TextField
id="avatar-upload"
type="file"
accept="image/*"
sx={{ display: "none" }}
onChange={handleImageChange}
/>
</Box>
);
};
export default AvatarUpload;

View File

@@ -1,6 +1,13 @@
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL;
//login
export const GET_USER_TOKEN = BASE_URL + "/dashboard/login";
//end login
//change password
export const CHANGE_PASSWORD =
BASE_URL + "/dashboard/profile/change_password";
//end change password
export const CHANGE_PASSWORD = BASE_URL + "/dashboard/profile/change_password";
//end change password
//user data
export const GET_USER_ROUTE = BASE_URL + "/dashboard/profile/info";
//user data

View File

@@ -51,27 +51,7 @@ export const UserProvider = ({ children }) => {
dispatch({ type: "CHANGE_USER", user });
}, []);
const changeUserLanguage = useCallback(
(language) => {
dispatch({ type: "CHANGE_USER_LANGUAGE", language });
axios
.post(
SET_LANGUAGE,
{ language: language },
{
headers: {
authorization: `Bearer ${state.token}`,
},
}
)
.then(() => {
getUser((data) => {
changeUser(data);
});
});
},
[state.token]
);
const changeUserLanguage = useCallback(/* use in multi language app */);
const changeAuthState = useCallback((isAuth) => {
dispatch({ type: "CHANGE_AUTH_STATE", isAuth });

View File

@@ -10,7 +10,6 @@ const useUser = () => {
getUser,
clearUser,
changeUser,
changeUserLanguage,
changeAuthState,
changeLanguageState,
clearToken,
@@ -25,7 +24,6 @@ const useUser = () => {
getUser,
clearUser,
changeUser,
changeUserLanguage,
changeAuthState,
changeLanguageState,
clearToken,

View File

@@ -16,7 +16,7 @@ const WithAuthMiddleware = ({ children }) => {
text={
<Typography sx={{ textAlign: "center" }}>
{t(
"typography_your_access_to_this_page_has_expired_Please_login_again"
"Authorization.typography_your_access_to_this_page_has_expired_Please_login_again"
)}
</Typography>
}
@@ -26,7 +26,7 @@ const WithAuthMiddleware = ({ children }) => {
variant="contained"
component={NextLinkComposed}
to={{
pathname: "/login",
pathname: "/login-expert",
query: { back_url: encodeURIComponent(router.asPath) },
}}
>

View File

@@ -37,14 +37,14 @@ const WithoutAuthMiddleware = ({ children }) => {
<Stack alignItems="center" spacing={2}>
<Typography sx={{ textAlign: "center" }}>
{t(
"typography_your_login_is_valid_and_you_do_not_need_to_login_again"
"Authorization.typography_your_login_is_valid_and_you_do_not_need_to_login_again"
)}
</Typography>
<Typography>
{t("typography_redirect_to")}{" "}
{t("Authorization.typography_redirect_to")}{" "}
{backUrlDecodedPath
? t("typography_routing_previuos_page")
: t("typography_routing_dashbaord_page")}
? t("Authorization.typography_routing_previuos_page")
: t("Authorization.typography_routing_dashbaord_page")}
</Typography>
</Stack>
}

View File

@@ -1,6 +1,6 @@
import DashboardChangePasswordComponent from "@/components/dashboard/change-password";
import TitlePage from "@/core/components/TitlePage";
import WithAuthMiddleware from "@/middlewares/WithoutAuth";
import WithAuthMiddleware from "@/middlewares/WithAuth";
import { parse } from "next-useragent";
export default function LoanFollowUp() {

View File

@@ -0,0 +1,23 @@
import DashboardEditProfile from "@/components/dashboard/edit-profile";
import TitlePage from "@/core/components/TitlePage";
import WithAuthMiddleware from "@/middlewares/WithAuth";
import { parse } from "next-useragent";
export default function LoanFollowUp() {
return (
<WithAuthMiddleware>
<TitlePage text="Dashboard.edit_profile" />
<DashboardEditProfile />
</WithAuthMiddleware>
);
}
export async function getServerSideProps({ req, locale }) {
const { isBot } = parse(req.headers["user-agent"]);
return {
props: {
messages: (await import(`&/locales/${locale}/app.json`)).default,
isBot,
},
};
}

View File

@@ -1,12 +1,12 @@
import DashboardFirstComponent from "@/components/dashboard/first";
import TitlePage from "@/core/components/TitlePage";
import WithAuthMiddleware from "@/middlewares/WithoutAuth";
import WithAuthMiddleware from "@/middlewares/WithAuth";
import { parse } from "next-useragent";
export default function Dashboard() {
return (
<WithAuthMiddleware>
<TitlePage text="dashboard.dashboard_page" />
<TitlePage text="Dashboard.dashboard_page" />
<DashboardFirstComponent />
</WithAuthMiddleware>
);

View File

@@ -6,7 +6,7 @@ import { parse } from "next-useragent";
export default function LoanFollowUp() {
return (
<WithAuthMiddleware>
<TitlePage text="dashboard.dashboard_page" />
<TitlePage text="Dashboard.dashboard_page" />
<DashboardLoanFollowUpComponent />
</WithAuthMiddleware>
);

View File

@@ -6,7 +6,7 @@ import { parse } from "next-useragent";
export default function LoanRequest() {
return (
<WithAuthMiddleware>
<TitlePage text="dashboard.dashboard_page" />
<TitlePage text="Dashboard.dashboard_page" />
<DashboardLoanRequestComponent />
</WithAuthMiddleware>
);