-
}
- onClick={handleClickOpen}
- style={{
- backgroundColor: '#5736ac',
- color: '#ffffff',
- borderRadius: '10px',
- height: '53px',
- width: '180px',
- textTransform: 'none',
- }}
- >
- Delete User
-
-
-
- {/* Custom content inside the dialog */}
-
-
setEmail(e.target.value)}
- autoComplete="email"
- />
- setPassword(e.target.value)}
- autoComplete="current-password"
- />
- {err && {err}
}
-
-
-
- );
-};
-
-export default DeleteUserDialog;
diff --git a/src/app/profile/style.css b/src/app/profile/style.css
index 6361878..c71547b 100644
--- a/src/app/profile/style.css
+++ b/src/app/profile/style.css
@@ -1,198 +1,189 @@
-.profile-container {
+/* Define CSS variables for colors and fonts */
+:root {
+ --primary-color: #5736ac;
+ --secondary-color: #808080;
+ --background-color: #ffffff;
+ --font-family: "SF Pro Display-Regular", Helvetica;
+ --font-color: #000000;
+ --font-color-secondary: #6c6c6c;
+}
+
+/* General styles */
+body {
+ font-family: var(--font-family);
+ color: var(--font-color);
+}
+
+.layout {
display: flex;
- justify-content: space-between;
+ justify-content: space-evenly;
align-items: flex-start;
- gap: 30px;
+ width: 100vw;
+ height: 100vh;
+ margin-top: 380px;
}
-.profile-left-section {
+.layout .left-section,
+.layout .right-section {
display: flex;
align-items: flex-start;
- padding-right: 55px;
+ flex-grow: 1;
+ justify-content: center;
+}
+
+.layout .right-section {
+ width: 50vw;
+ flex-direction: column;
+ gap: 16px;
+ align-items: center;
+ padding: 0 5vw;
}
-.full-name-and-bio {
- height: 295px;
- position: relative;
- width: 408px;
- margin-top: -135px;
- margin-left: 34%;
+.layout .left-section .full-name-and-bio {
display: flex;
flex-direction: column;
align-items: center;
+ row-gap: 16px;
+ margin-top: -25%;
+ z-index: 1;
}
-.full-name-and-bio .ellipse {
+.layout .left-section .full-name-and-bio .profile-image {
background-color: #9d9d9d;
- border: 5px solid;
- border-color: #ffffff;
- border-radius: 100px;
- height: 160px;
- width: 160px;
+ border-radius: 50%;
+ border:#ffffff 4px solid;
+ height: 200px;
+ width: 200px;
display: flex;
align-items: center;
justify-content: center;
+ color: var(--background-color);
+ font-size: 96px;
+ line-height: 1;
}
-.initials {
- color: #ffffff;
- font-size: 100px;
- margin-left:10px;
-}
-.buttonContainerProfile{
- margin-right: 217px;
-}
-.full-name-and-bio .text-wrapper {
- color: #000000;
- font-family: "SF Pro Display-Regular", Helvetica;
+.full-name-and-bio .name {
+ color: var(--font-color);
font-size: 48px;
font-weight: 400;
+ line-height: 1;
white-space: nowrap;
+ text-transform: capitalize;
}
-.full-name-and-bio .div {
- color: #6c6c6c;
- font-family: "SF Pro Display-Regular", Helvetica;
+.full-name-and-bio .email-address {
+ color: var(--font-color-secondary);
font-size: 24px;
- font-weight: 400;
+ white-space: nowrap;
}
-/* The vertical divider between sections */
-.rectangle1 {
- background-color: #e6e6e6;
- height: 58vh;
- width: 2px;
- margin-left: 150px;
- margin-right: 30px;
- flex-shrink: 0; /* Prevent the divider from shrinking */
+.profile-container {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-direction: column;
+ min-width: 50vw;
+ gap: 8px;
}
-.info {
- color: #000000;
- font-family: "SF Pro Display-Regular", Helvetica;
- font-size: 24px;
- font-weight: 400;
- width: 152px;
- margin-bottom: 36.59px;
+.row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
}
-.rectangle2 {
- background-color: #e5e5e5;
- height: 2px;
- width: 591px;
+.button-container {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 16px;
}
-.firstName {
- color: #000000;
- font-family: "SF Pro Display-Regular", Helvetica;
- font-size: 18px;
+.profile-container .title {
+ color: var(--font-color);
+ font-size: 24px;
font-weight: 400;
- white-space: nowrap;
- width: 90px;
+ line-height: 1;
+ padding: 2px;
}
+.firstName,
.lastName {
- margin-left: 159px;
- color: #000000;
- font-family: "SF Pro Display-Regular", Helvetica;
+ color: var(--font-color);
font-size: 18px;
font-weight: 400;
+ height: 36px;
+ padding: 2px;
white-space: nowrap;
- width: 90px;
-}
-
-.firstname-input {
- background-color: #ffffff;
- border: 1px solid;
- height: 56px;
- border-radius: 9px;
- width: 193px;
+ width: 50%;
}
+.firstname-input,
.lastname-input {
- background-color: #ffffff;
+ background-color: var(--background-color);
+ font-size: 18px;
+ padding-left: 8px;
border: 1px solid;
- margin-left: 58px;
- border-radius: 9px;
- height: 56px;
- width: 193px;
-}
-.profileLineContainer{
- display:flex;
- justify-content: space-between;
-}
-.firstname-border {
- max-width: 250px;
- padding: 1rem;
- position: relative;
- background: linear-gradient(to bottom, rgb(9, 251, 211), rgb(255, 111, 241)) 1;
- padding: 3px;
+ height: 36px;
+ border-radius: 8px;
+ width: 80%;
}
+
+.firstname-border,
.lastname-border {
- max-width: 250px;
- padding: 1rem;
- position: relative;
- background: linear-gradient(to bottom, rgb(9, 251, 211), rgb(255, 111, 241)) 1;
- padding: 3px;
-}
-@media (max-width: 1300px) {
- .profileLineContainer{
- display:flex;
- justify-content: left;
- gap:80px;
- }
- .rectangle2 {
- background-color: #e5e5e5;
- height: 2px;
- width: 391px;
- }
+ width: 50%
+}
+
+.gpa-input {
+ font-size: 18px;
+ height: 40px !important;
+ width: 80% !important;
}
+
/* Responsive design for smaller screens */
@media (max-width: 1100px) {
- .profile-container {
+ .layout {
flex-direction: column;
align-items: center;
- gap: 20px;
- margin-left: 50px;
}
-
- .full-name-and-bio {
+
+ .left-section,
+ .right-section {
width: 100%;
- margin-left: 0;
}
- .profile-left-section,
- .profile-right-section {
- width: 100%;
- text-align: center;
+ .profile-container {
+ flex-direction: column;
+ align-items: center;
+ gap: 8px;
}
- .full-name-and-bio .ellipse {
- width: 155px;
- height: 150px;
+ .initials {
+ color: #ffffff;
+ font-size: 70px;
+ }
+
+ .layout .left-section .full-name-and-bio {
+ margin-top: -10%;
+ width: 100%;
}
- .full-name-and-bio .text-wrapper {
- font-size: 36px;
+ .full-name-and-bio .profile-image {
+ width: 155px;
+ height: 155px;
+ font-size: 72px;
}
- .full-name-and-bio .div {
+ .title {
font-size: 18px;
}
- .firstname-input,
- .lastname-input {
- width: 100%;
- margin-left: 0;
- margin-top: 10px;
+ .full-name-and-bio .name {
+ font-size: 36px;
}
- .rectangle1 {
- display: none; /* Remove the divider on smaller screens */
- }
- .buttonContainerProfile{
- margin-left: 170px;
-
+ .full-name-and-bio .email-address {
+ font-size: 18px;
}
-
}
diff --git a/src/app/profile/temp.tsx b/src/app/profile/temp.tsx
index 978634e..5fb45ab 100644
--- a/src/app/profile/temp.tsx
+++ b/src/app/profile/temp.tsx
@@ -1,38 +1,202 @@
'use client';
-import React, { useState } from 'react';
+import React, { useEffect, useState } from 'react';
import { useAuth } from '@/firebase/auth/auth_context';
-import { Button } from '@mui/material';
+import {
+ Button,
+ Grid,
+ TextField,
+ FormControl,
+ InputLabel,
+ Select,
+ MenuItem,
+ CircularProgress,
+ Box,
+ Typography,
+} from '@mui/material';
import './style.css';
import HeaderCard from '@/component/HeaderCard/HeaderCard';
import DeleteUserButton from './DeleteUserButton';
+import GetUserRole from '@/firebase/util/GetUserRole';
import { updateProfile } from 'firebase/auth';
+import { getFirestore, doc, updateDoc } from 'firebase/firestore';
+import { collection, query, where, getDocs } from 'firebase/firestore';
+import { QrCode2 } from '@mui/icons-material';
+import { set } from 'react-hook-form';
+import { placeholderCSS } from 'react-select/dist/declarations/src/components/Placeholder';
interface ProfileProps {
userRole: string;
}
+const primaryButtonStyle: React.CSSProperties = {
+ borderRadius: '8px',
+ height: '40px',
+ width: '80px',
+ textTransform: 'none',
+ fontFamily: 'SF Pro Display-Bold , Helvetica',
+ backgroundColor: '#5736ac',
+ color: '#ffffff',
+};
+
+const secondaryButtonStyle: React.CSSProperties = {
+ borderRadius: '8px',
+ height: '40px',
+ width: '80px',
+ borderWidth: '2px',
+ textTransform: 'none',
+ fontFamily: 'SF Pro Display-Bold , Helvetica',
+ borderColor: '#808080',
+ color: '#808080',
+};
+
+const textFieldStyles = (isEditable: boolean) => ({
+ '& .MuiOutlinedInput-root': {
+ '& fieldset': {
+ borderColor: isEditable ? 'black' : '#cecece',
+ },
+ '&:hover fieldset': {
+ borderColor: isEditable ? 'black' : '#cecece',
+ },
+ '&.Mui-focused fieldset': {
+ borderColor: isEditable ? undefined : '#cecece',
+ borderWidth: isEditable ? undefined : '1px',
+ },
+ },
+ '& .MuiInputBase-input': {
+ cursor: isEditable ? 'text' : 'default',
+ },
+ '& .MuiInputLabel-outlined': {
+ color: isEditable ? undefined : '#888',
+ },
+ '& .MuiInputLabel-outlined.Mui-focused': {
+ color: isEditable ? undefined : '#888',
+ },
+});
+
export default function Profile(props: ProfileProps) {
const { user } = useAuth();
+ const [role, loading, error] = GetUserRole(user?.uid);
+ const uid = user?.uid as string;
+ const db = getFirestore();
- const nameParts = user.displayName.split(' ');
+ const [isLoading, setIsLoading] = useState(true);
- // Extract first and last names
- const firstName = nameParts[0] || '';
- const lastName = nameParts.slice(1).join(' ');
+ const [firstName, setFirstName] = useState('');
+ const [lastName, setLastName] = useState('');
+ const [email, setEmail] = useState('');
+ const [department, setDepartment] = useState('');
+
+ const [gpa, setGpa] = useState('');
+ const [phoneNumber, setPhoneNumber] = useState('');
+ const [graduationDate, setGraduationDate] = useState('');
+ const [degree, setDegree] = useState('');
const [updatedFirst, setUpdatedFirst] = useState('');
const [updatedLast, setUpdatedLast] = useState('');
+ const [updatedDepartment, setUpdatedDepartment] = useState('');
+ const [updatedGpa, setUpdatedGpa] = useState('');
+ const [updatedPhoneNumber, setUpdatedPhoneNumber] = useState('');
+ const [updatedGraduationDate, setUpdatedGraduationDate] = useState('');
+ const [updatedDegree, setUpdatedDegree] = useState('');
+
+ const [isEditing, setIsEditing] = useState(false);
const [open, setOpen] = React.useState(false);
+ useEffect(() => {
+ const fetchUserData = async () => {
+ setIsLoading(true);
+
+ try {
+ const usersCollectionRef = collection(db, 'users');
+ const q = query(usersCollectionRef, where('uid', '==', uid));
+ const querySnapshot = await getDocs(q);
+
+ if (!querySnapshot.empty) {
+ const docSnap = querySnapshot.docs[0];
+ const data = docSnap.data();
+ setFirstName(data.firstname || '');
+ setLastName(data.lastname || '');
+ setEmail(data.email || '');
+ setGpa(data.gpa || '');
+ setDepartment(data.department || '');
+ setPhoneNumber(data.phonenumber || '');
+ setGraduationDate(data.graduationdate || '');
+ setDegree(data.degree || '');
+ } else {
+ console.log('No such document!');
+ }
+ } catch (error) {
+ console.error('Error fetching user data:', error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ if (uid) {
+ fetchUserData();
+ }
+ }, [db, uid]);
+
const handleSave = async (e: any) => {
- e.preventDefault(); // Prevent the form from submitting in the traditional way
- if (updatedFirst.trim() !== '' && updatedLast.trim() !== '') {
+ e.preventDefault();
+ if (firstName.trim() !== '' && lastName.trim() !== '') {
try {
- await updateProfile(user, {
- displayName: `${updatedFirst} ${updatedLast}`,
- });
- window.location.reload();
- alert('Profile updated successfully');
+ const usersCollectionRef = collection(db, 'users');
+ const q = query(usersCollectionRef, where('uid', '==', uid));
+ const querySnapshot = await getDocs(q);
+
+ if (!querySnapshot.empty) {
+ const docRef = doc(db, 'users', querySnapshot.docs[0].id);
+ const docSnap = querySnapshot.docs[0];
+ const currentData = docSnap.data();
+ const updatedData: any = {};
+
+ // Always include these fields for all users
+ if (updatedFirst.trim() !== '') updatedData.firstname = updatedFirst;
+ if (updatedLast.trim() !== '') updatedData.lastname = updatedLast;
+ if (updatedDepartment.trim() !== '')
+ updatedData.department = updatedDepartment;
+
+ // Only include student fields if user is not faculty
+ if (showStudentFields) {
+ // For GPA field
+ if (updatedGpa.trim() !== '') {
+ updatedData.gpa = updatedGpa;
+ }
+
+ // For phone number field
+ if (updatedPhoneNumber.trim() !== '') {
+ updatedData.phonenumber = updatedPhoneNumber;
+ }
+
+ // For graduation date field
+ if (updatedGraduationDate.trim() !== '') {
+ updatedData.graduationdate = updatedGraduationDate;
+ }
+
+ // For degree field
+ if (updatedDegree.trim() !== '') {
+ updatedData.degree = updatedDegree;
+ }
+ }
+
+ // Only update if there are changes
+ if (Object.keys(updatedData).length > 0) {
+ await updateDoc(docRef, updatedData);
+ console.log('Profile updated successfully');
+ alert('Profile updated successfully');
+ setIsEditing(false);
+ window.location.reload();
+ } else {
+ console.log('No changes to update');
+ alert('No changes detected');
+ setIsEditing(false);
+ }
+ } else {
+ // This will only happen if the user ID doesn't exist in the collection
+ console.log('No document found for this user ID');
+ alert('Profile not found. Please contact support.');
+ }
} catch (error) {
console.error('Error updating profile: ', error);
alert('Failed to update profile');
@@ -41,97 +205,270 @@ export default function Profile(props: ProfileProps) {
alert('First name and last name cannot be empty.');
}
};
+
const handleCancel = () => {
setUpdatedFirst('');
setUpdatedLast('');
+ setUpdatedGpa('');
+ setUpdatedDepartment('');
+ setUpdatedPhoneNumber('');
+ setUpdatedGraduationDate('');
+ setUpdatedDegree('');
+ setIsEditing(false);
};
+ if (loading || isLoading) {
+ return (
+
-
-
-
-
- {firstName[0].toUpperCase() + lastName[0].toUpperCase()}
-
-
-
{user.displayName}
-
{user.email}
-
-
+
+
+
+
+ {firstName[0]?.toUpperCase() + lastName[0]?.toUpperCase()}
+
{firstName + ' ' + lastName}
+
{email}
+
-
-
-
)}
@@ -221,6 +228,13 @@ export default function DashboardWelcome(props: DashboardProps) {
image="https://c.animaapp.com/vYQBTcnO/img/profile@2x.png"
/>
+
+
+
)}
@@ -277,6 +291,13 @@ export default function DashboardWelcome(props: DashboardProps) {
text="Profile"
/>
+
+
+
)}
diff --git a/src/component/LogInCard/LogInCard.tsx b/src/component/LogInCard/LogInCard.tsx
index 19b25f5..3229640 100644
--- a/src/component/LogInCard/LogInCard.tsx
+++ b/src/component/LogInCard/LogInCard.tsx
@@ -1,7 +1,7 @@
'use client';
import { getAuth, sendPasswordResetEmail } from 'firebase/auth';
-import React from 'react';
+import React, { useState } from 'react';
import Dialog from '@mui/material/Dialog';
import Button from '@mui/material/Button';
import DialogActions from '@mui/material/DialogActions';
@@ -10,11 +10,11 @@ import Divider from '@mui/material/Divider';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import handleSignIn from '../../firebase/auth/auth_signin_password';
-import { useState } from 'react';
import './style.css';
import MuiAlert, { AlertProps } from '@mui/material/Alert';
-import { TextField } from '@mui/material';
-import Link from 'next/link';
+import { TextField, IconButton, InputAdornment } from '@mui/material';
+import { Visibility, VisibilityOff } from '@mui/icons-material';
+
import FormControl from '@mui/material/FormControl';
import { toast } from 'react-hot-toast';
@@ -32,46 +32,43 @@ export const LogInCard = ({
const [email, setEmail] = useState('');
const [emailVal, setEmailVal] = useState('');
const [password, setPassword] = useState('');
+ const [showPassword, setShowPassword] = useState(false);
const [open, setOpen] = React.useState(false);
+
const handleClose = () => {
setOpen(false);
};
const handleForgotPassword = (e: any) => {
- //handleSignOut();
e.preventDefault();
const auth = getAuth();
sendPasswordResetEmail(auth, emailVal)
.then(() => {
- // Password reset email sent!
- // ..
toast.success('Password reset email sent!');
})
.catch((error) => {
const errorCode = error.code;
const errorMessage = error.message;
- // ..
console.log(error);
});
setOpen(false);
};
- const Alert = React.forwardRef
(function Alert(
- props,
- ref
- ) {
- return ;
- });
+
+ const handleClickShowPassword = () => {
+ setShowPassword(!showPassword);
+ };
+
const handleSubmit = async (event: any) => {
setLoading(true);
event.preventDefault();
res = await handleSignIn(email, password);
- // Loading bar toggle
if (!res) {
setLoading(false);
} else {
setSuccess(true);
}
};
+
return (
-
Cancel
-
+
+ {showPassword ? : }
+
+
+ ),
+ }}
+ className="text-wrapper-3"
+ placeholder="Password"
+ margin="normal"
required
fullWidth
name="password"
- type="password"
+ type={showPassword ? 'text' : 'password'}
id="password"
onChange={(event: React.ChangeEvent) => {
setPassword(event.target.value);
diff --git a/src/component/TopNavBarSigned/style.css b/src/component/TopNavBarSigned/style.css
index 35faec8..2e38adb 100644
--- a/src/component/TopNavBarSigned/style.css
+++ b/src/component/TopNavBarSigned/style.css
@@ -1,7 +1,7 @@
.top-nav-bar-signed {
height: 37px;
position: fixed;
- width: 279px;
+ width: 379px;
}
.top-nav-bar-signed .text-wrapper-3 {
@@ -9,7 +9,7 @@
font-family: "SF Pro Display-Bold", Helvetica;
font-size: 16px;
font-weight: 700;
- left: 100px;
+ left: 210px;
letter-spacing: 0;
line-height: normal;
position: absolute;
@@ -23,7 +23,21 @@
font-family: "SF Pro Display-Bold", Helvetica;
font-size: 16px;
font-weight: 700;
- left: 0;
+ left: 130px;
+ letter-spacing: 0;
+ line-height: normal;
+ position: absolute;
+ top: 8px;
+ white-space: nowrap;
+ width: 51px;
+}
+
+.top-nav-bar-signed .text-wrapper-2 {
+ color: #ffffff;
+ font-family: "SF Pro Display-Bold", Helvetica;
+ font-size: 18px;
+ font-weight: 700;
+ left: 25px;
letter-spacing: 0;
line-height: normal;
position: absolute;
@@ -36,7 +50,7 @@
all: unset;
box-sizing: border-box;
height: 37px;
- left: 207px;
+ left: 307px;
position: absolute;
top: 0;
width: 117px;
diff --git a/src/components/Research/ApplicationCard.tsx b/src/components/Research/ApplicationCard.tsx
new file mode 100644
index 0000000..b90b0ae
--- /dev/null
+++ b/src/components/Research/ApplicationCard.tsx
@@ -0,0 +1,196 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { Card, CardContent, Typography, Button, Box } from '@mui/material';
+
+interface ApplicationCardProps {
+ userRole: string;
+ uid?: string;
+ project_title: string;
+ department: string;
+ faculty_mentor: { name: string; email: string };
+ date_applied: string;
+ terms_available: string;
+ student_level: string;
+ project_description: string;
+ faculty_members?: string[];
+ phd_student_mentor?: string;
+ prerequisites?: string;
+ credit?: string;
+ stipend?: string;
+ application_requirements?: string;
+ application_deadline?: string;
+ website?: string;
+ app_status: string;
+ onEdit?: () => void;
+ onShowApplications?: () => void;
+}
+
+const ApplicationCard: React.FC = ({
+ userRole,
+ uid,
+ project_title,
+ department,
+ date_applied,
+ faculty_mentor,
+ terms_available,
+ student_level,
+ project_description,
+ faculty_members = [],
+ app_status,
+ phd_student_mentor,
+ prerequisites,
+ credit,
+ stipend,
+ application_requirements,
+ application_deadline,
+ website,
+ onEdit,
+ onShowApplications,
+}) => {
+ const [expanded, setExpanded] = useState(false);
+ const [needsExpansion, setNeedsExpansion] = useState(false);
+ const descriptionRef = useRef(null);
+ const isFacultyInvolved =
+ userRole === 'faculty' && faculty_members.includes(uid || '');
+
+ useEffect(() => {
+ const checkTextOverflow = () => {
+ const element = descriptionRef.current;
+ if (!element) return;
+
+ if (expanded) {
+ setNeedsExpansion(true);
+ return;
+ }
+
+ const isOverflowing = element.scrollHeight > element.clientHeight;
+ setNeedsExpansion(isOverflowing);
+ };
+
+ checkTextOverflow();
+
+ window.addEventListener('resize', checkTextOverflow);
+ return () => window.removeEventListener('resize', checkTextOverflow);
+ }, [expanded, project_description]);
+
+ return (
+
+
+
+ {project_title}
+
+
+ {department}
+
+
+
+ Status:
+ {' '}
+
+ {app_status}
+
+
+
+ Date Applied:
+ {' '}
+
+ {date_applied}
+
+
+
+
+ Faculty Mentor:
+ {' '}
+
+ {Object.entries(faculty_mentor ?? {})
+ .map(([key, value]) => `${value}`)
+ .join(', ')}
+
+
+
+ Faculty Email:
+ {' '}
+
+ {Object.entries(faculty_mentor ?? {})
+ .map(([key, value]) => `${key}`)
+ .join(', ')}
+
+
+
+ Research Description
+
+ {project_description}
+
+
+
+
+ {needsExpansion ? (
+ setExpanded(!expanded)}>
+ {expanded ? 'Read Less' : 'Read More'}
+
+ ) : (
+
+ )}
+ {isFacultyInvolved && (
+
+
+ Edit Application
+
+
+ Show Applications
+
+
+ )}
+
+
+ );
+};
+
+export default ApplicationCard;
diff --git a/src/components/Research/ApplicationTile.tsx b/src/components/Research/ApplicationTile.tsx
new file mode 100644
index 0000000..1831599
--- /dev/null
+++ b/src/components/Research/ApplicationTile.tsx
@@ -0,0 +1,86 @@
+import React from 'react';
+import {
+ Box,
+ Tabs,
+ Tab,
+ Typography,
+ Avatar,
+ IconButton,
+ Button,
+ Paper,
+ Stack,
+} from '@mui/material';
+import ThumbUpAltOutlinedIcon from '@mui/icons-material/ThumbUpAltOutlined';
+import ThumbDownAltOutlinedIcon from '@mui/icons-material/ThumbDownAltOutlined';
+import ReviewModal from './ReviewModal';
+
+interface ApplicationTileProps {
+ key: any;
+ item: any;
+ status: 'Pending' | 'Approved' | 'Denied';
+ changeStatus: (id: string, app_status: string) => Promise;
+}
+
+const ApplicationTile: React.FC = ({
+ key,
+ item,
+ status,
+ changeStatus,
+}) => {
+ return (
+
+
+ {item?.firstname[0] || '' + item?.lastname[0] || ''}
+
+
+ {item.name}
+
+ {item.email}
+
+
+ {status === 'Pending' && (
+
+ changeStatus(item.id, 'Approved')}
+ >
+
+
+
+ changeStatus(item.id, 'Denied')}
+ />
+
+
+
+ )}
+ {status === 'Approved' && (
+
+ Approved
+
+ )}
+ {status === 'Denied' && (
+
+ Denied
+
+ )}
+
+ );
+};
+
+export default ApplicationTile;
diff --git a/src/components/Research/EditResearchModal.tsx b/src/components/Research/EditResearchModal.tsx
new file mode 100644
index 0000000..7e02e1b
--- /dev/null
+++ b/src/components/Research/EditResearchModal.tsx
@@ -0,0 +1,272 @@
+'use client';
+
+import React, { useState, useEffect } from 'react';
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ TextField,
+ Button,
+ Grid,
+ Typography,
+} from '@mui/material';
+import firebase from '@/firebase/firebase_config';
+
+interface EditResearchModalProps {
+ open: boolean;
+ onClose: () => void;
+ listingData: any; // The current listing's data (pre-filled)
+ onSubmitSuccess: () => void; // Callback to refresh the listings
+}
+
+const EditResearchModal: React.FC = ({
+ open,
+ onClose,
+ listingData,
+ onSubmitSuccess,
+}) => {
+ const [formData, setFormData] = useState({ ...listingData });
+ const [facultyEmail, setFacultyEmail] = useState('');
+ const [facultyName, setFacultyName] = useState('');
+
+ useEffect(() => {
+ setFormData({ ...listingData });
+ }, [listingData]);
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target;
+ setFormData((prev: typeof formData) => ({ ...prev, [name]: value }));
+ };
+
+ /** Adds a faculty mentor to the map. */
+ const handleAddFacultyMentor = () => {
+ if (facultyEmail && facultyName) {
+ setFormData((prev) => ({
+ ...prev,
+ faculty_mentor: {
+ ...prev.faculty_mentor,
+ [facultyEmail]: facultyName,
+ },
+ }));
+ setFacultyEmail('');
+ setFacultyName('');
+ }
+ };
+
+ /** Removes a faculty mentor from the map. */
+ const handleRemoveFacultyMentor = (email: string) => {
+ setFormData((prev) => {
+ const updatedMentors = { ...prev.faculty_mentor };
+ delete updatedMentors[email];
+ return { ...prev, faculty_mentor: updatedMentors };
+ });
+ };
+
+ const handleSubmit = async () => {
+ try {
+ const db = firebase.firestore();
+ const querySnapshot = await db
+ .collection('research-listings')
+ .where('id', '==', listingData.id)
+ .get();
+
+ if (querySnapshot.empty) {
+ throw new Error('No matching listing found!');
+ }
+
+ const listingRef = querySnapshot.docs[0].ref;
+ await listingRef.update(formData);
+
+ alert('Research listing updated!');
+ onSubmitSuccess();
+ onClose();
+ } catch (error) {
+ console.error('Update failed:', error);
+ alert('Failed to update listing.');
+ }
+ };
+
+ return (
+
+ Edit Research Listing
+
+
+
+
+
+
+
+
+
+
+ {/* Faculty Mentor */}
+
+ Faculty Mentors
+ setFacultyEmail(e.target.value)}
+ fullWidth
+ margin="dense"
+ />
+ setFacultyName(e.target.value)}
+ fullWidth
+ margin="dense"
+ />
+
+ Add Faculty Mentor
+
+
+ {formData.faculty_mentor &&
+ Object.entries(formData.faculty_mentor).map(([email, name]) => (
+
+
+
+ {name} ({email})
+
+ handleRemoveFacultyMentor(email)}
+ sx={{
+ textTransform: 'none',
+ color: '#D32F2F',
+ fontWeight: 500,
+ }}
+ >
+ Remove
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cancel
+
+ Save
+
+
+
+ );
+};
+
+export default EditResearchModal;
diff --git a/src/components/Research/FacultyApplicantsView.tsx b/src/components/Research/FacultyApplicantsView.tsx
new file mode 100644
index 0000000..9ff1de3
--- /dev/null
+++ b/src/components/Research/FacultyApplicantsView.tsx
@@ -0,0 +1,161 @@
+import React, { useEffect } from 'react';
+import {
+ Box,
+ Tabs,
+ Tab,
+ Typography,
+ Avatar,
+ IconButton,
+ Button,
+ Paper,
+ Stack,
+} from '@mui/material';
+import ApplicationTile from './ApplicationTile';
+import firebase from '@/firebase/firebase_config';
+import {
+ collection,
+ addDoc,
+ updateDoc,
+ doc,
+ where,
+ query,
+ documentId,
+ getDocs,
+} from 'firebase/firestore';
+
+interface FacultyApplicantsViewProps {
+ id: string;
+ researchListing: any;
+ onBack: () => void;
+}
+
+const FacultyApplicantsView: React.FC = ({
+ id,
+ researchListing,
+ onBack,
+}) => {
+ const [tabIndex, setTabIndex] = React.useState(0);
+ const [applications, setApplications] = React.useState(
+ researchListing.applications
+ );
+
+ const handleChange = (_event: React.SyntheticEvent, newValue: number) => {
+ setTabIndex(newValue);
+ };
+
+ const changeStatus = async (id: string, app_status: string) => {
+ const db = firebase.firestore();
+ const docRef = doc(db, 'research-listings', researchListing.docID);
+ const colRef = collection(docRef, 'applications');
+ const docAppRef = doc(colRef, id);
+ await updateDoc(docAppRef, { app_status });
+ for (var i = 0; i < researchListing.applications.length; i++) {
+ setApplications((old) =>
+ old.map((app) => (app.id === id ? { ...app, app_status } : app))
+ );
+ }
+ };
+
+ return (
+
+
+ {/* Top header area with button now aligned */}
+
+
+ {researchListing.project_title}
+
+
+
+ Back to Research Listings
+
+
+
+ {/* Tabs for Needs Review, Approved, Denied */}
+
+
+
+
+
+
+
+
+
+
+ {/* Section title */}
+
+ Applications
+
+
+ {/* Application tiles based on tabs */}
+ {tabIndex === 0 &&
+ applications
+ .filter((item) => item.app_status === 'Pending')
+ .map((item, index) => (
+
+ ))}
+ {tabIndex === 1 &&
+ applications
+ .filter((item) => item.app_status === 'Approved')
+ .map((item, index) => (
+
+ ))}
+ {tabIndex === 2 &&
+ applications
+ .filter((item) => item.app_status === 'Denied')
+ .map((item, index) => (
+
+ ))}
+
+
+ );
+};
+
+export default FacultyApplicantsView;
diff --git a/src/components/Research/FacultyResearchView.tsx b/src/components/Research/FacultyResearchView.tsx
new file mode 100644
index 0000000..85213d5
--- /dev/null
+++ b/src/components/Research/FacultyResearchView.tsx
@@ -0,0 +1,353 @@
+import React, { useEffect, useState } from 'react';
+import {
+ Box,
+ Typography,
+ Button,
+ Grid,
+ Container,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogContentText,
+ DialogTitle,
+} from '@mui/material';
+import HeaderCard from '@/components/HeaderCard/HeaderCard';
+import ResearchModal from '@/components/Research/Modal';
+import ProjectCard from '@/components/Research/ProjectCard';
+import FacultyApplicantsView from '@/components/Research/FacultyApplicantsView';
+import { deleteDoc, doc } from 'firebase/firestore';
+import firebase from '@/firebase/firebase_config';
+import EditResearchModal from './EditResearchModal';
+
+interface FacultyResearchViewProps {
+ researchListings: any[];
+ role: string;
+ uid: string;
+ getResearchListings: () => void;
+ postNewResearchPosition: (formData: any) => Promise;
+}
+
+const FacultyResearchView: React.FC = ({
+ researchListings,
+ role,
+ uid,
+ getResearchListings,
+ postNewResearchPosition,
+}) => {
+ const [studentView, showStudentView] = useState(true);
+ const [editModalOpen, setEditModalOpen] = useState(false);
+ const [editingForm, setEditingForm] = useState(null);
+ const [selectedResearchId, setSelectedResearchId] = useState(
+ null
+ );
+
+ // State for delete confirmation modal
+ const [deleteModalOpen, setDeleteModalOpen] = useState(false);
+ const [deleteDocID, setDeleteDocID] = useState(null);
+
+ // Open delete confirmation modal
+ const handleOpenDeleteModal = (docID: string) => {
+ setDeleteDocID(docID);
+ setDeleteModalOpen(true);
+ };
+
+ // Close delete confirmation modal
+ const handleCloseDeleteModal = () => {
+ setDeleteModalOpen(false);
+ setDeleteDocID(null);
+ };
+
+ // Handle delete action
+ const handleDelete = async () => {
+ if (!deleteDocID) return;
+ try {
+ const db = firebase.firestore();
+ const docRef = doc(db, 'research-listings', deleteDocID); // Reference to the document
+ await deleteDoc(docRef); // Delete the document
+ console.log(`Listing with ID ${deleteDocID} deleted successfully.`);
+ getResearchListings(); // Refresh the listings
+ } catch (error) {
+ console.error('Error deleting listing:', error);
+ } finally {
+ handleCloseDeleteModal(); // Close the modal
+ }
+ };
+
+ // Get my positions by filtering
+ const myPositions = researchListings.filter((item) =>
+ item.faculty_members?.includes(uid)
+ );
+
+ // Check if there are no positions when in my positions view
+ const hasNoPositions = studentView && myPositions.length === 0;
+
+ // Callback to go back to the research listings view
+ const handleBackToListings = () => {
+ setSelectedResearchId(null);
+ };
+
+ return (
+ <>
+
+
+ {/* Use Container for consistent width constraints */}
+
+ {/* Top-level heading */}
+
+ Research
+
+
+ {selectedResearchId ? (
+
+ listing.docID === selectedResearchId
+ )}
+ onBack={handleBackToListings}
+ />
+
+ ) : (
+ <>
+ {/* Header section with buttons - consistent width */}
+
+
+ {/* Left side: "My Positions" and button */}
+
+ My Positions:
+
+ showStudentView(!studentView)}
+ >
+ {studentView ? 'View all Positions' : 'View my Positions'}
+
+
+
+
+
+
+ {hasNoPositions ? (
+
+
+ No research positions found
+
+
+ You haven't created any research positions yet. Get
+ started by creating your first position using the "Create
+ New Position" button above.
+
+
+ ) : (
+
+ {studentView
+ ? myPositions.map((item, index) => (
+
+ {
+ setSelectedResearchId(item.docID);
+ }}
+ onEdit={() => {
+ console.log('Opening edit modal');
+ setEditingForm(item);
+ setEditModalOpen(true);
+ }}
+ onDelete={() => handleOpenDeleteModal(item.docID)} // Open confirmation modal
+ />
+
+ ))
+ : researchListings.map((item, index) => (
+
+ {
+ setSelectedResearchId(item.docID);
+ }}
+ onEdit={() => {
+ console.log('Opening edit modal');
+ setEditingForm(item);
+ setEditModalOpen(true);
+ }}
+ onDelete={() => handleOpenDeleteModal(item.docID)} // Open confirmation modal
+ />
+
+ ))}
+
+ )}
+ >
+ )}
+
+
+ {/* Delete Confirmation Modal */}
+
+
+ Confirm Deletion
+
+
+
+ Are you sure you want to delete this research listing? This action
+ cannot be undone.
+
+
+
+
+ Cancel
+
+
+ Delete
+
+
+
+
+ {editingForm && (
+ setEditModalOpen(false)}
+ listingData={editingForm}
+ onSubmitSuccess={getResearchListings}
+ />
+ )}
+ >
+ );
+};
+
+export default FacultyResearchView;
diff --git a/src/components/Research/LessInfoButton.tsx b/src/components/Research/LessInfoButton.tsx
new file mode 100644
index 0000000..3755317
--- /dev/null
+++ b/src/components/Research/LessInfoButton.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import Button from '@mui/material/Button';
+import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp';
+
+interface LessInfoButtonProps {
+ onClick: () => void;
+}
+
+const LessInfoButton: React.FC = ({ onClick }) => {
+ return (
+ }
+ sx={{
+ textTransform: 'none', // Prevent uppercase text
+ borderColor: '#ddd', // Lighter border color
+ borderRadius: '8px', // Rounded corners
+ color: '#000', // Text/icon color
+ }}
+ onClick={onClick}
+ >
+ Less Info
+
+ );
+};
+
+export default LessInfoButton;
diff --git a/src/components/Research/Modal.tsx b/src/components/Research/Modal.tsx
new file mode 100644
index 0000000..39383e5
--- /dev/null
+++ b/src/components/Research/Modal.tsx
@@ -0,0 +1,397 @@
+import React, { useState } from 'react';
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+ TextField,
+ SxProps,
+ Grid,
+ MenuItem,
+ Checkbox,
+ InputLabel,
+ FormControlLabel,
+ InputAdornment,
+} from '@mui/material';
+import firebase from '@/firebase/firebase_config';
+import { collection, addDoc } from 'firebase/firestore';
+import { Theme } from '@emotion/react';
+import { v4 as uuidv4 } from 'uuid';
+import { getStorage, ref, uploadBytes, getDownloadURL } from 'firebase/storage';
+
+/** Define an interface that matches your JSON keys (updated for faculty_mentor). */
+interface FormData {
+ id: string;
+ project_title: string;
+ department: string;
+ faculty_mentor: { [email: string]: string }; // Updated to use curly braces for the map
+ phd_student_mentor: string;
+ terms_available: string;
+ student_level: string;
+ prerequisites: string;
+ credit: string;
+ stipend: string;
+ application_requirements: string;
+ application_deadline: string;
+ website: string;
+ project_description: string;
+}
+
+/** Initialize all fields to empty strings. */
+interface ResearchModal {
+ onSubmitSuccess: () => void;
+ currentFormData: FormData;
+ buttonStyle?: SxProps;
+ buttonText: React.ReactNode; // Changed from string to ReactNode
+ firebaseQuery: (formData: any) => Promise;
+ uid: string;
+}
+
+const ResearchModal: React.FC = ({
+ onSubmitSuccess,
+ currentFormData,
+ buttonStyle,
+ buttonText,
+ firebaseQuery,
+ uid,
+}) => {
+ const [open, setOpen] = useState(false);
+ const [formData, setFormData] = useState(currentFormData);
+ const [facultyEmail, setFacultyEmail] = useState('');
+ const [facultyName, setFacultyName] = useState('');
+
+ /** Opens the dialog (modal). */
+ const handleOpen = () => {
+ setOpen(true);
+ };
+
+ /** Closes the dialog (modal).
+ * Note that we do NOT reset the form data here,
+ * so the draft remains if the user reopens the modal.
+ */
+ const handleClose = () => {
+ setOpen(false);
+ };
+
+ /** Updates the corresponding form field in state. */
+ const handleChange = (
+ event: React.ChangeEvent
+ ) => {
+ const { name, value } = event.target;
+ setFormData((prev) => ({ ...prev, [name]: value }));
+ };
+
+ /** Adds a faculty mentor to the map. */
+ const handleAddFacultyMentor = () => {
+ if (facultyEmail && facultyName) {
+ setFormData((prev) => ({
+ ...prev,
+ faculty_mentor: {
+ ...prev.faculty_mentor,
+ [facultyEmail]: facultyName,
+ },
+ }));
+ setFacultyEmail('');
+ setFacultyName('');
+ }
+ };
+
+ /** Removes a faculty mentor from the map. */
+ const handleRemoveFacultyMentor = (email: string) => {
+ setFormData((prev) => {
+ const updatedMentors = { ...prev.faculty_mentor };
+ delete updatedMentors[email];
+ return { ...prev, faculty_mentor: updatedMentors };
+ });
+ };
+
+ /** Submits the form and clears it, then closes the dialog. */
+ const handleSubmit = async () => {
+ const finalFormData = {
+ ...formData,
+ faculty_mentor: formData.faculty_mentor,
+ creator_id: uid,
+ faculty_members: [uid],
+ };
+ console.log('Final Form Data:', finalFormData);
+ firebaseQuery(finalFormData);
+ onSubmitSuccess();
+ setFormData(currentFormData);
+ handleClose();
+ };
+
+ return (
+
+ {/* Button to open the modal */}
+
+ {buttonText}
+
+
+
+ {buttonText}
+
+
+ {/* Project Title */}
+
+
+
+
+ {/* Department */}
+
+
+
+
+ {/* Faculty Mentor */}
+
+ setFacultyEmail(e.target.value)}
+ fullWidth
+ />
+ setFacultyName(e.target.value)}
+ fullWidth
+ />
+
+ Add Faculty Mentor
+
+
+ {Object.entries(formData.faculty_mentor).map(
+ ([email, name]) => (
+
+
+
+ {name} ({email})
+
+ handleRemoveFacultyMentor(email)}
+ sx={{
+ textTransform: 'none',
+ color: '#D32F2F',
+ fontWeight: 500,
+ }}
+ >
+ Remove
+
+
+
+ )
+ )}
+
+
+
+ {/* PhD Student Mentor */}
+
+
+
+
+ {/* Terms Available */}
+
+
+
+ {/* Student Level */}
+
+
+
+
+ {/* Prerequisites */}
+
+
+
+
+ {/* Credit */}
+
+
+
+
+ {/* Stipend */}
+
+ $
+ ), // Add dollar sign
+ }}
+ fullWidth
+ />
+
+
+ {/* Website */}
+
+
+
+
+ {/* Application Requirements */}
+
+
+
+
+ {/* Application Deadline */}
+
+ Application Deadline
+
+ {
+ setFormData((prev) => ({
+ ...prev,
+ application_deadline: e.target.checked ? 'Rolling' : '',
+ }));
+ }}
+ />
+ }
+ label="Rolling"
+ />
+
+
+ {/* Project Description */}
+
+
+
+
+
+
+
+ Close
+
+
+ Submit
+
+
+
+
+ );
+};
+
+export default ResearchModal;
diff --git a/src/components/Research/ModalApplicationForm.tsx b/src/components/Research/ModalApplicationForm.tsx
new file mode 100644
index 0000000..a5f42ae
--- /dev/null
+++ b/src/components/Research/ModalApplicationForm.tsx
@@ -0,0 +1,358 @@
+'use client';
+
+import React, { useState, useEffect } from 'react';
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ TextField,
+ Button,
+ Grid,
+ Snackbar,
+ Alert,
+} from '@mui/material';
+import { getAuth } from 'firebase/auth';
+import firebase from '@/firebase/firebase_config';
+import {
+ collection,
+ addDoc,
+ updateDoc,
+ doc,
+ where,
+ query,
+ documentId,
+ getDocs,
+} from 'firebase/firestore';
+import { v4 as uuidv4 } from 'uuid';
+
+interface ModalApplicationFormProps {
+ open: boolean;
+ onClose: () => void;
+ listingId: string;
+}
+
+const ModalApplicationForm: React.FC = ({
+ open,
+ onClose,
+ listingId,
+}) => {
+ const auth = getAuth();
+ const user = auth.currentUser;
+
+ const [formData, setFormData] = useState({
+ firstname: '',
+ lastname: '',
+ email: user?.email || '',
+ phone: '',
+ department: '',
+ degree: '',
+ gpa: '',
+ graduationDate: '',
+ resume: '',
+ qualifications: '',
+ weeklyHours: '',
+ });
+
+ const [submitted, setSubmitted] = useState(false);
+ const [error, setError] = useState('');
+
+ useEffect(() => {
+ const fetchProfileData = async () => {
+ if (!user) return;
+ console.log('🔥 Fetching profile for UID:', user.uid);
+
+ try {
+ const db = firebase.firestore();
+ const snapshot = await db
+ .collection('users') // change users to users_test for profile database
+ .where('uid', '==', user.uid) // change uid to userId
+ .get();
+
+ if (!snapshot.empty) {
+ const profileData = snapshot.docs[0].data();
+ console.log('✅ Profile data:', profileData);
+ setFormData((prev) => ({
+ ...prev,
+ firstname: profileData.firstname || '',
+ lastname: profileData.lastname || '',
+ phone: profileData.phonenumber || '',
+ department: profileData.department || '',
+ degree: profileData.degree || '',
+ gpa: profileData.gpa || '',
+ graduationDate: profileData.graduationdate || '',
+ resume: '',
+ qualifications: '',
+ weeklyHours: '',
+ }));
+ } else {
+ console.warn('⚠️ No matching profile found for user.uid');
+ }
+ } catch (error) {
+ console.error('Error fetching profile data:', error);
+ }
+ };
+
+ if (open && user?.uid) {
+ fetchProfileData();
+ }
+ }, [open, user]);
+
+ const handleChange = (e: any) => {
+ setFormData({ ...formData, [e.target.name]: e.target.value });
+ };
+
+ const handleSubmit = async () => {
+ if (!user) {
+ setError('You must be signed in to apply.');
+ return;
+ }
+ const requiredFields = [
+ 'firstname',
+ 'lastname',
+ 'phone',
+ 'department',
+ 'degree',
+ 'gpa',
+ 'graduationDate',
+ 'resume',
+ 'qualifications',
+ 'weeklyHours',
+ ];
+
+ const missingField = requiredFields.find(
+ (field) => !formData[field as keyof typeof formData]
+ );
+ if (missingField) {
+ setError(`Please fill out the ${missingField} field.`);
+ return;
+ }
+
+ const urlRegex = /^https?:\/\/[\w.-]+\.[a-z]{2,}.*$/i;
+ if (!urlRegex.test(formData.resume)) {
+ setError('Please enter a valid URL for the resume.');
+ return;
+ }
+
+ try {
+ const db = firebase.firestore();
+
+ // First, update the user profile with any changed form data
+ console.log('⏳ Updating user profile if values changed...');
+ const userRef = db.collection('users').where('uid', '==', user.uid);
+ const userSnapshot = await userRef.get();
+
+ if (!userSnapshot.empty) {
+ const userDocRef = userSnapshot.docs[0].ref;
+ const userData = userSnapshot.docs[0].data();
+
+ // Check if any profile data has been updated
+ const updatedProfileData = {
+ firstname:
+ formData.firstname !== userData.firstname
+ ? formData.firstname
+ : userData.firstname,
+ lastname:
+ formData.lastname !== userData.lastname
+ ? formData.lastname
+ : userData.lastname,
+ phonenumber:
+ formData.phone !== userData.phonenumber
+ ? formData.phone
+ : userData.phonenumber,
+ department:
+ formData.department !== userData.department
+ ? formData.department
+ : userData.department,
+ degree:
+ formData.degree !== userData.degree
+ ? formData.degree
+ : userData.degree,
+ gpa: formData.gpa !== userData.gpa ? formData.gpa : userData.gpa,
+ graduationdate:
+ formData.graduationDate !== userData.graduationdate
+ ? formData.graduationDate
+ : userData.graduationdate,
+ };
+
+ // Only update fields that have changed
+ const fieldsToUpdate = Object.entries(updatedProfileData)
+ .filter(([key, value]) => userData[key] !== value)
+ .reduce((obj, [key, value]) => ({ ...obj, [key]: value }), {});
+
+ if (Object.keys(fieldsToUpdate).length > 0) {
+ await userDocRef.update(fieldsToUpdate);
+ console.log('User profile updated with new values');
+ } else {
+ console.log('No profile updates needed');
+ }
+ }
+
+ const parentDocRef = doc(db, 'research-listings', listingId);
+ const noteColRef = collection(parentDocRef, 'applications');
+ const finalFormData = {
+ uid: user.uid,
+ app_status: 'Pending',
+ date: new Date().toLocaleDateString(),
+ ...formData,
+ };
+ await addDoc(noteColRef, finalFormData);
+ alert('Application submitted successfully!');
+ setSubmitted(true);
+ onClose();
+ } catch (err) {
+ if (err instanceof Error) {
+ console.error('🔥 Submission failed:', err.message);
+ alert('Submission failed: ' + err.message);
+ } else {
+ console.error('🔥 Submission failed with unknown error:', err);
+ alert('Submission failed. Unknown error occurred.');
+ }
+
+ setError('Submission failed. Please try again.');
+ }
+ };
+
+ return (
+ <>
+
+ Apply for Research Position
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cancel
+
+ Submit
+
+
+
+
+ setSubmitted(false)}
+ >
+ Application submitted!
+
+
+ setError('')}
+ >
+ {error}
+
+ >
+ );
+};
+
+export default ModalApplicationForm;
diff --git a/src/components/Research/MoreInfoButton.tsx b/src/components/Research/MoreInfoButton.tsx
new file mode 100644
index 0000000..97fd097
--- /dev/null
+++ b/src/components/Research/MoreInfoButton.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import Button from '@mui/material/Button';
+import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
+
+interface MoreInfoButtonProps {
+ onClick: () => void; // Function signature for the click handler
+}
+
+const MoreInfoButton: React.FC = ({ onClick }) => {
+ return (
+ }
+ sx={{
+ textTransform: 'none', // Keep text as "More Info" instead of uppercase
+ borderColor: '#ddd', // Lighter border color
+ borderRadius: '8px', // Adjust corner radius
+ color: '#000', // Text/icon color
+ }}
+ onClick={onClick} // Pass the onClick prop to the Button
+ >
+ More Info
+
+ );
+};
+
+export default MoreInfoButton;
diff --git a/src/components/Research/ProjectCard.tsx b/src/components/Research/ProjectCard.tsx
new file mode 100644
index 0000000..876578b
--- /dev/null
+++ b/src/components/Research/ProjectCard.tsx
@@ -0,0 +1,275 @@
+import React, { useState, useEffect, useRef } from 'react';
+import {
+ Card,
+ CardContent,
+ Typography,
+ Button,
+ Box,
+ Grid,
+} from '@mui/material';
+import ModalApplicationForm from './ModalApplicationForm';
+import {
+ collection,
+ addDoc,
+ updateDoc,
+ doc,
+ where,
+ query,
+ documentId,
+ getDocs,
+} from 'firebase/firestore';
+import firebase from '@/firebase/firebase_config';
+
+interface ProjectCardProps {
+ userRole: string;
+ uid?: string;
+ project_title: string;
+ department: string;
+ faculty_mentor: {};
+ terms_available: string;
+ student_level: string;
+ project_description: string;
+ faculty_members?: string[];
+ phd_student_mentor?: {} | string;
+ prerequisites?: string;
+ credit?: string;
+ stipend?: string;
+ application_requirements?: string;
+ application_deadline?: string;
+ website?: string;
+ applications?: any[];
+ onEdit?: () => void;
+ onShowApplications?: () => void;
+ onDelete?: () => void;
+ listingId: string;
+}
+
+const ProjectCard: React.FC = ({
+ userRole,
+ uid,
+ project_title,
+ department,
+ faculty_mentor,
+ terms_available,
+ student_level,
+ project_description,
+ faculty_members = [],
+ listingId,
+ phd_student_mentor,
+ prerequisites,
+ credit,
+ stipend,
+ application_requirements,
+ application_deadline,
+ website,
+ applications = [],
+ onEdit,
+ onShowApplications,
+ onDelete,
+}) => {
+ const [expanded, setExpanded] = useState(false);
+ const [needsExpansion, setNeedsExpansion] = useState(false);
+ const descriptionRef = useRef(null);
+ const isFacultyInvolved =
+ userRole === 'faculty' && faculty_members.includes(uid || '');
+ const [openModal, setOpenModal] = useState(false);
+
+ useEffect(() => {
+ const checkTextOverflow = () => {
+ const element = descriptionRef.current;
+ if (!element) return;
+
+ if (expanded) {
+ setNeedsExpansion(true);
+ return;
+ }
+
+ const isOverflowing = element.scrollHeight > element.clientHeight;
+ setNeedsExpansion(isOverflowing);
+ };
+
+ checkTextOverflow();
+
+ window.addEventListener('resize', checkTextOverflow);
+ return () => window.removeEventListener('resize', checkTextOverflow);
+ }, [expanded, project_description]);
+
+ const handleModalOpen = async () => {
+ // for (const application of applications) {
+ // if (application?.uid === uid) {
+ // alert('You have already applied to this project.');
+ // return;
+ // }
+ // }
+
+ setOpenModal(true);
+ };
+
+ return (
+ <>
+
+
+
+ {project_title}
+
+
+ {department}
+
+
+
+
+ Mentor Information
+
+
+ Faculty Mentor: {' '}
+ {Object.values(faculty_mentor).join(', ')}
+
+
+ PhD Student Mentor: {' '}
+ {typeof phd_student_mentor === 'string'
+ ? phd_student_mentor
+ : Object.entries(phd_student_mentor ?? {})
+ .map(([k, v]) => (k === 'info' ? v : `${v}, ${k}`))
+ .join(' ')}
+
+
+
+
+
+ Academic Information
+
+
+ Student Level: {student_level}
+
+
+ Terms Available: {terms_available}
+
+
+ Prerequisites: {prerequisites}
+
+
+ Credit: {credit}
+
+
+ Stipend: {stipend}
+
+
+
+
+
+ Application Details
+
+
+ Application Requirements: {' '}
+ {application_requirements}
+
+
+ Application Deadline: {application_deadline}
+
+
+ Website: {' '}
+ {website &&
+ !['n/a', 'na', 'none', 'no', ''].includes(
+ website.toLowerCase().trim()
+ ) ? (
+
+ {website}
+
+ ) : (
+ 'None provided'
+ )}
+
+
+
+
+
+ Research Description
+
+
+ {project_description}
+
+
+
+
+
+ {needsExpansion ? (
+ setExpanded(!expanded)}>
+ {expanded ? 'Read Less' : 'Read More'}
+
+ ) : (
+
+ )}
+ {userRole === 'student_applying' || userRole === 'student_applied' ? (
+ handleModalOpen()}
+ >
+ Apply
+
+ ) : isFacultyInvolved ? (
+
+
+ Edit Application
+
+
+ Show Applications
+
+
+ Delete Application
+
+
+ ) : null}
+
+
+ setOpenModal(false)}
+ listingId={listingId}
+ />
+ >
+ );
+};
+
+export default ProjectCard;
diff --git a/src/components/Research/ReviewModal.tsx b/src/components/Research/ReviewModal.tsx
new file mode 100644
index 0000000..4f07cd4
--- /dev/null
+++ b/src/components/Research/ReviewModal.tsx
@@ -0,0 +1,303 @@
+import React, { useState } from 'react';
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+ TextField,
+ Typography,
+ Box,
+ Divider,
+ SxProps,
+ Paper,
+ Grid,
+} from '@mui/material';
+import { Theme } from '@emotion/react';
+
+interface FormData {
+ item: any;
+ buttonText?: string;
+ buttonStyle?: SxProps;
+}
+
+// Fields that should be hidden from the review
+const HIDDEN_FIELDS = ['id', 'uid', 'docID', 'appid', '_id', 'app_id', 'key'];
+
+// Fields that should be displayed first and in a specific order
+const PRIORITY_FIELDS = [
+ 'firstname',
+ 'lastname',
+ 'email',
+ 'phone',
+ 'department',
+ 'degree',
+ 'gpa',
+ 'graduationDate',
+ 'project_title',
+ 'qualifications',
+ 'weeklyHours',
+ 'resume',
+ 'app_status',
+ 'date_applied',
+];
+
+// Fields that should be displayed with more space (multiline)
+const MULTILINE_FIELDS = [
+ 'qualifications',
+ 'project_description',
+ 'message',
+ 'additional_info',
+];
+
+// Human-readable field labels
+const FIELD_LABELS: Record = {
+ firstname: 'First Name',
+ lastname: 'Last Name',
+ qualifications: 'Qualifications',
+ project_title: 'Project Title',
+ terms_available: 'Terms Available',
+ student_level: 'Student Level',
+ degree: 'Degree',
+ gpa: 'GPA',
+ date_applied: 'Date Applied',
+ app_status: 'Application Status',
+};
+
+/** ReviewModal component for viewing application details */
+const ReviewModal: React.FC = ({
+ item,
+ buttonText = 'Review',
+ buttonStyle,
+}) => {
+ const [open, setOpen] = useState(false);
+
+ /** Opens the dialog (modal). */
+ const handleOpen = () => {
+ setOpen(true);
+ };
+
+ /** Closes the dialog (modal). */
+ const handleClose = () => {
+ setOpen(false);
+ };
+
+ // Get formatted field label
+ const getFieldLabel = (key: string): string => {
+ // Return predefined label if available
+ if (FIELD_LABELS[key]) return FIELD_LABELS[key];
+
+ // Format camelCase to Title Case
+ return key
+ .replace(/([A-Z])/g, ' $1') // Insert a space before all capital letters
+ .replace(/_/g, ' ') // Replace underscores with spaces
+ .replace(
+ /\w\S*/g,
+ (txt) => txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase()
+ ); // Capitalize first letter
+ };
+
+ // Check if field should be displayed
+ const shouldDisplayField = (key: string, value: any): boolean => {
+ if (HIDDEN_FIELDS.includes(key.toLowerCase())) return false;
+ if (
+ key.toLowerCase().includes('id') &&
+ typeof value === 'string' &&
+ value.length > 20
+ )
+ return false;
+ if (value === undefined || value === null) return false;
+ if (typeof value === 'object' && Object.keys(value).length === 0)
+ return false;
+ return true;
+ };
+
+ // Format field value for display
+ const formatFieldValue = (key: string, value: any): string => {
+ if (value === null || value === undefined) return '';
+ if (typeof value === 'object') return JSON.stringify(value);
+ return String(value);
+ };
+
+ // Order fields for display
+ const getOrderedFields = () => {
+ const entries = Object.entries(item).filter(([key, value]) =>
+ shouldDisplayField(key, value)
+ );
+
+ // Extract priority fields first (in specified order)
+ const priorityEntries = PRIORITY_FIELDS.map((key) =>
+ entries.find(([entryKey]) => entryKey.toLowerCase() === key.toLowerCase())
+ ).filter((entry) => entry !== undefined) as [string, any][];
+
+ // Get remaining fields
+ const remainingEntries = entries.filter(
+ ([key]) =>
+ !PRIORITY_FIELDS.some(
+ (priorityKey) => priorityKey.toLowerCase() === key.toLowerCase()
+ )
+ );
+
+ return [...priorityEntries, ...remainingEntries];
+ };
+
+ return (
+
+ {/* Button to open the modal */}
+
+ {buttonText}
+
+
+
+
+ Review Application
+
+
+
+
+ {item.project_title && (
+
+ {item.project_title}
+
+ )}
+
+ {item.date_applied && (
+
+ Applied: {item.date_applied}
+
+ )}
+
+
+ {/* Personal Info */}
+
+ Personal Information
+
+
+
+ {['firstname', 'lastname', 'email', 'phone'].map((key) =>
+ item[key] ? (
+
+
+ {getFieldLabel(key)}
+
+ {formatFieldValue(key, item[key])}
+
+ ) : null
+ )}
+
+
+ {/* Academic Info */}
+
+ Academic Background
+
+
+
+ {['department', 'degree', 'gpa', 'student_level'].map((key) =>
+ item[key] ? (
+
+
+ {getFieldLabel(key)}
+
+ {formatFieldValue(key, item[key])}
+
+ ) : null
+ )}
+
+
+ {/* Application Info */}
+
+ Application Details
+
+
+
+ {['weeklyHours', 'resume', 'app_status'].map((key) =>
+ item[key] ? (
+
+
+ {getFieldLabel(key)}
+
+ {key === 'resume' ? (
+
+
+ View Resume
+
+
+ ) : (
+ {formatFieldValue(key, item[key])}
+ )}
+
+ ) : null
+ )}
+
+ {item.qualifications && (
+
+
+ {getFieldLabel('qualifications')}
+
+
+
+ {formatFieldValue('qualifications', item.qualifications)}
+
+
+
+ )}
+
+
+
+
+
+ Close
+
+
+
+
+ );
+};
+
+export default ReviewModal;
diff --git a/src/components/Research/SearchBox.tsx b/src/components/Research/SearchBox.tsx
new file mode 100644
index 0000000..7ffc03a
--- /dev/null
+++ b/src/components/Research/SearchBox.tsx
@@ -0,0 +1,47 @@
+import React from 'react';
+import { Box, InputBase, SxProps, Theme } from '@mui/material';
+import MenuIcon from '@mui/icons-material/Menu';
+import SearchIcon from '@mui/icons-material/Search';
+interface SearchBoxProps {
+ placeholder?: string;
+ sx?: SxProps;
+ researchListingsFunc: () => Promise;
+}
+
+const SearchBox: React.FC = ({
+ placeholder = 'Hinted search text',
+ sx,
+ researchListingsFunc,
+}) => {
+ return (
+
+
+
+ researchListingsFunc()} />
+
+ );
+};
+
+export default SearchBox;
diff --git a/src/components/Research/StudentResearchView.tsx b/src/components/Research/StudentResearchView.tsx
new file mode 100644
index 0000000..0b4cf22
--- /dev/null
+++ b/src/components/Research/StudentResearchView.tsx
@@ -0,0 +1,319 @@
+import React, { useEffect, useState, useRef } from 'react';
+import {
+ Box,
+ TextField,
+ FormControl,
+ InputLabel,
+ Select,
+ MenuItem,
+ Button,
+ Typography,
+ Grid,
+ Container,
+} from '@mui/material';
+import ProjectCard from '@/components/Research/ProjectCard';
+import ApplicationCard from '@/components/Research/ApplicationCard';
+
+interface StudentResearchViewProps {
+ researchListings: any[];
+ researchApplications: any[];
+ role: string;
+ uid: string;
+ department: string;
+ setDepartment: (value: string) => void;
+ studentLevel: string;
+ setStudentLevel: (value: string) => void;
+ termsAvailable: string;
+ setTermsAvailable: (value: string) => void;
+ getResearchListings: () => void;
+ setResearchListings: (listings: any[]) => void;
+ getApplications: () => void;
+ setResearchApplications: (Applications: any[]) => void;
+}
+
+const StudentResearchView: React.FC = ({
+ researchListings,
+ researchApplications,
+ role,
+ uid,
+ department,
+ setDepartment,
+ setStudentLevel,
+ setTermsAvailable,
+ getResearchListings,
+ setResearchListings,
+ getApplications,
+}) => {
+ const [myApplications, showMyApplications] = useState(true);
+ const [originalListings, setOriginalListings] = useState([]);
+ const searchInputRef = useRef(null);
+
+ useEffect(() => {
+ if (researchListings.length > 0 && originalListings.length === 0) {
+ setOriginalListings([...researchListings]);
+ }
+ }, [researchListings, originalListings.length]);
+
+ useEffect(() => {
+ getApplications();
+ }, []);
+
+ const handleSearch = (searchText: string) => {
+ if (!searchText && department === '') {
+ setResearchListings([...originalListings]);
+ return;
+ }
+
+ const listingsToFilter =
+ originalListings.length > 0
+ ? [...originalListings]
+ : [...researchListings];
+
+ let filteredListings = listingsToFilter;
+
+ // Text search
+ if (searchText) {
+ filteredListings = filteredListings.filter((item) => {
+ const searchLower = searchText.toLowerCase();
+
+ const titleMatch =
+ item.project_title &&
+ typeof item.project_title === 'string' &&
+ item.project_title.toLowerCase().includes(searchLower);
+
+ const descriptionMatch =
+ item.project_description &&
+ typeof item.project_description === 'string' &&
+ item.project_description.toLowerCase().includes(searchLower);
+
+ const mentorMatch =
+ item.faculty_mentor &&
+ typeof item.faculty_mentor === 'string' &&
+ item.faculty_mentor.toLowerCase().includes(searchLower);
+
+ return titleMatch || descriptionMatch || mentorMatch;
+ });
+ console.log('Filtered Listings: ', filteredListings);
+ }
+
+ // Department filter with special handling for CISE
+ if (department) {
+ filteredListings = filteredListings.filter((item) => {
+ const normalized = item.department?.toLowerCase().trim();
+
+ if (
+ department === 'Computer and Information Sciences and Engineering'
+ ) {
+ return (
+ normalized === 'computer and information science and engineering' ||
+ normalized === 'computer and information sciences and engineering'
+ );
+ }
+
+ return normalized === department.toLowerCase();
+ });
+
+ console.log('Filtered Listings by Department: ', filteredListings);
+ }
+
+ setResearchListings(filteredListings);
+ };
+
+ const handleClearFilters = () => {
+ setDepartment('');
+ setStudentLevel('');
+ setTermsAvailable('');
+
+ if (searchInputRef.current) {
+ searchInputRef.current.value = '';
+ }
+
+ if (originalListings.length > 0) {
+ setResearchListings([...originalListings]);
+ } else {
+ getResearchListings();
+ }
+ };
+
+ const handleDepartmentChange = (value: string) => {
+ setDepartment(value);
+ const searchText = searchInputRef.current?.value || '';
+ handleSearch(searchText);
+ };
+
+ return (
+
+
+ Research
+
+ showMyApplications(!myApplications)}
+ >
+ {myApplications ? 'Show My Applications' : 'Switch to Default View'}
+
+
+
+
+ {/* Only show search controls when viewing research listings (not applications) */}
+ {myApplications ? (
+
+ {
+ if (e.target.value === '') {
+ handleSearch('');
+ }
+ }}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ e.preventDefault();
+ const searchText = (e.target as HTMLInputElement).value;
+ handleSearch(searchText);
+ }
+ }}
+ sx={{ flex: 1 }}
+ />
+
+
+ Clear
+
+
+
+ Department
+
+ handleDepartmentChange(e.target.value as string)
+ }
+ >
+ All
+
+ ECE
+
+
+ CISE
+
+ Education
+
+
+
+ ) : null}
+
+ {/* Content Display remains the same */}
+ {myApplications ? (
+
+ {researchListings.map((item, index) => (
+
+
+
+ ))}
+
+ ) : (
+
+ {researchApplications.map((item, index) => {
+ return (
+
+
+
+ );
+ })}
+
+ )}
+
+
+ );
+};
+
+export default StudentResearchView;
diff --git a/src/oldPages/Apply/page.tsx b/src/oldPages/Apply/page.tsx
index 1d83daa..5a88520 100644
--- a/src/oldPages/Apply/page.tsx
+++ b/src/oldPages/Apply/page.tsx
@@ -60,10 +60,53 @@ export default function Application() {
const { user } = useAuth();
const userId = user.uid;
+ const [firstname, setFirstname] = useState('');
+ const [lastname, setLastname] = useState('');
+ const [email, setEmail] = useState('');
+ const [phonenumber, setPhonenumber] = useState('');
+ const [gpa, setGpa] = useState('');
+ const [department, setDepartment] = useState('');
+
+ React.useEffect(() => {
+ const fetchProfileData = async () => {
+ if (!user) return;
+
+ try {
+ const db = firebase.firestore();
+ const snapshot = await db
+ .collection('users') // Adjust collection name if needed
+ .where('uid', '==', user.uid) // Ensure the field matches your Firestore schema
+ .get();
+
+ if (!snapshot.empty) {
+ const profileData = snapshot.docs[0].data();
+ console.log('✅ Profile data:', profileData);
+
+ // Set individual state variables with profile data
+ setFirstname(profileData.firstname || '');
+ setLastname(profileData.lastname || '');
+ setEmail(profileData.email || '');
+ setPhonenumber(profileData.phonenumber || '');
+ setGpa(profileData.gpa || '');
+ setDepartment(profileData.department || '');
+ } else {
+ console.warn('⚠️ No matching profile found for user.uid');
+ }
+ } catch (error) {
+ console.error('Error fetching profile data:', error);
+ }
+ };
+
+ if (user?.uid) {
+ fetchProfileData();
+ }
+ }, [user]);
+
// get the current date in month/day/year format
const current = new Date();
- const current_date = `${current.getMonth() + 1
- }-${current.getDate()}-${current.getFullYear()}`;
+ const current_date = `${
+ current.getMonth() + 1
+ }-${current.getDate()}-${current.getFullYear()}`;
// extract the nationality
const [nationality, setNationality] = React.useState(null);
@@ -176,15 +219,14 @@ export default function Application() {
// extract the specific user data from the form data into a parsable object
const applicationData = {
- firstname: formData.get('firstName') as string,
- lastname: formData.get('lastName') as string,
- email: formData.get('email') as string,
- ufid: formData.get('ufid') as string,
- phonenumber: formData.get('phone-number') as string,
- gpa: formData.get('gpa-select') as string,
- department: formData.get('department-select') as string,
- degree: formData.get('degrees-radio-group') as string,
- semesterstatus: formData.get('semstatus-radio-group') as string,
+ firstname: (firstname as string) || '',
+ lastname: (lastname as string) || '',
+ email: (email as string) || '',
+ phonenumber: (phonenumber as string) || '',
+ gpa: gpa as string,
+ department: (department as string) || '',
+ degree: (formData.get('degrees-radio-group') as string) || '',
+ semesterstatus: (formData.get('semstatus-radio-group') as string) || '',
additionalprompt: additionalPromptValue,
nationality: nationality as string,
englishproficiency: 'NA',
@@ -199,6 +241,8 @@ export default function Application() {
resume_link: formData.get('resumeLink') as string,
};
+ console.log('Application Data:', applicationData);
+
if (!applicationData.email.includes('ufl')) {
toast.error('Please enter a valid ufl email!');
setLoading(false);
@@ -211,42 +255,10 @@ export default function Application() {
toast.error('Please enter a valid last name!');
setLoading(false);
return;
- } else if (applicationData.ufid == '') {
- toast.error('Please enter a valid ufid!');
- setLoading(false);
- return;
} else if (applicationData.phonenumber === '') {
toast.error('Please enter a valid phone number!');
setLoading(false);
return;
- } else if (
- applicationData.degree === null ||
- applicationData.degree === ''
- ) {
- toast.error('Please select a degree!');
- setLoading(false);
- return;
- } else if (
- applicationData.department === null ||
- applicationData.department === ''
- ) {
- toast.error('Please select a department!');
- setLoading(false);
- return;
- } else if (
- applicationData.semesterstatus === null ||
- applicationData.semesterstatus === ''
- ) {
- toast.error('Please select a semester status!');
- setLoading(false);
- return;
- } else if (
- applicationData.resume_link === null ||
- applicationData.resume_link === ''
- ) {
- toast.error('Please provide a resume link!');
- setLoading(false);
- return;
} else if (
applicationData.position === null ||
applicationData.position === ''
@@ -258,10 +270,6 @@ export default function Application() {
toast.error('Please enter your available hours!');
setLoading(false);
return;
- } else if (applicationData.available_semesters.length == 0) {
- toast.error('Please enter your available semesters!');
- setLoading(false);
- return;
} else if (coursesArray.length == 0) {
toast.error('Please enter your courses!');
setLoading(false);
@@ -368,14 +376,13 @@ export default function Application() {
);
setNames(data);
console.log(names);
-
} catch (err) {
console.log(err);
}
}
fetchData();
}, []);
- console.log("eek");
+ console.log('eek');
return (
<>
@@ -405,98 +412,89 @@ export default function Application() {
}}
>
-
-
- Personal Information
-
-
+
+ Personal Information
+
-
+
-
+
-
+
-
+
+
-
+
-
-
-
-
-
+
-
-
-
-
-
Position Information
@@ -561,13 +559,6 @@ export default function Application() {
-
-
- Please provide your most recently calculated cumulative UF
- GPA.
-
-
-
Please upload a google drive link to your resume.
diff --git a/src/types/User.ts b/src/types/User.ts
index 883cfd1..b070c9f 100644
--- a/src/types/User.ts
+++ b/src/types/User.ts
@@ -34,14 +34,14 @@ export type Role =
| 'student_applied'
| 'student_accepted'
| 'student_denied';
-
+
export const roleMapping: Record = {
- Student: "Student",
- admin: "Admin",
- faculty: "Faculty",
- unapproved: "Unapproved",
- student_applying: "Student",
- student_applied: "Student",
- student_accepted: "Student (Accepted)",
- student_denied: "Student (Denied)",
-};
\ No newline at end of file
+ Student: 'Student',
+ admin: 'Admin',
+ faculty: 'Faculty',
+ unapproved: 'Unapproved',
+ student_applying: 'Student',
+ student_applied: 'Student',
+ student_accepted: 'Student (Accepted)',
+ student_denied: 'Student (Denied)',
+};