├── public ├── _redirects ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json └── index.html ├── src ├── shared │ ├── history.js │ ├── authorization.js │ ├── toast.js │ └── SecureRoute.js ├── assets │ └── images │ │ ├── dev.png │ │ ├── profil.png │ │ ├── basry-logo.png │ │ ├── education.png │ │ ├── experience.png │ │ └── dev.svg ├── components │ ├── User │ │ ├── About │ │ │ ├── About.css │ │ │ └── About.js │ │ ├── Footer │ │ │ ├── Footer.css │ │ │ └── Footer.js │ │ ├── PageIntro │ │ │ ├── styles.css │ │ │ └── PageIntro.js │ │ ├── Login │ │ │ ├── styles.css │ │ │ └── Login.js │ │ ├── Navbar │ │ │ ├── styles.css │ │ │ └── Navbar.js │ │ ├── Skills │ │ │ └── Skills.js │ │ ├── Education │ │ │ └── Education.js │ │ ├── Projects │ │ │ └── Projects.js │ │ ├── Experience │ │ │ └── Experience.js │ │ └── Contacts │ │ │ └── Contacts.js │ └── Admin │ │ ├── SideBar │ │ ├── SidebarData.js │ │ ├── SideBar.css │ │ └── SideBar.js │ │ ├── Modal.js │ │ ├── Table.js │ │ ├── SkillModal.js │ │ ├── EducationModal.js │ │ ├── ProjectModal.js │ │ └── ExperienceModal.js ├── apis │ ├── userApi.js │ ├── messageApi.js │ ├── skillApi.js │ ├── serverApi.js │ ├── projectApi.js │ ├── educationApi.js │ └── experienceApi.js ├── pages │ ├── Home.js │ ├── NotFound.js │ ├── SkillAdmin.js │ ├── ProjectAdmin.js │ ├── EducationAdmin.js │ ├── ExperienceAdmin.js │ ├── PortfolioUI.js │ └── MessageAdmin.js ├── reducers │ ├── message.js │ ├── skill.js │ ├── project.js │ ├── education.js │ ├── experience.js │ ├── index.js │ └── isLogged.js ├── reportWebVitals.js ├── index.css ├── actions │ ├── messageAction.js │ ├── loginAction.js │ ├── skillAction.js │ ├── projectAction.js │ ├── educationAction.js │ └── experienceAction.js ├── index.js └── App.js ├── readmeResources ├── home.PNG ├── aboutme.PNG ├── contact.PNG ├── footer.PNG ├── skills.PNG ├── adminlogin.PNG ├── education.PNG ├── experience.PNG ├── projects.PNG ├── skilsAdmin.PNG ├── addEducation.PNG ├── contactAdmin.PNG ├── projectAdmin.PNG ├── adminDashboead.PNG ├── editEdication.PNG ├── educationAdmin.PNG └── experienceAdmin.PNG ├── .gitignore ├── package.json └── README.md /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/public/logo512.png -------------------------------------------------------------------------------- /src/shared/history.js: -------------------------------------------------------------------------------- 1 | import {createBrowserHistory} from 'history' 2 | 3 | export default createBrowserHistory(); -------------------------------------------------------------------------------- /readmeResources/home.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/readmeResources/home.PNG -------------------------------------------------------------------------------- /src/assets/images/dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/src/assets/images/dev.png -------------------------------------------------------------------------------- /readmeResources/aboutme.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/readmeResources/aboutme.PNG -------------------------------------------------------------------------------- /readmeResources/contact.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/readmeResources/contact.PNG -------------------------------------------------------------------------------- /readmeResources/footer.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/readmeResources/footer.PNG -------------------------------------------------------------------------------- /readmeResources/skills.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/readmeResources/skills.PNG -------------------------------------------------------------------------------- /readmeResources/adminlogin.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/readmeResources/adminlogin.PNG -------------------------------------------------------------------------------- /readmeResources/education.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/readmeResources/education.PNG -------------------------------------------------------------------------------- /readmeResources/experience.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/readmeResources/experience.PNG -------------------------------------------------------------------------------- /readmeResources/projects.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/readmeResources/projects.PNG -------------------------------------------------------------------------------- /readmeResources/skilsAdmin.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/readmeResources/skilsAdmin.PNG -------------------------------------------------------------------------------- /src/assets/images/profil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/src/assets/images/profil.png -------------------------------------------------------------------------------- /readmeResources/addEducation.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/readmeResources/addEducation.PNG -------------------------------------------------------------------------------- /readmeResources/contactAdmin.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/readmeResources/contactAdmin.PNG -------------------------------------------------------------------------------- /readmeResources/projectAdmin.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/readmeResources/projectAdmin.PNG -------------------------------------------------------------------------------- /src/assets/images/basry-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/src/assets/images/basry-logo.png -------------------------------------------------------------------------------- /src/assets/images/education.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/src/assets/images/education.png -------------------------------------------------------------------------------- /src/assets/images/experience.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/src/assets/images/experience.png -------------------------------------------------------------------------------- /readmeResources/adminDashboead.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/readmeResources/adminDashboead.PNG -------------------------------------------------------------------------------- /readmeResources/editEdication.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/readmeResources/editEdication.PNG -------------------------------------------------------------------------------- /readmeResources/educationAdmin.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/readmeResources/educationAdmin.PNG -------------------------------------------------------------------------------- /readmeResources/experienceAdmin.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oussamabasry/portfolio-frontend-react/HEAD/readmeResources/experienceAdmin.PNG -------------------------------------------------------------------------------- /src/components/User/About/About.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .text-font{ 4 | font-size: large; 5 | font-family: 'Itim', cursive; 6 | text-align: justify; 7 | } -------------------------------------------------------------------------------- /src/apis/userApi.js: -------------------------------------------------------------------------------- 1 | import api from "./serverApi"; 2 | 3 | export const loginApi = (authData) => { 4 | return api.post("/users/login", authData); 5 | }; 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/pages/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function Home() { 4 | return ( 5 |
6 |

Home

7 |
8 | ); 9 | } 10 | 11 | export default Home; 12 | -------------------------------------------------------------------------------- /src/shared/authorization.js: -------------------------------------------------------------------------------- 1 | const auth = () => { 2 | const user = JSON.parse(localStorage.getItem("userData")); 3 | if (user && user.isLogged) { 4 | return user.isLogged; 5 | } 6 | return false; 7 | }; 8 | 9 | export default auth; 10 | -------------------------------------------------------------------------------- /src/components/User/Footer/Footer.css: -------------------------------------------------------------------------------- 1 | .foot { 2 | background-color: #282d32; 3 | } 4 | 5 | .title { 6 | color: white; 7 | } 8 | 9 | .sub-title { 10 | color: #dddddd; 11 | } 12 | 13 | .icons-color { 14 | color: #fff; 15 | } 16 | 17 | .link-hover :hover { 18 | color: #00ffd4; 19 | } 20 | -------------------------------------------------------------------------------- /src/pages/NotFound.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const NotFound = () => { 4 | return ( 5 |
6 |

Error 404 :{" "}Page Not Found

7 |
8 | ); 9 | }; 10 | 11 | export default NotFound; 12 | -------------------------------------------------------------------------------- /src/reducers/message.js: -------------------------------------------------------------------------------- 1 | const messageReducer = (state = [], action) => { 2 | switch (action.type) { 3 | case "GET_MESSAGES": 4 | return action.payload; 5 | case "UPDATE_MESSAGE": 6 | return state.map((msg) => 7 | msg._id === action.payload._id ? action.payload : msg 8 | ); 9 | default: 10 | return state; 11 | } 12 | }; 13 | 14 | export default messageReducer; 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/apis/messageApi.js: -------------------------------------------------------------------------------- 1 | import api from "./serverApi"; 2 | 3 | export const getMessagesApi = () => { 4 | return api.get("/messages/", { 5 | headers: { 6 | Authorization: `Bearer ${ 7 | JSON.parse(localStorage.getItem("userData")).token 8 | }`, 9 | }, 10 | }); 11 | }; 12 | 13 | export const updateMessageApi = (messageId, message) => { 14 | return api.put(`/messages/${messageId}`, message); 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/User/PageIntro/styles.css: -------------------------------------------------------------------------------- 1 | .rgba-gradient { 2 | background-color: #eee; 3 | } 4 | 5 | .header { 6 | height: 100vh; 7 | color: #fff; 8 | } 9 | 10 | .dev-img { 11 | animation: move 2.5s ease-in-out infinite; 12 | } 13 | 14 | @keyframes move { 15 | 0%, 16 | 100% { 17 | transform: translateY(0); 18 | } 19 | 50% { 20 | transform: translateY(40px); 21 | transform: translateX(20px); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | background-color: #eeeeee; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /src/apis/skillApi.js: -------------------------------------------------------------------------------- 1 | import api from "./serverApi"; 2 | 3 | export const addSkillApi = (skill) => { 4 | return api.post("/skills/", skill); 5 | }; 6 | 7 | export const getSkillsApi = () => { 8 | return api.get("/skills/"); 9 | }; 10 | 11 | export const deleteSkillApi = (skillId) => { 12 | return api.delete(`/skills/${skillId}`); 13 | }; 14 | 15 | export const updateSkillApi = (skillId, skill) => { 16 | return api.put(`/skills/${skillId}`, skill); 17 | }; 18 | -------------------------------------------------------------------------------- /src/apis/serverApi.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const api = axios.create({ 4 | baseURL: "https://basryback.herokuapp.com", 5 | }); 6 | 7 | export const setAuthorizationToken = (token) => { 8 | if (token) { 9 | axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; 10 | } else { 11 | delete axios.defaults.headers.common["Authorization"]; 12 | } 13 | }; 14 | 15 | export default api; 16 | 17 | export const domainName= "https://basryback.herokuapp.com/"; 18 | -------------------------------------------------------------------------------- /src/apis/projectApi.js: -------------------------------------------------------------------------------- 1 | import api from "./serverApi"; 2 | 3 | export const addProjectApi = (project) => { 4 | return api.post("/projects/", project); 5 | }; 6 | 7 | export const getProjectsApi = () => { 8 | return api.get("/projects/"); 9 | }; 10 | 11 | export const deleteProjectApi = (projectId) => { 12 | return api.delete(`/projects/${projectId}`); 13 | }; 14 | 15 | export const updateProjectApi = (projectId, project) => { 16 | return api.put(`/projects/${projectId}`, project); 17 | }; 18 | -------------------------------------------------------------------------------- /src/apis/educationApi.js: -------------------------------------------------------------------------------- 1 | import api from "./serverApi"; 2 | 3 | export const addEducationApi = (education) => { 4 | return api.post("/educations/", education); 5 | }; 6 | 7 | export const getEducationsApi = () => { 8 | return api.get("/educations/"); 9 | }; 10 | 11 | export const deleteEducationApi = (educationId) => { 12 | return api.delete(`/educations/${educationId}`); 13 | }; 14 | 15 | export const updateEducationApi = (educationId, education) => { 16 | return api.put(`/educations/${educationId}`, education); 17 | }; 18 | -------------------------------------------------------------------------------- /src/apis/experienceApi.js: -------------------------------------------------------------------------------- 1 | import api from "./serverApi"; 2 | 3 | export const addExperienceApi = (experience) => { 4 | return api.post("/experiences/", experience); 5 | }; 6 | 7 | export const getExperiencesApi = () => { 8 | return api.get("/experiences/"); 9 | }; 10 | 11 | export const deleteExperienceApi = (experienceId) => { 12 | return api.delete(`/experiences/${experienceId}`); 13 | }; 14 | 15 | export const updateExperienceApi = (experienceId, experience) => { 16 | return api.put(`/experiences/${experienceId}`, experience); 17 | }; 18 | -------------------------------------------------------------------------------- /src/components/User/Login/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --input-padding-x: 1.5rem; 3 | --input-padding-y: 0.75rem; 4 | } 5 | 6 | 7 | 8 | .btn-login { 9 | font-size: 0.9rem; 10 | letter-spacing: 0.05rem; 11 | padding: 0.75rem 1rem; 12 | border-radius: 2rem; 13 | } 14 | 15 | .form-label-group { 16 | position: relative; 17 | margin-bottom: 1rem; 18 | } 19 | 20 | .form-label-group > input, 21 | .form-label-group > label { 22 | padding: var(--input-padding-y) var(--input-padding-x); 23 | height: auto; 24 | border-radius: 2rem; 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/reducers/skill.js: -------------------------------------------------------------------------------- 1 | const skillReducer = (state = [], action) => { 2 | switch (action.type) { 3 | case "GET_SKILLS": 4 | return action.payload; 5 | 6 | case "ADD_SKILL": 7 | return [...state, action.payload]; 8 | 9 | case "DELETE_SKILL": 10 | return state.filter((ski) => ski._id !== action.payload); 11 | 12 | case "UPDATE_SKILL": 13 | return state.map((ski) => 14 | ski._id === action.payload._id ? action.payload : ski 15 | ); 16 | default: 17 | return state; 18 | } 19 | }; 20 | 21 | export default skillReducer; 22 | -------------------------------------------------------------------------------- /src/reducers/project.js: -------------------------------------------------------------------------------- 1 | const projectReducer = (state = [], action) => { 2 | switch (action.type) { 3 | case "GET_PROJECTS": 4 | return action.payload; 5 | 6 | case "ADD_PROJECT": 7 | return [...state, action.payload]; 8 | 9 | case "DELETE_PROJECT": 10 | return state.filter((proj) => proj._id !== action.payload); 11 | 12 | case "UPDATE_PROJECT": 13 | return state.map((proj) => 14 | proj._id === action.payload._id ? action.payload : proj 15 | ); 16 | default: 17 | return state; 18 | } 19 | }; 20 | 21 | export default projectReducer; 22 | -------------------------------------------------------------------------------- /src/reducers/education.js: -------------------------------------------------------------------------------- 1 | const educationReducer = (state = [], action) => { 2 | switch (action.type) { 3 | case "GET_EDUCATIONS": 4 | return action.payload; 5 | 6 | case "ADD_EDUCATION": 7 | return [...state, action.payload]; 8 | 9 | case "DELETE_EDUCATION": 10 | return state.filter((edu) => edu._id !== action.payload); 11 | 12 | case "UPDATE_EDUCATION": 13 | return state.map((edu) => 14 | edu._id === action.payload._id ? action.payload : edu 15 | ); 16 | default: 17 | return state; 18 | } 19 | }; 20 | 21 | export default educationReducer; 22 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/reducers/experience.js: -------------------------------------------------------------------------------- 1 | const experienceReducer = (state = [], action) => { 2 | switch (action.type) { 3 | case "GET_EXPERIENCES": 4 | return action.payload; 5 | 6 | case "ADD_EXEPERIENCE": 7 | return [...state, action.payload]; 8 | 9 | case "DELETE_EXEPERIENCE": 10 | return state.filter((exp) => exp._id !== action.payload); 11 | 12 | case "UPDATE_EXEPERIENCE": 13 | return state.map((exp) => 14 | exp._id === action.payload._id ? action.payload : exp 15 | ); 16 | default: 17 | return state; 18 | } 19 | }; 20 | 21 | export default experienceReducer; 22 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import loggedReducer from "./isLogged"; 2 | import educationReducer from "./education"; 3 | import experienceReducer from "./experience"; 4 | import skillReducer from "./skill"; 5 | import projectReducer from "./project"; 6 | import messageReducer from "./message"; 7 | import { combineReducers } from "redux"; 8 | 9 | const allReducers = combineReducers({ 10 | educations: educationReducer, 11 | experiences: experienceReducer, 12 | skills: skillReducer, 13 | projects: projectReducer, 14 | messages: messageReducer, 15 | login: loggedReducer, 16 | }); 17 | 18 | export default allReducers; 19 | -------------------------------------------------------------------------------- /src/reducers/isLogged.js: -------------------------------------------------------------------------------- 1 | const loggedReducer = ( 2 | state = { isLogin: localStorage.getItem("isLogged"), token: localStorage.getItem("token") }, 3 | action 4 | ) => { 5 | switch (action.type) { 6 | case "SIGN_IN": 7 | return { 8 | ...state, 9 | isLogin: action.payload.isLogin, 10 | token: action.payload.token, 11 | }; 12 | case "LOGOUT": 13 | return { 14 | ...state, 15 | isLogin: action.payload.isLogin, 16 | token: action.payload.token, 17 | }; 18 | default: 19 | return state; 20 | } 21 | }; 22 | 23 | export default loggedReducer; 24 | -------------------------------------------------------------------------------- /src/shared/toast.js: -------------------------------------------------------------------------------- 1 | import { toast } from "react-toastify"; 2 | 3 | export const toastSuccess = (message) => { 4 | toast.success(message, { 5 | position: "top-right", 6 | autoClose: 7000, 7 | hideProgressBar: false, 8 | closeOnClick: true, 9 | pauseOnHover: true, 10 | draggable: true, 11 | progress: undefined, 12 | }); 13 | }; 14 | 15 | export const toastError = (message) => { 16 | toast.error(message, { 17 | position: "top-right", 18 | autoClose: 7000, 19 | hideProgressBar: false, 20 | closeOnClick: true, 21 | pauseOnHover: true, 22 | draggable: true, 23 | progress: undefined, 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /src/shared/SecureRoute.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Redirect, Route } from "react-router-dom"; 3 | import isLogin from "./authorization"; 4 | 5 | const SecureRoute = (props) => { 6 | const [isLogged] = useState(isLogin); 7 | return ( 8 | 11 | isLogged ? ( 12 | 13 | ) : ( 14 | 17 | ) 18 | } 19 | > 20 | ); 21 | }; 22 | 23 | export default SecureRoute; 24 | -------------------------------------------------------------------------------- /src/actions/messageAction.js: -------------------------------------------------------------------------------- 1 | import { getMessagesApi, updateMessageApi } from "../apis/messageApi"; 2 | 3 | export const getMessages = () => async (dispatch) => { 4 | try { 5 | const { data } = await getMessagesApi(); 6 | dispatch({ type: "GET_MESSAGES", payload: data }); 7 | } catch (error) { 8 | console.log(error); 9 | } 10 | }; 11 | 12 | export const updateMessage = (id, message) => async (dispatch) => { 13 | try { 14 | const { data } = await updateMessageApi(id, message); 15 | dispatch({ 16 | type: "UPDATE_MESSAGE", 17 | payload: { ...message, _id: data.message._id }, 18 | }); 19 | } catch (error) { 20 | console.log(error); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/components/User/Navbar/styles.css: -------------------------------------------------------------------------------- 1 | 2 | .rgba-gradient-nav { 3 | background-color: #eee !important; 4 | } 5 | .navbar-scroll-color{ 6 | background-color: #fff !important; 7 | } 8 | 9 | .rgba-gradient { 10 | background-color: #eee; 11 | } 12 | 13 | 14 | ul li { 15 | text-align: center; 16 | padding: 0 10px 0 0; 17 | } 18 | 19 | ul li a { 20 | color: #fff !important; 21 | font-size: 13px; 22 | } 23 | 24 | .navbar .navbar-nav > li > a:hover, 25 | .navbar-default .navbar-nav > li > a:focus { 26 | color: #0275d8 !important; 27 | } 28 | 29 | .navbar { 30 | background-color: #fff; 31 | } 32 | 33 | .nav-link, 34 | .navbar-brand { 35 | color: black !important; 36 | font-size: 15px; 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/components/User/Skills/Skills.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import SkillBar from "react-skillbars"; 3 | import { useSelector, useDispatch } from "react-redux"; 4 | import { getSkills } from "../../../actions/skillAction"; 5 | 6 | const Skills = ({ reff }) => { 7 | const skills = useSelector((state) => state.skills); 8 | const dispatch = useDispatch(); 9 | 10 | useEffect(() => { 11 | dispatch(getSkills()); 12 | }, [dispatch]); 13 | 14 | const colors = { 15 | bar: "#3498db", 16 | title: { 17 | text: "#fff", 18 | background: "#2980b9", 19 | }, 20 | }; 21 | 22 | return ( 23 |
24 |
25 |

26 | Skills 27 |

28 | 33 |
34 |
35 | ); 36 | }; 37 | 38 | export default Skills; 39 | -------------------------------------------------------------------------------- /src/actions/loginAction.js: -------------------------------------------------------------------------------- 1 | import { loginApi } from "../apis/userApi"; 2 | import { toastSuccess, toastError } from "../shared/toast"; 3 | import history from "../shared/history"; 4 | import api from "../apis/serverApi"; 5 | 6 | export const loginUser = (authData) => async (dispatch) => { 7 | try { 8 | const { data } = await loginApi(authData); 9 | api.defaults.headers.common["Authorization"] = `Bearer ${data.token}`; 10 | localStorage.setItem( 11 | "userData", 12 | JSON.stringify({ 13 | token: data.token, 14 | isLogged: true, 15 | }) 16 | ); 17 | toastSuccess("Login Successfully"); 18 | dispatch({ 19 | type: "SIGN_IN", 20 | payload: { isLogin: true, token: data.token }, 21 | }); 22 | history.push("/education"); 23 | } catch (error) { 24 | console.log(error); 25 | toastError("Incorrect email or password"); 26 | } 27 | }; 28 | 29 | export const logoutUser = () => { 30 | delete api.defaults.headers.common["Authorization"]; 31 | localStorage.setItem( 32 | "userData", 33 | JSON.stringify({ 34 | token: null, 35 | isLogged: false, 36 | }) 37 | ); 38 | return { 39 | type: "LOGOUT", 40 | payload: { isLogin: false, token: null }, 41 | }; 42 | }; 43 | -------------------------------------------------------------------------------- /src/components/Admin/SideBar/SidebarData.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import * as FaIcons from "react-icons/fa"; 3 | import * as AiIcons from "react-icons/ai"; 4 | import * as IoIcons from "react-icons/io"; 5 | import * as FcIcons from "react-icons/fc"; 6 | import * as GoIcons from "react-icons/go"; 7 | 8 | export const SidebarData = [ 9 | { 10 | title: "Home", 11 | path: "/", 12 | icon: , 13 | cName: "nav-text", 14 | }, 15 | { 16 | title: "Educations", 17 | path: "/education", 18 | icon: , 19 | cName: "nav-text", 20 | }, 21 | { 22 | title: "Experiences", 23 | path: "/experience", 24 | icon: , 25 | cName: "nav-text", 26 | }, 27 | { 28 | title: "Skills", 29 | path: "/skill", 30 | icon: , 31 | cName: "nav-text", 32 | }, 33 | { 34 | title: "Projects", 35 | path: "/project", 36 | icon: , 37 | cName: "nav-text", 38 | }, 39 | { 40 | title: "Messages", 41 | path: "/messages", 42 | icon: , 43 | cName: "nav-text", 44 | }, 45 | { 46 | title: "Logout", 47 | path: "/support", 48 | icon: , 49 | cName: "nav-text", 50 | }, 51 | ]; 52 | -------------------------------------------------------------------------------- /src/components/Admin/SideBar/SideBar.css: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | background-color: #060b26; 3 | height: 80px; 4 | display: flex; 5 | justify-content: flex-start; 6 | align-items: center; 7 | } 8 | 9 | .menu-bars { 10 | margin-left: 2rem; 11 | font-size: 2rem; 12 | background: none; 13 | } 14 | 15 | .nav-menu { 16 | background-color: #060b26; 17 | width: 250px; 18 | height: 100vh; 19 | display: flex; 20 | justify-content: center; 21 | position: fixed; 22 | top: 0; 23 | left: -100%; 24 | transition: 850ms; 25 | } 26 | 27 | .nav-menu.active { 28 | left: 0; 29 | transition: 350ms; 30 | } 31 | 32 | .nav-text { 33 | display: flex; 34 | justify-content: flex-start; 35 | align-items: center; 36 | padding: 8px 0px 8px 16px; 37 | list-style: none; 38 | height: 60px; 39 | } 40 | 41 | .nav-text a { 42 | text-decoration: none; 43 | color: #f5f5f5; 44 | font-size: 18px; 45 | width: 95%; 46 | height: 100%; 47 | display: flex; 48 | align-items: center; 49 | padding: 0 16px; 50 | border-radius: 4px; 51 | } 52 | 53 | .nav-text a:hover { 54 | background-color: #1a83ff; 55 | } 56 | 57 | .nav-menu-items { 58 | width: 100%; 59 | } 60 | 61 | .sidebar-toggle { 62 | background-color: #060b26; 63 | width: 100%; 64 | height: 80px; 65 | display: flex; 66 | justify-content: flex-start; 67 | align-items: center; 68 | } 69 | 70 | span { 71 | margin-left: 16px; 72 | } 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "portfolio-frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.12.0", 7 | "@testing-library/react": "^11.2.6", 8 | "@testing-library/user-event": "^12.8.3", 9 | "axios": "^0.21.1", 10 | "jquery": "^3.6.0", 11 | "moment": "^2.29.1", 12 | "react": "^17.0.2", 13 | "react-datepicker": "^3.8.0", 14 | "react-dom": "^17.0.2", 15 | "react-hook-form": "^7.3.6", 16 | "react-icons": "^4.2.0", 17 | "react-modal-image": "^2.5.0", 18 | "react-redux": "^7.2.4", 19 | "react-router-dom": "^5.2.0", 20 | "react-scripts": "4.0.3", 21 | "react-skillbars": "^1.6.1", 22 | "react-toastify": "^7.0.4", 23 | "redux": "^4.1.0", 24 | "redux-thunk": "^2.3.0", 25 | "web-vitals": "^1.1.1" 26 | }, 27 | "scripts": { 28 | "start": "react-scripts start", 29 | "build": "react-scripts build", 30 | "test": "react-scripts test", 31 | "eject": "react-scripts eject" 32 | }, 33 | "eslintConfig": { 34 | "extends": [ 35 | "react-app", 36 | "react-app/jest" 37 | ] 38 | }, 39 | "browserslist": { 40 | "production": [ 41 | ">0.2%", 42 | "not dead", 43 | "not op_mini all" 44 | ], 45 | "development": [ 46 | "last 1 chrome version", 47 | "last 1 firefox version", 48 | "last 1 safari version" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/actions/skillAction.js: -------------------------------------------------------------------------------- 1 | import { 2 | addSkillApi, 3 | getSkillsApi, 4 | deleteSkillApi, 5 | updateSkillApi, 6 | } from "../apis/skillApi"; 7 | import { toastSuccess, toastError } from "../shared/toast"; 8 | 9 | export const getSkills = () => async (dispatch) => { 10 | try { 11 | const { data } = await getSkillsApi(); 12 | dispatch({ type: "GET_SKILLS", payload: data }); 13 | } catch (error) { 14 | console.log(error); 15 | } 16 | }; 17 | 18 | export const addSkill = (skill) => async (dispatch) => { 19 | try { 20 | const { data } = await addSkillApi(skill); 21 | dispatch({ type: "ADD_SKILL", payload: data }); 22 | toastSuccess("Skill Added Successfully"); 23 | } catch (error) { 24 | console.log(error); 25 | toastError("Error while adding Skill"); 26 | } 27 | }; 28 | 29 | export const deleteSkill = (id) => async (dispatch) => { 30 | try { 31 | await deleteSkillApi(id); 32 | dispatch({ type: "DELETE_SKILL", payload: id }); 33 | toastSuccess("Skill deleted successfully"); 34 | } catch (error) { 35 | console.log(error); 36 | toastError("Error while deleting Skill"); 37 | } 38 | }; 39 | 40 | export const updateSkill = (id, skill) => async (dispatch) => { 41 | try { 42 | const { data } = await updateSkillApi(id, skill); 43 | dispatch({ 44 | type: "UPDATE_SKILL", 45 | payload: { ...skill, _id: data.skill._id }, 46 | }); 47 | toastSuccess("Skill updated successfully"); 48 | } catch (error) { 49 | console.log(error); 50 | toastError("Error while updating Skill"); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /src/components/User/About/About.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import profil from "../../../assets/images/profil.png"; 3 | import "./About.css"; 4 | 5 | const About = ({ reff }) => { 6 | return ( 7 |
13 |
14 |

