diff --git a/src/Components/Layout/Navbar/NavBarRightSide.tsx b/src/Components/Layout/Navbar/NavBarRightSide.tsx index d864590..ff8bbc6 100644 --- a/src/Components/Layout/Navbar/NavBarRightSide.tsx +++ b/src/Components/Layout/Navbar/NavBarRightSide.tsx @@ -14,10 +14,12 @@ import { IoIosNotificationsOutline } from "react-icons/io"; import { CiCirclePlus } from "react-icons/ci"; import { TbWorld } from "react-icons/tb"; import TooltipComp from "./Tooltip"; +import { useNavigate } from "react-router-dom"; const NavBarRightSide = () => { const userData = getLocalStorage(USER_KEY); const [t] = useTranslation(); + const Navigate = useNavigate(); // const translateArray = translateOptions(search_array, t); const { handel_open_model } = useModalHandler(); @@ -54,7 +56,7 @@ const NavBarRightSide = () => {
{userData?.username}

{userData?.type}

*/} - Profile + (Navigate('/profile'))} src="/Image/faker_user.png" alt="Profile" /> ); diff --git a/src/Pages/Admin/Notifications/Page.tsx b/src/Pages/Admin/Notifications/Page.tsx index 46930e6..4ad956c 100644 --- a/src/Pages/Admin/Notifications/Page.tsx +++ b/src/Pages/Admin/Notifications/Page.tsx @@ -17,7 +17,7 @@ const Page = () => { return (
-

{t("header.notifications")}

+

{t("header.notifications")}

diff --git a/src/Pages/Admin/Profile/Form/AttachmentForm.tsx b/src/Pages/Admin/Profile/Form/AttachmentForm.tsx new file mode 100644 index 0000000..da3397c --- /dev/null +++ b/src/Pages/Admin/Profile/Form/AttachmentForm.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { FaImage } from "react-icons/fa"; +import ImageBoxField from "./ImageBoxField/ImageBoxField"; + +const AttachmentForm = () => { + const [t] = useTranslation(); + + return ( +
+
+
+ +

{t("header.attachments")}

+
+
+
+ + +
+
+ ); +}; + +export default AttachmentForm; diff --git a/src/Pages/Admin/Profile/Form/HeaderForm.tsx b/src/Pages/Admin/Profile/Form/HeaderForm.tsx new file mode 100644 index 0000000..eb9a94e --- /dev/null +++ b/src/Pages/Admin/Profile/Form/HeaderForm.tsx @@ -0,0 +1,24 @@ +import { Button } from 'antd'; +import React from 'react' +import { useTranslation } from 'react-i18next' + +const HeaderForm = ({name,Icon,ButtonIcon, isHaveButtonIcon= true,buttonName = "edit"}:{name:string,Icon:any,ButtonIcon?:any,isHaveButtonIcon?:boolean,buttonName?:string}) => { + const {t} = useTranslation(); + return ( + <> +
+ +

{t(`header.${name}`)}

+
+
+ +
+ + ) +} + +export default HeaderForm \ No newline at end of file diff --git a/src/Pages/Admin/Profile/Form/ImageBoxField/ImageBoxField.scss b/src/Pages/Admin/Profile/Form/ImageBoxField/ImageBoxField.scss new file mode 100644 index 0000000..66e2f2f --- /dev/null +++ b/src/Pages/Admin/Profile/Form/ImageBoxField/ImageBoxField.scss @@ -0,0 +1,38 @@ +.ImageBoxField { + .ImageBox { + width: 120px; + height: 120px; + display: flex; + align-items: center; + justify-content: center; + border: max(1.5px, 0.1vw) dashed #a9c3f1; + margin-block: 10px; + border-radius: 5px; + z-index: 9999999 !important; + .ImageBoxIcon { + cursor: pointer; + } + .imagePreview { + max-width: 99%; + height: auto; + max-height: 99%; + object-fit: contain; + border-radius: 5px; + } + } + .ImageHeader { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 10px; + } + + .ImageCancelIcon { + width: 16px !important; + height: 16px !important; + } + .ImageBoxIcon { + width: 20px !important; + height: 20px !important; + } +} diff --git a/src/Pages/Admin/Profile/Form/ImageBoxField/ImageBoxField.tsx b/src/Pages/Admin/Profile/Form/ImageBoxField/ImageBoxField.tsx new file mode 100644 index 0000000..d163048 --- /dev/null +++ b/src/Pages/Admin/Profile/Form/ImageBoxField/ImageBoxField.tsx @@ -0,0 +1,87 @@ +import { useFormikContext } from "formik"; +import { useState, useRef, useEffect } from "react"; +import "./ImageBoxField.scss"; +import ImageIcon from "./ImageIcon"; +import ImageCancelIcon from "./ImageCancelIcon"; +import { generateImagePreview } from "./generateImagePreview"; +import { getNestedValue } from "../../../../../utils/getNestedValue"; +import { useTranslation } from "react-i18next"; + +// Helper function to generate image preview from a File + +const ImageBoxField = ({ name }: any) => { + const formik = useFormikContext(); + const value = getNestedValue(formik.values, name); + const [imagePreview, setImagePreview] = useState(null); + const fileInputRef = useRef(null); + + useEffect(() => { + if (value instanceof File) { + generateImagePreview(value, setImagePreview); + } else if (typeof value === "string") { + setImagePreview(value); + } else { + setImagePreview(null); + } + }, [value]); + + const handleFileChange = (event: any) => { + const file = event.target.files[0]; + if (file) { + generateImagePreview(file, setImagePreview); + formik.setFieldValue(name, file); + } + }; + + const handleButtonClick = () => { + const fileInput = fileInputRef.current; + if (fileInput) { + fileInput.click(); + } + }; + + const handleCancel = () => { + setImagePreview(""); + formik.setFieldValue(name, ""); + + if (fileInputRef.current) { + fileInputRef.current.value = ""; + } + }; + const [t] = useTranslation(); + return ( +
+
{t(`input.${name}`)}
+
+ {imagePreview ? ( + <> + + + + ) : ( +
hidden
+ )} +
+
+ {imagePreview ? ( + Preview + ) : ( + + )} +
+ +
+ ); +}; + +export default ImageBoxField; diff --git a/src/Pages/Admin/Profile/Form/ImageBoxField/ImageCancelIcon.tsx b/src/Pages/Admin/Profile/Form/ImageBoxField/ImageCancelIcon.tsx new file mode 100644 index 0000000..d42ba53 --- /dev/null +++ b/src/Pages/Admin/Profile/Form/ImageBoxField/ImageCancelIcon.tsx @@ -0,0 +1,18 @@ +import React from "react"; + +interface ImageCancelIconProps extends React.HTMLAttributes {} + +const ImageCancelIcon: React.FC = (props) => { + return ( +
+ + + +
+ ); +}; + +export default ImageCancelIcon; diff --git a/src/Pages/Admin/Profile/Form/ImageBoxField/ImageIcon.tsx b/src/Pages/Admin/Profile/Form/ImageBoxField/ImageIcon.tsx new file mode 100644 index 0000000..4ca597a --- /dev/null +++ b/src/Pages/Admin/Profile/Form/ImageBoxField/ImageIcon.tsx @@ -0,0 +1,18 @@ +import React from "react"; + +interface ImageIconProps extends React.HTMLAttributes {} + +const ImageIcon: React.FC = (props) => { + return ( +
+ + + +
+ ); +}; + +export default ImageIcon; diff --git a/src/Pages/Admin/Profile/Form/ImageBoxField/generateImagePreview.ts b/src/Pages/Admin/Profile/Form/ImageBoxField/generateImagePreview.ts new file mode 100644 index 0000000..3f754d3 --- /dev/null +++ b/src/Pages/Admin/Profile/Form/ImageBoxField/generateImagePreview.ts @@ -0,0 +1,10 @@ +export const generateImagePreview = ( + file: File, + setImagePreview: (result: string) => void, +) => { + const reader = new FileReader(); + reader.onloadend = () => { + setImagePreview(reader.result as string); + }; + reader.readAsDataURL(file); +}; diff --git a/src/Pages/Admin/Profile/Form/PasswordDetailsForm.tsx b/src/Pages/Admin/Profile/Form/PasswordDetailsForm.tsx new file mode 100644 index 0000000..88efe37 --- /dev/null +++ b/src/Pages/Admin/Profile/Form/PasswordDetailsForm.tsx @@ -0,0 +1,26 @@ +import { useTranslation } from "react-i18next"; +import { FaStore } from "react-icons/fa"; +import ValidationField from "../../../../Components/ValidationField/ValidationField"; +import { CiEdit } from "react-icons/ci"; +import HeaderForm from "./HeaderForm"; + +const PasswordDetailsForm = () => { + const [t] = useTranslation(); + + return ( +
+
+ +
+
+ +
+
+ ); +}; + +export default PasswordDetailsForm; diff --git a/src/Pages/Admin/Profile/Form/PersonalDetailsForm.tsx b/src/Pages/Admin/Profile/Form/PersonalDetailsForm.tsx new file mode 100644 index 0000000..96d9ed5 --- /dev/null +++ b/src/Pages/Admin/Profile/Form/PersonalDetailsForm.tsx @@ -0,0 +1,56 @@ +import { useTranslation } from "react-i18next"; +import { FaStore } from "react-icons/fa"; +import ValidationField from "../../../../Components/ValidationField/ValidationField"; +import { statusType } from "../../../../config/statusType"; +import { Button } from "antd"; +import { CiEdit } from "react-icons/ci"; +import HeaderForm from "./HeaderForm"; + +const PersonalDetailsForm = () => { + const [t] = useTranslation(); + return ( +
+
+ +
+
+ + + + + + + + +
+
+ ); +}; + +export default PersonalDetailsForm; diff --git a/src/Pages/Admin/Profile/Form/TitleDetailsForm.tsx b/src/Pages/Admin/Profile/Form/TitleDetailsForm.tsx new file mode 100644 index 0000000..1e62655 --- /dev/null +++ b/src/Pages/Admin/Profile/Form/TitleDetailsForm.tsx @@ -0,0 +1,33 @@ +import { useTranslation } from "react-i18next"; +import { FaStore } from "react-icons/fa"; +import ValidationField from "../../../../Components/ValidationField/ValidationField"; +import { nationalities } from "../../../../types/App"; +import { CiEdit } from "react-icons/ci"; +import HeaderForm from "./HeaderForm"; + +const TitleDetailsForm = () => { + const [t] = useTranslation(); + + return ( +
+
+ +
+
+ + +
+
+ ); +}; + +export default TitleDetailsForm; diff --git a/src/Pages/Admin/Profile/Form/formUtils.ts b/src/Pages/Admin/Profile/Form/formUtils.ts new file mode 100644 index 0000000..b14915a --- /dev/null +++ b/src/Pages/Admin/Profile/Form/formUtils.ts @@ -0,0 +1,13 @@ +import * as Yup from "yup"; + +export const getInitialValues = (objectToEdit: Partial) => { + return { + id: objectToEdit?.id ?? null, + name: objectToEdit?.name ?? null, + }; +}; + +export const getValidationSchema = () => { + // validate input + return Yup.object().shape({}); +}; diff --git a/src/Pages/Admin/Profile/Page.tsx b/src/Pages/Admin/Profile/Page.tsx new file mode 100644 index 0000000..def9cc2 --- /dev/null +++ b/src/Pages/Admin/Profile/Page.tsx @@ -0,0 +1,58 @@ +import { useTranslation } from "react-i18next"; +import useSetPageTitle from "../../../Hooks/useSetPageTitle"; +import PageHeader from "../../../Layout/Dashboard/PageHeader"; +import { Suspense } from "react"; +import { Spin } from "antd"; +import { ModalEnum } from "../../../enums/Model"; +import { canAddReSeller } from "../../../utils/hasAbilityFn"; +import { Formik, Form } from "formik"; +import { getInitialValues, getValidationSchema } from "./Form/formUtils"; +import PersonalDetailsForm from "./Form/PersonalDetailsForm"; +import TitleDetailsForm from "./Form/TitleDetailsForm"; +import AttachmentForm from "./Form/AttachmentForm"; +import PasswordDetailsForm from "./Form/PasswordDetailsForm"; + +const Page = () => { + const [t] = useTranslation(); + useSetPageTitle(t(`page_header.add_reseller`)); + const handelSubmit = (values: any) => { + console.log(values, "values"); + }; + useSetPageTitle([ + {name:`${t(`page_header.home`)}`, path:"/"}, + {name:`${t(`page_header.profile`)}`, path:"tag"} + ]); + return ( +
+ }> + +
+ +
+ + + + +
+ + +
+ +
+
+
+
+ ); +}; + +export default Page; diff --git a/src/Pages/Admin/Reseller/Add/Page.tsx b/src/Pages/Admin/Reseller/Add/Page.tsx index 19d6daa..8a16bc0 100644 --- a/src/Pages/Admin/Reseller/Add/Page.tsx +++ b/src/Pages/Admin/Reseller/Add/Page.tsx @@ -25,13 +25,13 @@ const TableHeader = () => { ModelAbility={ModalEnum?.RE_SELLER_ADD} canAdd={false} /> -
+
-
+ diff --git a/src/Routes.tsx b/src/Routes.tsx index 745ad8a..def863d 100644 --- a/src/Routes.tsx +++ b/src/Routes.tsx @@ -35,6 +35,7 @@ const User = React.lazy(() => import("./Pages/Admin/User/Page")); const Param = React.lazy(() => import("./Pages/Admin/Param/Page")); const QuestionBank = React.lazy(() => import("./Pages/Admin/QuestionBank/Page")); const Notifications = React.lazy(() => import("./Pages/Admin/Notifications/Page")); +const Profile = React.lazy(() => import("./Pages/Admin/Profile/Page")); /// RESELLER /// const Student_Package = React.lazy( @@ -253,6 +254,14 @@ export const CrudRoute: TCrudRoute[] = [ abilities: ABILITIES_ENUM?.STUDENT, abilities_value: ABILITIES_VALUES_ENUM.INDEX, prevPath: 0, + }, + { + header: "page_header.profile", + element: , + path: `/${ABILITIES_ENUM?.PROFILE}`, + abilities: ABILITIES_ENUM?.PROFILE, + abilities_value: ABILITIES_VALUES_ENUM.INDEX, + prevPath: 0, } ]; diff --git a/src/Styles/Layout/NavBar.scss b/src/Styles/Layout/NavBar.scss index b061827..af7cafc 100644 --- a/src/Styles/Layout/NavBar.scss +++ b/src/Styles/Layout/NavBar.scss @@ -34,6 +34,7 @@ img { width: 40px; height: 40px; + cursor: pointer; } h6 { font-size: 0.8vw; diff --git a/src/Styles/Pages/index.scss b/src/Styles/Pages/index.scss index 9c48266..5f71030 100644 --- a/src/Styles/Pages/index.scss +++ b/src/Styles/Pages/index.scss @@ -11,4 +11,5 @@ @import "./exercise.scss"; @import './reSeller.scss'; @import './InfoCard.scss'; -@import './notifications.scss'; \ No newline at end of file +@import './notifications.scss'; +@import './profile.scss'; \ No newline at end of file diff --git a/src/Styles/Pages/notifications.scss b/src/Styles/Pages/notifications.scss index 7aee571..2cd760f 100644 --- a/src/Styles/Pages/notifications.scss +++ b/src/Styles/Pages/notifications.scss @@ -7,26 +7,33 @@ box-shadow: 2px 2px 8px 3px rgba(0, 0, 0, 0.1); border: 1.5px solid #E9EDF4; border-radius: 10px; + background: #fff; .notification_header{ display: flex;align-items: center;justify-content: space-between; padding-inline: 20px; + h4{ + color: var(--secondary); + } } .notification_body{ display: flex; flex-direction: column; justify-content: center; padding-inline: 10px ; gap: 30px; - // transition: ease-in-out .4s; .notification_card{ display: flex; justify-content: space-between; padding: 20px 20px; - box-shadow: 2px 2px 8px 2px rgba(0, 0, 0, .05); - border: 1.5px solid #E9EDF4; + @include Shadow; + border: 2px solid #E9EDF4; border-radius: 10px; + background: #fff; cursor: pointer; >div{ display: flex;align-items: center; gap: 14px; + h5{ + color: var(--secondary); + } img{ width: 70px; } diff --git a/src/Styles/Pages/profile.scss b/src/Styles/Pages/profile.scss new file mode 100644 index 0000000..0113dff --- /dev/null +++ b/src/Styles/Pages/profile.scss @@ -0,0 +1,36 @@ +.profile{ + .header_form{ + display: flex; justify-content: space-between; + >div{ + h4{ + font-weight: 900; + } + display: flex;align-items: center; + gap: 10px; + } + } +} + + + + + +.header_form{ + .edit_button{ + background: var(--primary); + @include Flex; + color: var(--white); + border: none !important; + svg{ + font-size: 34px !important; + background: transparent !important; + color: var(--white) !important; + } + &:hover{ + background: var(--primary) !important; + color: var(--white) !important; + border: none !important; + } + } +} + diff --git a/src/Styles/Pages/reSeller.scss b/src/Styles/Pages/reSeller.scss index bcfd460..cff1520 100644 --- a/src/Styles/Pages/reSeller.scss +++ b/src/Styles/Pages/reSeller.scss @@ -1,11 +1,12 @@ .main_form_body { display: flex; flex-wrap: wrap; + gap: 20px; background: var(--bg); padding: 40px 10px; > * { // max-width: 30%; - flex-basis: 33%; + flex-basis: 31%; } } @@ -41,3 +42,28 @@ } } } + +.Form_details_container{ + display: flex; flex-direction: column; + gap: 30px; +} + +.PersonalDetailsForm, +.TitleDetailsForm, +.AttachmentForm, +.PasswordDetailsForm{ + box-shadow: 2px 2px 7px 2px rgba(0, 0, 0, 0.1); + margin-bottom: 10px; + border-radius: 10px; + .header_form{ + background: #F2F4F8; + border-radius: 10px; + svg{ + border-radius: 5px; + color: var(--secondary); + } + } + .main_form_body{ + border-radius: 10px; + } +} \ No newline at end of file diff --git a/src/enums/abilities.ts b/src/enums/abilities.ts index 45e8f6a..0414158 100644 --- a/src/enums/abilities.ts +++ b/src/enums/abilities.ts @@ -49,6 +49,7 @@ export enum ABILITIES_ENUM { Student_Package = "student_package", QUESTION_BANK = "QuestionBank", NOTIFICATIONS = "Notifications", + PROFILE = "profile" //// } diff --git a/src/translate/ar.json b/src/translate/ar.json index fd7002c..0a2179d 100644 --- a/src/translate/ar.json +++ b/src/translate/ar.json @@ -130,7 +130,11 @@ "edit_question":"تعديل سؤال", "notifications":"الإشعارات", "delete_all":" حذف الكل", - "delete":"حذف" + "delete":"حذف", + "attachments":"المرفقات", + "password":"كلمة المرور", + "edit":"تعديل", + "change":"تغيير" }, "columns": { "id": "الرقم التعريفي", @@ -335,7 +339,8 @@ "reseller_details": "تفاصيل اعادة البيع", "reseller": "البائعين", "student_package": "حزمة الطالب", - "collection":"تحصيل" + "collection":"تحصيل", + "profile":"الملف الشخصي" }, "education_class_actions": { "Student_Records": "سجلات الطلاب", @@ -456,7 +461,9 @@ "unit":"الوحدة", "lesson":"الدرس", "date_of_receipt":"تاريخ الاستلام", - "amount_value":"قيمة المبلغ" + "amount_value":"قيمة المبلغ", + "email_address":"عنوان البريد الإلكتروني", + "current_password":"كلمة المرور الحالية" }, "select": { "enums": { @@ -788,7 +795,8 @@ "student_package": "حزمة الطالب", "quiz":"الاختبارات", "questionBank":"بنك الأسئلة", - "notifications":"الإشعارات" + "notifications":"الإشعارات", + "profile":"الملف الشخصي" }, "message": { "some_thing_went_wrong": "حدث خطأ ما", @@ -826,7 +834,8 @@ "reseller":"البائعين", "QuestionBank":"بنك الأسئلة", "reseller_details":"تفاصيل البائع", - "notifications":"الإشعارات" + "notifications":"الإشعارات", + "profile":"الملف الشخصي" }, "page_header": { "home": "لوحة القيادة", @@ -866,7 +875,8 @@ "student_package": "حزمة الطالب", "QuestionBank":"بنك الأسئلة", "reseller_details":"تفاصيل البائع", - "notifications":"الإشعارات" + "notifications":"الإشعارات", + "profile":"الملف الشخصي" }, "table": { "student": "قائمة الطلاب",