diff --git a/client/src/components/tutorComponents/ProfilePicModal.tsx b/client/src/components/tutorComponents/ProfilePicModal.tsx new file mode 100644 index 0000000..9617a14 --- /dev/null +++ b/client/src/components/tutorComponents/ProfilePicModal.tsx @@ -0,0 +1,88 @@ +import React from "react"; +import { api } from "../../services/api"; +import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from "reactstrap"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faEdit } from "@fortawesome/free-solid-svg-icons"; +import Avatar from "react-avatar-edit"; +import "../../containers/DashboardPage/ClientSettings.css"; +import defaultUser from "../../assets/default_user.png"; +import { CompressAndSaveImg } from "../../services/tools"; + + + export interface IProfilePicModalProps { + isTutor:boolean, + firstName:string, + lastName:string, + clientImg:string, + imgModalOpen:boolean, + croppedImg:string, + setImgModalOpen: (arg:boolean) => void, + cancelImgChange: () => void, + setCroppedImg: (arg:string) =>void, + setClientImg: (arg:string) =>void, + userid:string, +} + +export const ProfilePicModal = (props:IProfilePicModalProps) => { + + const {clientImg,imgModalOpen,croppedImg,userid} = props; + const {setImgModalOpen,cancelImgChange,setCroppedImg,setClientImg} = props; + + const handleImageSave = async (img: string) => { + + if (props.isTutor === true){ + console.log(userid) + await api.SetTutorProfileImage(img, userid); + } + else{ + + await api.SetClientProfileImage(img, userid); + + } + setClientImg(img); + } + + const saveImgChange = async () => { + if(croppedImg.toString() !== "") { + CompressAndSaveImg(croppedImg, props.firstName + props.lastName + "-photo", handleImageSave); + } else { + handleImageSave(croppedImg); + } + + setImgModalOpen(false); + } + return ( + <> + + setImgModalOpen(true)}> + + + {setImgModalOpen(!imgModalOpen)}} className="img-modal"> + {cancelImgChange()}}>Edit Profile Photo + + Change your profile photo here. +
+ setCroppedImg(img)} + onClose={() => {setCroppedImg("")}} + onBeforeFileLoad={() => {}} + src={clientImg} + /> +
+ + + + +
+ + + + ); + + +} \ No newline at end of file diff --git a/client/src/components/tutorComponents/Settings.tsx b/client/src/components/tutorComponents/Settings.tsx index 11d96bd..d0f9396 100644 --- a/client/src/components/tutorComponents/Settings.tsx +++ b/client/src/components/tutorComponents/Settings.tsx @@ -3,8 +3,13 @@ import classNames from "classnames"; import { Container, Row, Col, ListGroup, ListGroupItem, Button, Modal, ModalHeader, ModalBody, ModalFooter, Form, InputGroup, Input, Alert, Nav, NavItem, NavLink, TabContent, TabPane } from "reactstrap"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faEdit, faBan, faPlus, faCheck, faTimes } from "@fortawesome/free-solid-svg-icons"; +import {ProfilePicModal,IProfilePicModalProps} from './ProfilePicModal' +import { TutorDataSlice } from "../../store/TutorData/types"; +import { ClientDataSlice } from "../../store/ClientData/types"; +import { connect } from 'react-redux'; import Slider from 'react-rangeslider'; import Autocomplete from 'react-autocomplete'; +import defaultUser from "../../assets/default_user.png"; import TimePicker from 'rc-time-picker'; import moment from 'moment'; import Cookies from "js-cookie"; @@ -15,7 +20,8 @@ import "./settings.css"; import 'rc-time-picker/assets/index.css'; interface iSettingsProps { - + clientData:ClientDataSlice, + tutorData: TutorDataSlice } interface iSettingsState { @@ -49,11 +55,14 @@ interface iSettingsState { interval_modal: boolean, schedule_modal: boolean, add_time_err: boolean, - add_time_err_msg: string + add_time_err_msg: string, + imgModalOpen:boolean, + croppedImg:string } +//to-do: connect to Redux store Client + Tutor Data slice class Settings extends Component { constructor(props: iSettingsProps) { @@ -65,7 +74,7 @@ class Settings extends Component { temp_firstn: "", temp_lastn: "", email: "test2@gmail.com", - obj_id: userid !== undefined ? userid : "61a5a9bbc73a5d336d8d0b74", + obj_id: userid || "61a5a9bbc73a5d336d8d0b74", profile_pic: "", description: "Typescript port", temp_description: "", @@ -114,7 +123,9 @@ class Settings extends Component { interval_modal: false, schedule_modal: false, add_time_err: false, - add_time_err_msg: "" + add_time_err_msg: "", + imgModalOpen:false, + croppedImg:"" } as iSettingsState; } @@ -139,7 +150,7 @@ class Settings extends Component { temp_lastn: tutor.last_name, meeting_interval: parseInt(tutor.interval), temp_meeting_interval: parseInt(tutor.interval), - profile_pic:tutor.profile_img, + profile_pic:tutor.profile_img || defaultUser, description: tutor.description || "", temp_description: tutor.description || "" @@ -590,8 +601,26 @@ class Settings extends Component { return hrs + ":" + mins + meridiem; } + + + render(): JSX.Element{ let schedule_days = [{day: "Sunday", abbr: "SUN"}, {day: "Monday", abbr: "MON"}, {day: "Tuesday", abbr: "TUE"}, {day: "Wednesday", abbr: "WED"}, {day: "Thursday", abbr: "THU"}, {day: "Friday", abbr: "FRI"}, {day: "Saturday", abbr: "SAT"}]; + + const profilePicProps = { + isTutor:true, + firstName:this.state.first_name, + lastName:this.state.last_name, + clientImg:this.state.profile_pic, + imgModalOpen:this.state.imgModalOpen, + croppedImg:this.state.croppedImg, + setImgModalOpen: (arg:boolean) => this.setState({...this.state,imgModalOpen:arg}), + setCroppedImg: (arg:string) =>this.setState({...this.state,croppedImg:arg}), + setClientImg: (arg:string) =>this.setState({...this.state,profile_pic:arg}), + cancelImgChange: () => this.setState({...this.state,croppedImg:"",imgModalOpen:false}), + userid:this.state.obj_id + + } as IProfilePicModalProps; return ( @@ -601,9 +630,14 @@ class Settings extends Component { - + {/* {'profile + */} + + + + {this.state.first_name + " " + this.state.last_name} @@ -873,4 +907,11 @@ class Settings extends Component { } -export default Settings; \ No newline at end of file +function mapStateToProps(state: any) { + return { + tutorData: state.tutorData, + clientData: state.clientData + } +} + +export default connect(mapStateToProps)(Settings); \ No newline at end of file diff --git a/client/src/containers/DashboardPage/Dashboard.tsx b/client/src/containers/DashboardPage/Dashboard.tsx index 00bee2b..8245a42 100644 --- a/client/src/containers/DashboardPage/Dashboard.tsx +++ b/client/src/containers/DashboardPage/Dashboard.tsx @@ -9,9 +9,7 @@ import { actions as clientDataActions } from "../../store/ClientData/slice"; import { selectClientData } from "../../store/ClientData/selectors"; import {useDispatch, useSelector} from 'react-redux' import {selectSidebarToggled} from "../../store/ClientFlowData/selectors"; -import TutorPanelBlank from "./TutorPanelSignup"; import { api } from "../../services/api"; -import { Spinner } from "reactstrap"; import TutorPanelSignup from "./TutorPanelSignup"; export interface IParams { diff --git a/client/src/services/api.ts b/client/src/services/api.ts index 40c251e..6b8d4a5 100644 --- a/client/src/services/api.ts +++ b/client/src/services/api.ts @@ -41,7 +41,7 @@ export class ApiService { public async GetTutorById(id: String) { console.log("Fetching Tutor"); - let url = this.tutorsEndpoint + 'tutor/' + id; + let url = this.tutorsEndpoint + id; let response = await axios.get(url); let tutor: TutorsResponse = {data: []} tutor.data = response.data; @@ -197,6 +197,19 @@ export class ApiService { return await axios.put(url, body, {withCredentials: true}); } + + public async SetTutorProfileImage(img: String, id: String) { + let url = this.tutorsEndpoint + 'tutor'; + let body = { + userid: id, + profile_img: img + } + + return await axios.put(url, body, {withCredentials: true}); + } + + + public async SetMeetingLink(id: String, link: String) { let url = this.appointmentsEndpoint + 'link'; let body = { diff --git a/server/routes/api/tutors.js b/server/routes/api/tutors.js index 30c97af..946678a 100644 --- a/server/routes/api/tutors.js +++ b/server/routes/api/tutors.js @@ -21,6 +21,9 @@ let router = express.Router(); //Email notify admins of new tutor signup applications var emailsender = require("../../lib/emailsender.js"); +//dev mode +const developer = process.env.NODE_ENV !== 'production'; + //Models const mongoose = require('mongoose'); const Subject = require('../../models/Subject'); @@ -31,6 +34,10 @@ const TutorApplication = require('../../models/TutorApplication'); // Middleware const withAuth = require('../../middleware/token_auth') +const jwt = require('jsonwebtoken'); +const parseCookies = require("../../lib/parseCookies"); +const secret = require("../../config/secret"); +const passport = require('passport'); //mongoose.set('useFindAndModify', false); @@ -130,6 +137,23 @@ router.post("/", withAuth, (req, res) => { // PUT /api/tutors/tutor // Update a tutor router.put("/tutor", withAuth, (req, res) => { + + const token = parseCookies(req.headers.cookie).token + + //protect route + if(!!!developer){ + jwt.verify(token, secret, function (err, decoded) { + if (err) { + + res.status(401).send('Unauthorized: Invalid token'); + } else if (req.body.userid !== decoded.userid) { + res.status(403).send(`Forbidden: Access denied ${req.body.userid} ${decoded.userid}`); + + } else{ + true; + }}); + } + const entries = Object.keys(req.body) const updates = {} @@ -159,6 +183,22 @@ router.put("/tutor", withAuth, (req, res) => { // Update a tutor router.delete("/:tutor_id", withAuth, (req, res) => { + const token = parseCookies(req.headers.cookie).token + + //protect route + if(!!!developer){ + jwt.verify(token, secret, function (err, decoded) { + if (err) { + + res.status(401).send('Unauthorized: Invalid token'); + } else if (req.body.userid !== decoded.userid) { + res.status(403).send('Forbidden: Access denied'); + + } else{ + true; + }}); + } + Tutor.deleteOne( { _id: req.params.tutor_id } ) diff --git a/server/routes/api/users.js b/server/routes/api/users.js index 5c7c7b0..4df13db 100644 --- a/server/routes/api/users.js +++ b/server/routes/api/users.js @@ -20,10 +20,11 @@ const secret = require('../../config/secret'); * @namespace userRouter */ let router = express.Router(); - +//dev mode +const developer = process.env.NODE_ENV !== 'production'; // Middleware const withAuth = require('../../middleware/token_auth'); - +const parseCookies = require("../../lib/parseCookies"); /** * Route serving subjects form. * @name get/api/users @@ -143,6 +144,23 @@ router.post('/', async (req, res) => { // PUT /api/users/user // Update a user router.put("/user", withAuth, (req, res) => { + + const token = parseCookies(req.headers.cookie).token + + if(!!!developer){ + jwt.verify(token, secret, function (err, decoded) { + console.log(req.body.userid) + console.log(decoded.userid) + if (err) { + + res.status(401).send('Unauthorized: Invalid token'); + } else if (req.body.userid !== decoded.userid) { + res.status(403).send('Forbidden: Access denied'); + + } else{ + true; + }}); + } const entries = Object.keys(req.body) const updates = {}