15 | About Me 16 |

17 | 18 |
19 |
20 | profil 21 |
22 |
23 |

24 | I am an engineering student in the second year of Software 25 | Engineering and Distributed Computer Systems at the Higher Normal 26 | School of Technical Education Mohammedia (ENSET-M). 27 |
Through my university studies, I acquired strong skills in 28 | the field of software engineering. The various projects and 29 | internships that I have carried out have enabled me to develop not 30 | only my hard skills but also my soft skills. 31 |
Motivated, I know how to adapt, I have a sense of 32 | responsibility and organization. 33 |

34 |
35 |
36 |
37 |
38 | ); 39 | }; 40 | 41 | export default About; 42 | -------------------------------------------------------------------------------- /src/pages/SkillAdmin.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import { deleteSkill } from "../actions/skillAction"; 4 | import SkillModal from "../components/Admin/SkillModal"; 5 | import Table from "../components/Admin/Table"; 6 | 7 | const SkillAdmin = () => { 8 | const skills = useSelector((state) => state.skills); 9 | const dispatch = useDispatch(); 10 | const [selectedSkill, setSelectedSkill] = useState({ 11 | type: "", 12 | level: 0, 13 | }); 14 | 15 | const ondelteClick = (skill) => { 16 | dispatch(deleteSkill(skill._id)); 17 | }; 18 | 19 | const onEditClick = (skill) => { 20 | setSelectedSkill(skill); 21 | }; 22 | 23 | return ( 24 |
25 |
26 | 36 | 37 | 44 | 51 | 52 | ); 53 | }; 54 | 55 | export default SkillAdmin; 56 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import "react-toastify/dist/ReactToastify.css"; 5 | import App from "./App"; 6 | import reportWebVitals from "./reportWebVitals"; 7 | import { createStore, applyMiddleware, compose } from "redux"; 8 | import { Provider } from "react-redux"; 9 | import allReducers from "./reducers"; 10 | import thunk from "redux-thunk"; 11 | import { getEducations } from "./actions/educationAction"; 12 | import { getExperiences } from "./actions/experienceAction"; 13 | import { getprojects } from "./actions/projectAction"; 14 | import { getSkills } from "./actions/skillAction"; 15 | import api from "./apis/serverApi"; 16 | 17 | const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 18 | 19 | const store = createStore(allReducers, composeEnhancer(applyMiddleware(thunk))); 20 | store.dispatch(getEducations()); 21 | store.dispatch(getExperiences()); 22 | store.dispatch(getprojects()); 23 | store.dispatch(getSkills()); 24 | 25 | const user = JSON.parse(localStorage.getItem("userData")); 26 | 27 | if (user !== null) { 28 | api.defaults.headers.common["Authorization"] = `Bearer ${user.token}`; 29 | } 30 | 31 | ReactDOM.render( 32 | 33 | 34 | 35 | 36 | , 37 | document.getElementById("root") 38 | ); 39 | 40 | // If you want to start measuring performance in your app, pass a function 41 | // to log results (for example: reportWebVitals(console.log)) 42 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 43 | reportWebVitals(); 44 | -------------------------------------------------------------------------------- /src/actions/projectAction.js: -------------------------------------------------------------------------------- 1 | import { 2 | addProjectApi, 3 | getProjectsApi, 4 | deleteProjectApi, 5 | updateProjectApi, 6 | } from "../apis/projectApi"; 7 | import { toastSuccess, toastError } from "../shared/toast"; 8 | 9 | export const getprojects = () => async (dispatch) => { 10 | try { 11 | const { data } = await getProjectsApi(); 12 | dispatch({ type: "GET_PROJECTS", payload: data }); 13 | } catch (error) { 14 | console.log(error); 15 | } 16 | }; 17 | 18 | export const addProject = (project) => async (dispatch) => { 19 | try { 20 | const { data } = await addProjectApi(project); 21 | dispatch({ type: "ADD_PROJECT", payload: data }); 22 | toastSuccess("Project Added Successfully"); 23 | } catch (error) { 24 | console.log(error); 25 | toastError("Error while adding project"); 26 | } 27 | }; 28 | 29 | export const deleteProject = (id) => async (dispatch) => { 30 | try { 31 | await deleteProjectApi(id); 32 | dispatch({ type: "DELETE_PROJECT", payload: id }); 33 | toastSuccess("Project deleted Successfully"); 34 | } catch (error) { 35 | console.log(error); 36 | toastError("Error while deleting project"); 37 | } 38 | }; 39 | 40 | export const updateProject = (id, project) => async (dispatch) => { 41 | try { 42 | const { data } = await updateProjectApi(id, project); 43 | dispatch({ 44 | type: "UPDATE_PROJECT", 45 | payload: data.project, 46 | //payload:{...project,_id: data.project._id}, 47 | }); 48 | toastSuccess("Project Updated Successfully"); 49 | } catch (error) { 50 | console.log(error); 51 | toastError("Error while updating project"); 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /src/components/Admin/Modal.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Modal = ({ id, header, submitValue, colorButton, children, onClick }) => { 4 | return ( 5 |
6 | 47 |
48 | ); 49 | }; 50 | 51 | export default Modal; 52 | -------------------------------------------------------------------------------- /src/actions/educationAction.js: -------------------------------------------------------------------------------- 1 | import { 2 | addEducationApi, 3 | getEducationsApi, 4 | deleteEducationApi, 5 | updateEducationApi, 6 | } from "../apis/educationApi"; 7 | import { toastSuccess, toastError } from "../shared/toast"; 8 | 9 | export const getEducations = () => async (dispatch) => { 10 | try { 11 | const { data } = await getEducationsApi(); 12 | dispatch({ type: "GET_EDUCATIONS", payload: data }); 13 | } catch (error) { 14 | console.log(error); 15 | } 16 | }; 17 | 18 | export const addEducation = (education) => async (dispatch) => { 19 | try { 20 | const { data } = await addEducationApi(education); 21 | dispatch({ type: "ADD_EDUCATION", payload: data }); 22 | toastSuccess("Education Added Successfully"); 23 | } catch (error) { 24 | console.log(error); 25 | toastError("Error while adding education"); 26 | } 27 | }; 28 | 29 | export const deleteEducation = (id) => async (dispatch) => { 30 | try { 31 | await deleteEducationApi(id); 32 | toastSuccess("Education Deleted Successfully"); 33 | dispatch({ type: "DELETE_EDUCATION", payload: id }); 34 | } catch (error) { 35 | console.log(error); 36 | toastError("Error while deleting education"); 37 | } 38 | }; 39 | 40 | export const updateEducation = (id, education) => async (dispatch) => { 41 | try { 42 | const { data } = await updateEducationApi(id, education); 43 | dispatch({ 44 | type: "UPDATE_EDUCATION", 45 | payload: {...education,_id: data.education._id} 46 | }); 47 | toastSuccess("Education Updated Successfully"); 48 | } catch (error) { 49 | console.log(error); 50 | toastError("Error while Updated education"); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /src/actions/experienceAction.js: -------------------------------------------------------------------------------- 1 | import { 2 | addExperienceApi, 3 | getExperiencesApi, 4 | deleteExperienceApi, 5 | updateExperienceApi, 6 | } from "../apis/experienceApi"; 7 | import { toastSuccess, toastError } from "../shared/toast"; 8 | 9 | export const getExperiences = () => async (dispatch) => { 10 | try { 11 | const { data } = await getExperiencesApi(); 12 | dispatch({ type: "GET_EXPERIENCES", payload: data }); 13 | } catch (error) { 14 | console.log(error); 15 | } 16 | }; 17 | 18 | export const addExperience = (experience) => async (dispatch) => { 19 | try { 20 | const { data } = await addExperienceApi(experience); 21 | dispatch({ type: "ADD_EXEPERIENCE", payload: data }); 22 | toastSuccess("Experience Added Successfully"); 23 | } catch (error) { 24 | console.log(error); 25 | toastError("Error while adding experience"); 26 | } 27 | }; 28 | 29 | export const deleteExperience = (id) => async (dispatch) => { 30 | try { 31 | await deleteExperienceApi(id); 32 | dispatch({ type: "DELETE_EXEPERIENCE", payload: id }); 33 | toastSuccess("Experience deleted Successfully"); 34 | } catch (error) { 35 | console.log(error); 36 | toastError("Error while deleting experience"); 37 | } 38 | }; 39 | 40 | export const updateExperience = (id, experience) => async (dispatch) => { 41 | try { 42 | const { data } = await updateExperienceApi(id, experience); 43 | dispatch({ 44 | type: "UPDATE_EXEPERIENCE", 45 | payload:{...experience,_id: data.experience._id}, 46 | }); 47 | toastSuccess("Experience Updated Successfully"); 48 | } catch (error) { 49 | console.log(error); 50 | toastError("Error while updating experience"); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /src/pages/ProjectAdmin.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import { deleteProject } from "../actions/projectAction"; 4 | import ProjectModal from "../components/Admin/ProjectModal"; 5 | import Table from "../components/Admin/Table"; 6 | 7 | function ProjectAdmin() { 8 | const projects = useSelector((state) => state.projects); 9 | const dispatch = useDispatch(); 10 | const [selectedProject, setSelectedProject] = useState({ 11 | title: "", 12 | description: "", 13 | technologies: "", 14 | link: "", 15 | projectImage: "", 16 | }); 17 | 18 | const ondelteClick = (project) => { 19 | dispatch(deleteProject(project._id)); 20 | }; 21 | 22 | const onEditClick = (data) => { 23 | setSelectedProject(data); 24 | }; 25 | 26 | return ( 27 |
28 |
38 | 39 | 46 | 53 | 54 | ); 55 | } 56 | 57 | export default ProjectAdmin; 58 | -------------------------------------------------------------------------------- /src/pages/EducationAdmin.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import { deleteEducation } from "../actions/educationAction"; 4 | import EducationModal from "../components/Admin/EducationModal"; 5 | import Table from "../components/Admin/Table"; 6 | 7 | function EducationAdmin() { 8 | const educations = useSelector((state) => state.educations); 9 | const dispatch = useDispatch(); 10 | const [selectedEduca, setSelectedEduca] = useState({ 11 | title: "", 12 | school: "", 13 | city: "", 14 | startDate: "", 15 | endDate: "", 16 | }); 17 | 18 | const ondelteClick = (education) => { 19 | dispatch(deleteEducation(education._id)); 20 | }; 21 | 22 | const onEditClick = (data) => { 23 | setSelectedEduca(data); 24 | }; 25 | 26 | return ( 27 |
28 |
38 | 39 | 46 | 53 | 54 | ); 55 | } 56 | 57 | export default EducationAdmin; 58 | -------------------------------------------------------------------------------- /src/components/User/PageIntro/PageIntro.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./styles.css"; 3 | import homeImage from "../../../assets/images/dev.svg"; 4 | 5 | const PageIntro = ({ reff }) => { 6 | return ( 7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |

19 | Full Stack Web Developper 20 |

21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | home 37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | ); 46 | }; 47 | 48 | export default PageIntro; 49 | -------------------------------------------------------------------------------- /src/components/User/Education/Education.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import educationImage from "../../../assets/images/education.png"; 3 | import { useSelector, useDispatch } from "react-redux"; 4 | import { getEducations } from "../../../actions/educationAction"; 5 | import moment from "moment"; 6 | 7 | const Education = ({ reff }) => { 8 | const dispatch = useDispatch(); 9 | const educations = useSelector((state) => state.educations); 10 | 11 | useEffect(() => { 12 | dispatch(getEducations()); 13 | }, [dispatch]); 14 | 15 | const education = educations.map((edu) => { 16 | return ( 17 |
18 |

19 | {edu.title} 20 |

21 |

22 | {edu.school}, {edu.city} 23 |

24 |

25 | {moment(edu.startDate).format("MMM YYYY")} -{" "} 26 | {moment(edu.endDate).format("MMM YYYY")} 27 |

28 |
29 |
30 | ); 31 | }); 32 | 33 | return ( 34 |
40 |
41 |

42 | Education 43 |

44 | 45 |
46 |
47 | education 52 |
53 | 54 |
55 |
{education}
56 |
57 |
58 |
59 |
60 | ); 61 | }; 62 | 63 | export default Education; 64 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Router, Route, Switch } from "react-router-dom"; 3 | import { useSelector } from "react-redux"; 4 | import PortfolioUI from "./pages/PortfolioUI"; 5 | import Login from "./components/User/Login/Login"; 6 | import SideBar from "./components/Admin/SideBar/SideBar"; 7 | import EducationAdmin from "./pages/EducationAdmin"; 8 | import { ToastContainer } from "react-toastify"; 9 | import history from "./shared/history"; 10 | import SecureRoute from "./shared/SecureRoute"; 11 | import ExperienceAdmin from "./pages/ExperienceAdmin"; 12 | import SkillAdmin from "./pages/SkillAdmin"; 13 | import MessageAdmin from "./pages/MessageAdmin"; 14 | import isLogin from "./shared/authorization"; 15 | import ProjectAdmin from "./pages/ProjectAdmin"; 16 | import NotFound from "./pages/NotFound"; 17 | 18 | function App() { 19 | const [isLogged, setIsLogged] = useState(isLogin); 20 | const login = useSelector((state) => state.login.isLogin); 21 | 22 | useEffect(() => { 23 | setIsLogged(isLogin); 24 | }, [login]); 25 | 26 | return ( 27 |
28 | 29 | {isLogged && } 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 52 |
53 | ); 54 | } 55 | 56 | export default App; 57 | -------------------------------------------------------------------------------- /src/components/Admin/SideBar/SideBar.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { useHistory } from "react-router-dom"; 4 | import { logoutUser } from "../../../actions/loginAction"; 5 | import * as FaIcons from "react-icons/fa"; 6 | import * as AiIcons from "react-icons/ai"; 7 | import { Link } from "react-router-dom"; 8 | import { SidebarData } from "./SidebarData"; 9 | import "./SideBar.css"; 10 | import { IconContext } from "react-icons"; 11 | 12 | function SideBar() { 13 | const [sidebar, setSidebar] = useState(false); 14 | const dispatch = useDispatch(); 15 | const history = useHistory(); 16 | const showSidebar = () => setSidebar(!sidebar); 17 | 18 | const onLogout = () => { 19 | dispatch(logoutUser()); 20 | history.push("/"); 21 | }; 22 | 23 | return ( 24 | <> 25 | 26 |
27 | 28 | 29 | 30 |
31 | 57 |
58 | 59 | ); 60 | } 61 | 62 | export default SideBar; 63 | -------------------------------------------------------------------------------- /src/pages/ExperienceAdmin.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import { deleteExperience } from "../actions/experienceAction"; 4 | import ExperienceModal from "../components/Admin/ExperienceModal"; 5 | import Table from "../components/Admin/Table"; 6 | 7 | const ExperienceAdmin = () => { 8 | const experiences = useSelector((state) => state.experiences); 9 | const dispatch = useDispatch(); 10 | const [selectedExperience, setSelectedExperience] = useState({ 11 | title: "", 12 | company: "", 13 | city: "", 14 | startDate: "", 15 | endDate: "", 16 | description: "", 17 | technologies: "", 18 | }); 19 | 20 | const ondelteClick = (experience) => { 21 | dispatch(deleteExperience(experience._id)); 22 | }; 23 | 24 | const onEditClick = (experience) => { 25 | setSelectedExperience(experience); 26 | }; 27 | 28 | return ( 29 |
30 |
31 |
57 | 58 | 65 | 72 | 73 | ); 74 | }; 75 | 76 | export default ExperienceAdmin; 77 | -------------------------------------------------------------------------------- /src/components/User/Projects/Projects.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import { getprojects } from "../../../actions/projectAction"; 4 | import ModalImage from "react-modal-image"; 5 | import { domainName } from "../../../apis/serverApi"; 6 | 7 | const Projects = ({ reff }) => { 8 | const projects = useSelector((state) => state.projects); 9 | const dispatch = useDispatch(); 10 | 11 | useEffect(() => { 12 | dispatch(getprojects()); 13 | }, [dispatch]); 14 | 15 | const project = projects.map((proj) => { 16 | return ( 17 |
21 |
22 | 27 |
28 |
29 |

{proj.title}

30 |

{proj.description}

31 |

39 | Technologies: 40 |

41 |

{proj.technologies}

42 | {proj.haveLink && ( 43 | 51 | )} 52 |
53 |
54 | ); 55 | }); 56 | 57 | return ( 58 |
64 |
65 |

Projects

66 | 67 |
{project}
68 |
69 |
70 | ); 71 | }; 72 | 73 | export default Projects; 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Basry Portfolio Frontend Part 2 | 3 | Dynamic personal website with admin panel which contains educations, experiences, skills, projects, contact fields. 4 | ## Technologies 5 | ##### MERN STACK: Mongo DB, ExpressJS, ReactJS, NodeJS. 6 | 7 | ## Dependancies 8 | * react 9 | * redux 10 | * react-redux 11 | * redux-thunk 12 | * react-router-dom 13 | * react-hook-form 14 | * axios 15 | * moment 16 | * react-datepicker 17 | * react-dom 18 | * react-icons 19 | * react-modal-image 20 | * react-scripts 21 | * react-skillbars 22 | * react-toastify 23 | * web-vitals 24 | 25 | 26 | ## Project Screen Shots 27 | ### Intro 28 | ![Home page](readmeResources/home.PNG) 29 | ### About Me 30 | ![About Me](readmeResources/aboutme.PNG) 31 | ### Educations 32 | ![Education](readmeResources/education.PNG) 33 | ### Experiences 34 | ![Experience](readmeResources/experience.PNG) 35 | ### Skills 36 | ![Skills](readmeResources/skills.PNG) 37 | ### Projects 38 | ![Projects](readmeResources/projects.PNG) 39 | ### Contact Me 40 | ![Contact](readmeResources/contact.PNG) 41 | ### Footer 42 | ![Footer](readmeResources/footer.PNG) 43 | ### Login Admin Page 44 | ![Login](readmeResources/adminlogin.PNG) 45 | ### Dashboard Admin 46 | ![Dashboard](readmeResources/adminDashboead.PNG) 47 | ### Educations Admin 48 | ![Educations Admin](readmeResources/educationAdmin.PNG) 49 | ### Add Education 50 | ![Add Education](readmeResources/addEducation.PNG) 51 | ### Edit Education 52 | ![Edit Education](readmeResources/editEdication.PNG) 53 | ### Experiences Admin 54 | ![Experiences Admin](readmeResources/experienceAdmin.PNG) 55 | ### Skills Admin 56 | ![Skills Admin](readmeResources/skilsAdmin.PNG) 57 | ### Project Admin 58 | ![Project Admin](readmeResources/projectAdmin.PNG) 59 | ### Contact Admin 60 | ![Contact Admin](readmeResources/contactAdmin.PNG) 61 | 62 | 63 | ## Demo 64 | Click [Demo](https://basry.herokuapp.com/) to go to the demonstration page. 65 | 66 | ## Installation and Setup Instructions 67 | 68 | Clone down this repository. You will need `node` and `npm` installed globally on your machine. 69 | 70 | Installation: 71 | 72 | `npm install` 73 | 74 | 75 | To Start Server: 76 | 77 | `npm start` 78 | 79 | To Visit App: 80 | 81 | `localhost:3000/` 82 | 83 | ## Notes 84 | * please note that the application will not work before colone the API express, click [here](https://github.com/oussamabasry/portfolio-backend) to go to backend repository. 85 | * Change the server IP address in /src/app/serverApi.js file. 86 | 87 | -------------------------------------------------------------------------------- /src/pages/PortfolioUI.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from "react"; 2 | import Contacts from "../components/User/Contacts/Contacts"; 3 | import Education from "../components/User/Education/Education"; 4 | import Experience from "../components/User/Experience/Experience"; 5 | import About from "../components/User/About/About"; 6 | import Footer from "../components/User/Footer/Footer"; 7 | import PageIntro from "../components/User/PageIntro/PageIntro"; 8 | import Projects from "../components/User/Projects/Projects"; 9 | import Skills from "../components/User/Skills/Skills"; 10 | import isLogin from "../shared/authorization"; 11 | import { useSelector } from "react-redux"; 12 | import Navbar from "../components/User/Navbar/Navbar"; 13 | 14 | const PortfolioUI = () => { 15 | const [isLogged, setIsLogged] = useState(isLogin); 16 | const login = useSelector((state) => state.login.isLogin); 17 | const projectSection = useRef(null); 18 | const educationSection = useRef(null); 19 | const experienceSection = useRef(null); 20 | const skillSection = useRef(null); 21 | const contactSection = useRef(null); 22 | const homeSection = useRef(null); 23 | const aboutSection = useRef(null); 24 | 25 | useEffect(() => { 26 | setIsLogged(isLogin); 27 | }, [login]); 28 | 29 | const scrollUtil = (section) => { 30 | window.scrollTo({ 31 | top: section.current.offsetTop, 32 | behavior: "smooth", 33 | }); 34 | }; 35 | const onLinkClick = (section) => { 36 | switch (section) { 37 | case "projectSection": 38 | scrollUtil(projectSection); 39 | break; 40 | case "aboutSection": 41 | scrollUtil(aboutSection); 42 | break; 43 | case "educationSection": 44 | scrollUtil(educationSection); 45 | break; 46 | case "experienceSection": 47 | scrollUtil(experienceSection); 48 | break; 49 | case "skillSection": 50 | scrollUtil(skillSection); 51 | break; 52 | case "contactSection": 53 | scrollUtil(contactSection); 54 | break; 55 | case "homeSection": 56 | scrollUtil(homeSection); 57 | break; 58 | default: 59 | scrollUtil(homeSection); 60 | } 61 | }; 62 | 63 | return ( 64 |
65 | {!isLogged && } 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
74 |
75 | ); 76 | }; 77 | 78 | export default PortfolioUI; 79 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | 28 | 29 | 33 | 34 | 38 | 39 | 45 | Basry 46 | 47 | 48 | 49 |
50 | 60 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/components/User/Experience/Experience.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import experienceImage from "../../../assets/images/experience.png"; 3 | import { useSelector, useDispatch } from "react-redux"; 4 | import { getExperiences } from "../../../actions/experienceAction"; 5 | import moment from "moment"; 6 | 7 | const Experience = ({ reff }) => { 8 | const experiences = useSelector((state) => state.experiences); 9 | const dispatch = useDispatch(); 10 | 11 | useEffect(() => { 12 | dispatch(getExperiences()); 13 | }, [dispatch]); 14 | 15 | const experience = experiences.map((exp) => { 16 | return ( 17 |
18 |
19 |
20 |

21 | {exp.title} 22 |

23 |

24 | {exp.company}, {exp.city} 25 |

26 |

27 | {moment(exp.startDate).format("MMM YYYY")} -{" "} 28 | {moment(exp.endDate).format("MMM YYYY")} 29 |

30 |

31 | {exp.description} 32 |

33 |

41 | Technologies: 42 |

43 |

44 | {" "} 45 | {exp.technologies} 46 |

47 |
48 |
49 |
50 |
51 | ); 52 | }); 53 | 54 | return ( 55 |
61 |
62 |

63 | Experience 64 |

65 | 66 |
67 |
68 | experience 73 |
74 | 75 |
76 |
{experience}
77 |
78 |
79 |
80 |
81 | ); 82 | }; 83 | 84 | export default Experience; 85 | -------------------------------------------------------------------------------- /src/components/Admin/Table.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import moment from "moment"; 3 | import { domainName } from "../../apis/serverApi"; 4 | 5 | function Table({ 6 | title, 7 | tableData, 8 | headerText, 9 | headerProprities, 10 | idModalEdit, 11 | idModalAdd, 12 | ondelteClick, 13 | onEditClick, 14 | }) { 15 | const dataRows = tableData.map((data) => { 16 | return ( 17 |
18 | {headerProprities.map((prop, index) => { 19 | return ( 20 | 36 | ); 37 | })} 38 | 47 | 58 | 59 | ); 60 | }); 61 | 62 | const headersRow = headerText.map((head, index) => { 63 | return ( 64 | 67 | ); 68 | }); 69 | 70 | return ( 71 |
72 |
73 |

{title}

74 | 82 |
83 | 84 |
85 |
21 | {(prop === "startDate" || prop === "endDate") && 22 | moment(data[prop]).format("MMM YYYY")} 23 | {prop === "projectImage" && ( 24 | project 29 | )} 30 | {!( 31 | prop === "startDate" || 32 | prop === "endDate" || 33 | prop === "projectImage" 34 | ) && data[prop]} 35 | 39 | 46 | 48 | 57 |
65 | {head} 66 |
86 | 87 | 88 | {headersRow} 89 | 90 | 91 | 92 | 93 | {dataRows} 94 |
95 |
96 |
97 | ); 98 | } 99 | 100 | export default Table; 101 | -------------------------------------------------------------------------------- /src/components/User/Login/Login.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./styles.css"; 3 | import { useForm } from "react-hook-form"; 4 | import { useDispatch } from "react-redux"; 5 | import { loginUser } from "../../../actions/loginAction"; 6 | 7 | const Login = () => { 8 | const { 9 | register, 10 | handleSubmit, 11 | formState: { errors }, 12 | } = useForm(); 13 | 14 | const dispatch = useDispatch(); 15 | 16 | const onSubmit = async (data) => { 17 | dispatch(loginUser(data)); 18 | }; 19 | 20 | return ( 21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |

