import React, { createContext, useEffect, useState } from 'react'
import axios from 'axios'
import { ToastContainer, toast } from 'react-toastify'
import { loader, profileCheck } from '../helpers'
import { useHistory } from 'react-router-dom'
import PlanOfStudy from './PlanOfStudy'
import {
InstructorProvider,
useInstructorContext,
} from '../../scripts/instructorProfileContext'
import moment from 'moment'
/**
* Functional component for displaying the student's profile page
* @category User
* @function
* @component
* @param {React.FC<Props>} props - React props object
* @returns {React.ReactElement} The profile page of the user
*/
const Profile = (props) => {
const token = localStorage.getItem(process.env.REACT_APP_JWT)
const history = useHistory()
const {
match: { params },
} = props
profileCheck(token, history, params)
const instructor = useInstructorContext()
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
const [isInstructor, setIsInstructor] = useState(false)
const [showInstructor, setShowInstructor] = useState(false)
/**
* @summary Asynchronous function for fetching the user's profile based on the URL parameter containing the user's ID
* @function
* @returns {Object} The user's object
*/
const getUser = async () => {
let payload = {
// language=GraphQL
query: `{
user(id: "${params.id}" ){
firstName,
lastName,
middleName
email,
isAdmin,
dob,
plan{
id,
modules{
role
}
}
}
}`,
}
const data = await axios
.post(`${process.env.REACT_APP_API}/graphql`, payload, {
headers: {
'Content-Type': 'application/json',
},
})
.then((document) => {
return document.data.data.user
})
.catch((err) => {
toast.error(err.response.data.error, {
position: toast.POSITION.TOP_RIGHT,
})
})
setUser(data)
return data
}
/**
* @summary Asynchronous function for updating the user's profile. This function is called when the user submits the update profile form.
* @function
* @param {React.MouseEvent<HTMLButtonElement, MouseEvent>} e - The event object of the form
*/
const updateUser = async (e) => {
setLoading(true)
e.preventDefault()
if (user.dob) {
const dob = user.dob.split('/')
const mom = moment()
await mom.set({
year: dob[0],
month: dob[1],
date: dob[2],
})
await setUser({ ...user, dob: mom.unix() })
}
const payload = {
query: `mutation{
updateUser(input: {
id: "${params.id}",
middleName: "${user.middleName}",
firstName: "${user.firstName}",
lastName: "${user.lastName}",
email: "${user.email}",
password: "${user.password}",
passwordConf: "${user.passwordConf}",
dob: "${user.dob}",
${
isInstructor
? `instructorProfile: {
title: "${instructor.state.instructorProfile.title || ''}",
officeLocation: "${
instructor.state.instructorProfile
.officeLocation || ''
}",
officeHours: "${instructor.state.instructorProfile.officeHours || ''}",
contactPolicy: "${instructor.state.instructorProfile.contactPolicy || ''}",
phone: "${instructor.state.instructorProfile.phone || ''}",
background: "${instructor.state.instructorProfile.background || ''}",
researchInterest: "${
instructor.state.instructorProfile
.researchInterest || ''
}",
selectedPapersAndPublications: "${
instructor.state.instructorProfile
.selectedPapersAndPublications ||
''
}",
personalWebsite: "${
instructor.state.instructorProfile
.personalWebsite || ''
}",
philosophy: "${instructor.state.instructorProfile.philosophy || ''}"
}`
: ''
}
}){
firstName,
lastName,
middleName,
email,
dob,
instructorProfile{
title,
phone,
officeHours,
officeLocation,
}
}
}`,
}
await axios
.post(`${process.env.REACT_APP_API}/graphql`, payload, {
headers: {
'Content-Type': 'application/json',
},
})
.then((res) => {
if (res.data) {
setLoading(false)
setUser(res.data.data.updateUser)
toast.success('Your profile was updated!', {
position: toast.POSITION.TOP_RIGHT,
})
} else {
setLoading(false)
toast.error('There was an error updating your profile', {
position: toast.POSITION.TOP_RIGHT,
})
}
})
.catch((err) => {
setLoading(false)
err.response.data.errors.map((error) => {
toast.error(error.message, {
position: toast.POSITION.TOP_RIGHT,
})
})
})
}
/**
* @summary Asynchronous function for deleting the user's profile. This function is called when the user clicks on the delete account button.
* @function
* @param {React.MouseEvent<HTMLButtonElement, MouseEvent>} e - The event object of the form
*/
const deleteUser = async (e) => {
e.preventDefault()
const payload = {
query: `mutation{
deleteUser(id: "${params.id}"){
id
}
}`,
}
await axios
.post(`${process.env.REACT_APP_API}/graphql`, payload, {
headers: {
'Content-Type': 'application/json',
},
})
.then(() => {
setLoading(false)
props.history.push('/users/register')
})
.catch((err) => {
toast.error(err.response.data.error, {
position: toast.POSITION.TOP_RIGHT,
})
})
}
/**
* @summary Helper function for checking if the user has any enrollments as an instructor.
* @function
* @param {Object} usr - The user's profile object
*/
const toggleInstructor = (usr) => {
console.log(
'User: ',
usr.plan.modules.find((m) => m.role === 'GRADER')
)
user?.plan?.modules.map((module) => {
console.log(module)
if (module.role === 'TEACHER' || module.role === 'GRADER') {
setIsInstructor(true)
}
})
}
/**
* @summary Function that handles the change between user's profile and instructor's profile. This function dispatches a reducer action with a 'SET_INSTRUCTOR_PROFILE' `type` and a `payload` of the field name and value that was changed.
* @function
* @param {React.ChangeEvent<HTMLInputElement>} event - The event object of the click event
* @example
*
* const handleInstructorProfileChange = (event) => {
* instructor.dispatch({
* type: 'SET_INSTRUCTOR_PROFILE',
* payload: { [event.target.name]: event.target.value },
* })
* }
* //returns [...{title: "Chair of the Department of Computer Science"}]
*
* return (
* <input
* type="text"
* name="title"
* value="Chair of the Department of Computer Science"
* onChange={(event) => handleInstructorProfileChange(event)}
* />
* )
*/
const handleInstructorProfileChange = (event) => {
instructor.dispatch({
type: 'SET_INSTRUCTOR_PROFILE',
payload: { [event.target.name]: event.target.value },
})
}
useEffect(() => {
getUser().then((res) => {
toggleInstructor(res)
setLoading(false)
})
}, [showInstructor])
return loading ? (
loader()
) : (
<>
<div className="w-11/12 lg:w-3/4 mx-4 lg:mx-auto flex flex-col md:flex-row mt-3">
<ToastContainer />
<nav className="w-full md:w-1/4 mr-8 flex flex-col border border-gray-200 shadow-sm rounded-md h-full">
{isInstructor ? (
<button
onClick={() => setShowInstructor(!showInstructor)}
>
<li className="py-1 px-3 hover:bg-gray-100 border-b border-gray-300 list-none">
Switch to Professor
</li>
</button>
) : null}
<a className="text-base" href="#user">
<li className="py-1 px-3 hover:bg-gray-100 border-b border-gray-300 list-none">
User information
</li>
</a>
<a className="text-base" href="#modules">
<li className="py-1 px-3 hover:bg-gray-100 border-b border-gray-300 list-none">
Plan of Study
</li>
</a>
<a className="text-base" href="#security">
<li className="py-1 px-3 hover:bg-gray-100 border-b border-gray-300 list-none">
Security
</li>
</a>
<a className="text-base" href="#notifications">
<li className="py-1 px-3 hover:bg-gray-100 border-b border-gray-300 list-none">
Notifications
</li>
</a>
<a className="text-base" href="#kill">
<li className="py-1 px-3 hover:bg-gray-100 border-gray-300 list-none">
Close account
</li>
</a>
</nav>
<div className="w-full md:w-3/4">
<h3
id="user"
className="text-2xl bold border-b border-gray-100 mb-3"
>
Profile
</h3>
<form>
<div className="flex md:flex-row md:justify-between flex-col mb-3">
<label
htmlFor="name"
className="block flex-1 mr-2 font-bold"
>
First name
<input
className="bg-gray-50 border border-gray-200 rounded shadow-sm py-1 px-2 block w-full mt-1"
type="text"
placeholder="First name"
name="firstName"
value={user.firstName}
onChange={(e) =>
setUser({
...user,
firstName: e.target.value,
})
}
/>
</label>
<label
htmlFor="name"
className="block flex-1 mx-2 font-bold"
>
Middle name
<input
className="bg-gray-50 border border-gray-200 rounded shadow-sm py-1 px-2 block w-full mt-1"
type="text"
placeholder="Middle name"
name="middleName"
value={user.middleName}
onChange={(e) =>
setUser({
...user,
middleName: e.target.value,
})
}
/>
</label>
<label
htmlFor="name"
className="block flex-1 ml-2 font-bold"
>
Last name
<input
className="bg-gray-50 border border-gray-200 rounded shadow-sm py-1 px-2 block w-full mt-1"
type="text"
placeholder="Last name"
name="lastName"
value={user.lastName}
onChange={(e) =>
setUser({
...user,
lastName: e.target.value,
})
}
/>
</label>
</div>
<div className="w-full mb-3">
<label
htmlFor=""
className="block flex-1 font-bold"
>
Email
<input
className="bg-gray-50 border border-gray-200 rounded shadow-sm py-1 px-2 block w-full mt-1"
type="email"
placeholder="Email"
name="email"
value={user.email}
onChange={(e) =>
setUser({
...user,
email: e.target.value,
})
}
/>
</label>
</div>
<div className="w-full mb-3">
<label
htmlFor=""
className="block flex-1 font-bold"
>
Date of birth
<input
className="bg-gray-50 border border-gray-200 rounded shadow-sm py-1 px-2 block w-full mt-1"
type="text"
placeholder="YYYY/MM/DD"
name="dob"
defaultValue={user.dob}
onChange={(e) =>
setUser({
...user,
dob: e.target.value,
})
}
/>
</label>
</div>
{showInstructor && (
<>
<div className="w-full mb-3">
<label
htmlFor=""
className="block flex-1 font-bold"
>
Title
<input
className="bg-gray-50 border border-gray-200 rounded shadow-sm py-1 px-2 block w-full mt-1 capitalize"
type="text"
placeholder="Title"
name="title"
defaultValue={
instructor.state
.instructorProfile.title
}
onChange={(event) =>
handleInstructorProfileChange(
event
)
}
/>
</label>
</div>
<div className="w-full mb-3">
<label
htmlFor=""
className="block flex-1 font-bold"
>
Office location
<input
className="bg-gray-50 border border-gray-200 rounded shadow-sm py-1 px-2 block w-full mt-1 capitalize"
type="text"
placeholder="Office location"
name="officeLocation"
defaultValue={
instructor.state
.instructorProfile
.officeLocation
}
onChange={(event) =>
handleInstructorProfileChange(
event
)
}
/>
</label>
</div>
<div className="w-full mb-3">
<label
htmlFor=""
className="block flex-1 font-bold"
>
Office hours
<input
className="bg-gray-50 border border-gray-200 rounded shadow-sm py-1 px-2 block w-full mt-1 capitalize"
type="text"
placeholder="Office hours"
name="officeHours"
defaultValue={
instructor.state
.instructorProfile
.officeHours
}
onChange={(event) =>
handleInstructorProfileChange(
event
)
}
/>
</label>
</div>
<div className="w-full mb-3">
<label
htmlFor=""
className="block flex-1 font-bold"
>
Contact policy
<textarea
className="bg-gray-50 border border-gray-200 rounded shadow-sm py-1 px-2 block w-full mt-1 capitalize"
placeholder="Contact policy"
name="contactPolicy"
defaultValue={
instructor.state
.instructorProfile
.contactPolicy
}
onChange={(event) =>
handleInstructorProfileChange(
event
)
}
/>
</label>
</div>
<div className="w-full mb-3">
<label
htmlFor=""
className="block flex-1 font-bold"
>
Phone number
<input
className="bg-gray-50 border border-gray-200 rounded shadow-sm py-1 px-2 block w-full mt-1 capitalize"
type="text"
placeholder="Phone number"
name="phone"
defaultValue={
instructor.state
.instructorProfile.phone
}
onChange={(event) =>
handleInstructorProfileChange(
event
)
}
/>
</label>
</div>
<div className="w-full mb-3">
<label
htmlFor=""
className="block flex-1 font-bold"
>
Research interest
<textarea
className="bg-gray-50 border border-gray-200 rounded shadow-sm py-1 px-2 block w-full mt-1 capitalize"
placeholder="Research interest"
name="researchInterest"
defaultValue={
instructor.state
.instructorProfile
.researchInterest
}
onChange={(event) =>
handleInstructorProfileChange(
event
)
}
/>
</label>
</div>
<div className="w-full mb-3">
<label
htmlFor=""
className="block flex-1 font-bold"
>
Teaching philosophy
<textarea
className="bg-gray-50 border border-gray-200 rounded shadow-sm py-1 px-2 block w-full mt-1 capitalize"
placeholder="Teaching philosophy"
name="philosophy"
defaultValue={
instructor.state
.instructorProfile
.philosophy
}
onChange={(event) =>
handleInstructorProfileChange(
event
)
}
/>
</label>
</div>
</>
)}
<div className="w-full mb-3 flex gap-4">
<label
htmlFor=""
className="block flex-1 font-bold"
>
Password
<input
className="lg:basis-1/2 bg-gray-50 border border-gray-200 rounded shadow-sm py-1 px-2 block mt-1 w-full"
type="password"
placeholder="Password"
name="password"
defaultValue={user.password}
onChange={(e) =>
setUser({
...user,
password: e.target.value,
})
}
/>
</label>
<label
htmlFor=""
className="block flex-1 font-bold"
>
Password Confirmation
<input
className="lg:basis-1/2 bg-gray-50 border border-gray-200 rounded shadow-sm py-1 px-2 block mt-1 w-full"
type="password"
placeholder="Password Confirmation"
name="passwordConf"
defaultValue={user.passwordConf}
onChange={(e) =>
setUser({
...user,
passwordConf: e.target.value,
})
}
/>
</label>
</div>
<button
className="bg-blue-300 border-blue-200 rounded w-auto text-black px-4 py-2"
onClick={(e) => updateUser(e)}
type="submit"
>
Update profile
</button>
</form>
<h3
id="modules"
className="text-2xl bold border-b border-gray-100 mb-3 mt-3"
>
My Plan of Study
</h3>
<div className="">
<PlanOfStudy param={params.id} />
</div>
<h3
id="security"
className="text-2xl bold border-b border-gray-100 mb-3 mt-3"
>
Security
</h3>
<div className="">
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Suscipit beatae quam sint quis sapiente nobis esse! Et
reprehenderit a eum laudantium earum? Voluptas aliquam,
sit eaque in sed distinctio vitae!
</div>
<h3
id="notifications"
className="text-2xl bold border-b border-gray-100 mb-3 mt-3"
>
Notifications
</h3>
<div className="">
Lorem ipsum dolor sit amet consectetur adipisicing elit.
Suscipit beatae quam sint quis sapiente nobis esse! Et
reprehenderit a eum laudantium earum? Voluptas aliquam,
sit eaque in sed distinctio vitae!
</div>
<h3
id="kill"
className="text-2xl bold border-b border-gray-100 text-red-500 mb-3 mt-3"
>
Danger zone
</h3>
<button
className="text-white border-red-400 bg-red-500 rounded w-auto px-4 py-2"
onClick={() => deleteUser()}
>
Kill account
</button>
</div>
</div>
</>
)
}
export default Profile
Source