29 | Login only for administrator !{" "} 30 |

31 |
32 |
33 | 43 | {errors.email && errors.email.type === "required" && ( 44 |
45 | You must enter your email 46 |
47 | )} 48 | {errors.email && errors.email.type === "pattern" && ( 49 |
50 | You must enter a valid email 51 |
52 | )} 53 |
54 | 55 |
56 | 65 | 66 | {errors.password && 67 | errors.password.type === "required" && ( 68 |
69 | You must enter your password 70 |
71 | )} 72 |
73 | 74 | 79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | ); 88 | }; 89 | 90 | export default Login; 91 | -------------------------------------------------------------------------------- /src/pages/MessageAdmin.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import moment from "moment"; 4 | import { updateMessage, getMessages } from "../actions/messageAction"; 5 | 6 | const MessageAdmin = () => { 7 | const messages = useSelector((state) => state.messages); 8 | const dispatch = useDispatch(); 9 | const [selectedMessage, setSelectedMessage] = useState(null); 10 | 11 | useEffect(() => { 12 | dispatch(getMessages()); 13 | }, [dispatch]); 14 | 15 | const onMessageClick = (message) => { 16 | if (!message.isSeen) { 17 | dispatch(updateMessage(message._id, { ...message, isSeen: true })); 18 | } 19 | setSelectedMessage(message); 20 | }; 21 | 22 | const listMessages = messages.map((msg) => { 23 | return ( 24 |
onMessageClick(msg)} 30 | > 31 |
32 |
{msg.subject}
33 | {moment(msg.date).format("MMMM Do YYYY, h:mm:ss a")} 34 |
35 |

36 | {msg.message.substring(0, 100)} 37 | {" . . ."} 38 |

39 |

See More

40 |
41 |
42 | Send by: 43 | {msg.name} 44 |
45 | {!msg.isSeen && ( 46 | 1 47 | )} 48 |
49 |
50 | ); 51 | }); 52 | return ( 53 |
54 |
55 |
56 |

Message Details

57 | {selectedMessage && ( 58 |
62 |
63 | From: 64 | {selectedMessage.name} 65 |
66 | Date: 67 | {moment(selectedMessage.date).format( 68 | "MMMM Do YYYY, h:mm:ss a" 69 | )} 70 |
71 |
72 |
73 |
74 | Email: 75 |

{selectedMessage.email}

76 |
77 |
78 | Subject: 79 | 80 | {selectedMessage.subject} 81 | 82 |
83 |
84 | Message: 85 |

{selectedMessage.message}

86 |
87 |
88 |
89 | )} 90 |
91 |
92 |

Conversations

93 |
{listMessages}
94 |
95 |
96 |
97 | ); 98 | }; 99 | 100 | export default MessageAdmin; 101 | -------------------------------------------------------------------------------- /src/components/Admin/SkillModal.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useForm } from "react-hook-form"; 3 | import { useDispatch } from "react-redux"; 4 | import { addSkill, updateSkill } from "../../actions/skillAction"; 5 | 6 | const SkillModal = ({ id, header, skil, submitValue, colorButton }) => { 7 | const { 8 | register, 9 | handleSubmit, 10 | reset, 11 | setValue, 12 | } = useForm(); 13 | const dispatch = useDispatch(); 14 | 15 | useEffect(() => { 16 | if (id === "editSkill") { 17 | setValue("type", skil.type); 18 | setValue("level", skil.level); 19 | } 20 | }, [skil, id, setValue]); 21 | 22 | const onClick = (data) => { 23 | if (id === "editSkill") { 24 | dispatch(updateSkill(skil._id, data)); 25 | } else { 26 | dispatch(addSkill(data)); 27 | } 28 | reset(); 29 | }; 30 | return ( 31 |
32 | 113 |
114 | ); 115 | }; 116 | 117 | export default SkillModal; 118 | -------------------------------------------------------------------------------- /src/components/User/Navbar/Navbar.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import "../Navbar/styles.css"; 3 | import logo from "../../../assets/images/basry-logo.png"; 4 | import { Link } from "react-router-dom"; 5 | 6 | const Navbar = ({ onLinkClick }) => { 7 | const [toggler, setToggler] = useState(true); 8 | const [navbar, setNavbar] = useState(false); 9 | 10 | const changeBackground = () => { 11 | window.scrollY > window.screen.height - window.screen.height * 0.27 12 | ? setNavbar(true) 13 | : setNavbar(false); 14 | }; 15 | window.addEventListener("scroll", changeBackground); 16 | 17 | return ( 18 | 125 | ); 126 | }; 127 | 128 | export default Navbar; 129 | -------------------------------------------------------------------------------- /src/components/User/Footer/Footer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./Footer.css"; 3 | import { Link } from "react-router-dom"; 4 | 5 | const Footer = ({ onLinkClick }) => { 6 | return ( 7 |
8 |
9 |
10 |
11 |
12 | Oussama Basry 13 |
14 |

15 | I am an engineering student in the second year of Software 16 | Engineering and Distributed Computer Systems at the Higher Normal 17 | School of Technical Education Mohammedia (ENSET-M). 18 |

19 |
20 | 21 |
22 |
23 | Liens 24 |
25 |

26 | onLinkClick("educationSection")} 28 | className="sub-title" 29 | style={{ textDecoration: "none" }} 30 | to="/" 31 | > 32 | {" "} 33 | Education 34 | 35 |

36 |

37 | onLinkClick("experienceSection")} 39 | to="/" 40 | className="sub-title" 41 | style={{ textDecoration: "none" }} 42 | > 43 | Experience 44 | 45 |

46 | 47 |

48 | onLinkClick("projectSection")} 50 | to="/" 51 | className="sub-title" 52 | style={{ textDecoration: "none" }} 53 | > 54 | Projects 55 | 56 |

57 |

58 | onLinkClick("skillSection")} 60 | to="/" 61 | className="sub-title" 62 | style={{ textDecoration: "none" }} 63 | > 64 | Skills 65 | 66 |

67 |
68 | 69 |
70 |
71 | Contact 72 |
73 |

Morocco, Casablanca, Mohammedia

74 |

obasry@gmail.com

75 |

+212 6 50 56 38 27

76 |
77 |
78 | 79 |
80 | 81 |
82 |
83 |

84 | {" "} 85 | Copyright ©2021 All rights reserved by: 86 | 87 | Oussama Basry 88 | 89 |

90 |
91 | 92 |
93 |
94 | 140 |
141 |
142 |
143 |
144 |
145 | ); 146 | }; 147 | 148 | export default Footer; 149 | -------------------------------------------------------------------------------- /src/components/Admin/EducationModal.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useForm } from "react-hook-form"; 3 | import { useDispatch } from "react-redux"; 4 | import { addEducation, updateEducation } from "../../actions/educationAction"; 5 | import DatePicker from "react-datepicker"; 6 | import "react-datepicker/dist/react-datepicker.css"; 7 | 8 | const EducationModal = ({ id, header, edu, submitValue, colorButton }) => { 9 | const [startDate, setStartDate] = useState(new Date()); 10 | const [endDate, setEndDate] = useState(new Date()); 11 | const { register, handleSubmit, reset, setValue } = useForm(); 12 | const dispatch = useDispatch(); 13 | 14 | useEffect(() => { 15 | if (id === "editEducation") { 16 | setValue("title", edu.title); 17 | setValue("school", edu.school); 18 | setValue("city", edu.city); 19 | setStartDate(Date.parse(edu.startDate)); 20 | setStartDate(Date.parse(edu.endDate)); 21 | } 22 | }, [edu, id, setValue]); 23 | 24 | const onClick = (data) => { 25 | data.startDate = startDate; 26 | data.endDate = endDate; 27 | if (id === "editEducation") { 28 | dispatch(updateEducation(edu._id, data)); 29 | } else { 30 | dispatch(addEducation(data)); 31 | } 32 | reset(); 33 | }; 34 | return ( 35 |
36 | 153 |
154 | ); 155 | }; 156 | 157 | export default EducationModal; 158 | -------------------------------------------------------------------------------- /src/components/Admin/ProjectModal.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useForm } from "react-hook-form"; 3 | import { useDispatch } from "react-redux"; 4 | import { addProject, updateProject } from "../../actions/projectAction"; 5 | 6 | const ProjectModal = ({ id, header, proj, submitValue, colorButton }) => { 7 | const { register, handleSubmit, reset, setValue } = useForm(); 8 | const dispatch = useDispatch(); 9 | 10 | useEffect(() => { 11 | if (id === "editProject") { 12 | setValue("title", proj.title); 13 | setValue("description", proj.description); 14 | setValue("technologies", proj.technologies); 15 | setValue("haveLink", proj.haveLink); 16 | setValue("link", proj.link); 17 | } 18 | }, [proj, id, setValue]); 19 | 20 | const onClick = (data) => { 21 | const formData = new FormData(); 22 | formData.append("title", data.title); 23 | formData.append("description", data.description); 24 | formData.append("technologies", data.technologies); 25 | formData.append("haveLink", data.haveLink); 26 | formData.append("link", data.link); 27 | formData.append("projectImage", data.projectImage[0]); 28 | if (id === "editProject") { 29 | dispatch(updateProject(proj._id, formData)); 30 | } else { 31 | dispatch(addProject(formData)); 32 | } 33 | reset(); 34 | }; 35 | return ( 36 |
37 |