From 1da48177ab4a88c5f9b3a33e6df9c5dd90ef4d71 Mon Sep 17 00:00:00 2001 From: Veriny <2002.ijustin@gmail.com> Date: Mon, 18 Sep 2023 17:59:58 -0700 Subject: [PATCH 01/18] modified attendence query so only untaken attendences are deleted when students are dropped pre-commit fixes fix merge conflict --- csm_web/scheduler/views/student.py | 41 +++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/csm_web/scheduler/views/student.py b/csm_web/scheduler/views/student.py index fc3d29c8..3f2b696d 100644 --- a/csm_web/scheduler/views/student.py +++ b/csm_web/scheduler/views/student.py @@ -1,16 +1,14 @@ from django.core.exceptions import ObjectDoesNotExist from django.db.models import Q from django.utils import timezone -from scheduler.models import Attendance, SectionOccurrence from rest_framework import status, viewsets from rest_framework.decorators import action from rest_framework.exceptions import PermissionDenied from rest_framework.response import Response -import datetime -from .utils import log_str, logger, get_object_or_error from ..models import Student from ..serializers import AttendanceSerializer, StudentSerializer +from .utils import get_object_or_error, log_str, logger class StudentViewSet(viewsets.GenericViewSet): @@ -28,6 +26,10 @@ def get_queryset(self): @action(detail=True, methods=["patch"]) def drop(self, request, pk=None): + """ + Drops a student form a course. + PATCH: Drop a given student. Check for student ban if coordinator made request + """ student = get_object_or_error(self.get_queryset(), pk=pk) is_coordinator = student.course.coordinator_set.filter( user=request.user @@ -43,7 +45,10 @@ def drop(self, request, pk=None): student.course.whitelist.remove(student.user) student.save() logger.info( - f" User {log_str(request.user)} dropped Section {log_str(student.section)} for Student user {log_str(student.user)}" + " User %s dropped section %s for Student %s.", + log_str(request.user), + log_str(student.section), + log_str(student.user), ) # filter attendances and delete future attendances now = timezone.now().astimezone(timezone.get_default_timezone()) @@ -51,15 +56,25 @@ def drop(self, request, pk=None): Q( sectionOccurrence__date__gte=now.date(), sectionOccurrence__section=student.section, + presence="", ) ).delete() logger.info( - f" Deleted {num_deleted} attendances for user {log_str(student.user)} in Section {log_str(student.section)} after {now.date()}" + " Deleted %s attendances for user %s in Section %s after %s", + num_deleted, + log_str(student.user), + log_str(student.section), + now.date(), ) return Response(status=status.HTTP_204_NO_CONTENT) @action(detail=True, methods=["get", "put"]) def attendances(self, request, pk=None): + """ + Method for updating attendances. + GET: Gets the attendances for a student + PUT: Updates the attendances for a student + """ student = get_object_or_error(self.get_queryset(), pk=pk) if request.method == "GET": return Response( @@ -81,18 +96,26 @@ def attendances(self, request, pk=None): ) except ObjectDoesNotExist: logger.error( - f" Could not record attendance for User {log_str(request.user)}, used non-existent attendance id {request.data['id']}" + ( + " Could not record attendance for User %s, used" + " non-existent attendance ID %s" + ), + log_str(request.user), + request.data["id"], ) return Response(status=status.HTTP_400_BAD_REQUEST) if serializer.is_valid(): attendance = serializer.save() logger.info( - f" Attendance {log_str(attendance)} recorded for User {log_str(request.user)}" + " Attendance %s recorded for User %s", + log_str(attendance), + log_str(request.user), ) return Response(status=status.HTTP_204_NO_CONTENT) logger.error( - f" Could not record attendance for User {log_str(request.user)}, errors: {serializer.errors}" + " Could not record attendance for User %s, errors: %s", + log_str(request.user), + serializer.errors, ) return Response(serializer.errors, status=status.HTTP_422_UNPROCESSABLE_ENTITY) - From 2b4488b7a6f07b4b204c0988b14ff911bc3877a9 Mon Sep 17 00:00:00 2001 From: Veriny <2002.ijustin@gmail.com> Date: Mon, 16 Oct 2023 19:35:20 -0700 Subject: [PATCH 02/18] backend almost --- csm_web/scheduler/models.py | 12 +++++++++++ csm_web/scheduler/serializers.py | 3 ++- csm_web/scheduler/views/user.py | 34 ++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/csm_web/scheduler/models.py b/csm_web/scheduler/models.py index 9e367ab6..d22abf01 100644 --- a/csm_web/scheduler/models.py +++ b/csm_web/scheduler/models.py @@ -51,6 +51,18 @@ def week_bounds(date): class User(AbstractUser): priority_enrollment = models.DateTimeField(null=True, blank=True) + class Pronouns(models.TextChoices): + HE = "he/him" + SHE = "she/her" + THEY = "they/them" + NONE = "" + + pronouns = models.CharField(choices=Pronouns.choices, blank=True) + is_private = models.BooleanField(default=False) + pronunciation = models.CharField(max_length=20) + + bio = models.CharField(max_length=300) + def can_enroll_in_course(self, course, bypass_enrollment_time=False): """Determine whether this user is allowed to enroll in the given course.""" # check restricted first diff --git a/csm_web/scheduler/serializers.py b/csm_web/scheduler/serializers.py index cd31f741..72997289 100644 --- a/csm_web/scheduler/serializers.py +++ b/csm_web/scheduler/serializers.py @@ -167,7 +167,8 @@ class Meta: class UserSerializer(serializers.ModelSerializer): class Meta: model = User - fields = ("id", "email", "first_name", "last_name", "priority_enrollment") + fields = ("id", "email", "first_name", "last_name", "priority_enrollment", + "pronouns", "is_private", "pronunciation", "bio") class ProfileSerializer(serializers.Serializer): # pylint: disable=abstract-method diff --git a/csm_web/scheduler/views/user.py b/csm_web/scheduler/views/user.py index aeef9440..f4deca7f 100644 --- a/csm_web/scheduler/views/user.py +++ b/csm_web/scheduler/views/user.py @@ -2,6 +2,8 @@ from rest_framework.response import Response from rest_framework import status from rest_framework.decorators import api_view +from django_ratelimit.decorators import ratelimit + from .utils import viewset_with from ..models import Coordinator, User @@ -32,3 +34,35 @@ def userinfo(request): """ serializer = UserSerializer(request.user) return Response(serializer.data, status=status.HTTP_200_OK) + + +@api_view(["GET", "PUT"]) +def profile(request, pk=None): + """ + Function for handling user profile things + GET: Gets the information associated with the user profile. + PUT: Edit the profile of a user specified by the user id. ANY coordinator for ANY course can edit ANY profile. + Request: {'user_id': int} + Response: status code + """ + queryset = User.objects.all() + coordinators = Coordinator.objects.all() + + if request.method == "GET": + serializer = UserSerializer(queryset.get(pk=pk)) + return Response(serializer.data, status=status.HTTP_200_OK) + + if not ( + request.user.is_superuser + or (queryset.filter(pk=pk).exists() and queryset.get(pk=pk) == request.user) + or coordinators.filter(user=request.user).exists() + ): + raise PermissionDenied("You're not allowed to edit that user's profile.") + else: + data = {} + data["bio"] = request.data["bio"] + + +@api_view(["GET", "PUT"]) +def upload_image(request, pk=None): + pass From c9d86243e68b8af1e4a15e721f556dda80ef9476 Mon Sep 17 00:00:00 2001 From: Veriny <2002.ijustin@gmail.com> Date: Mon, 23 Oct 2023 03:29:21 -0700 Subject: [PATCH 03/18] backend endpoints added --- csm_web/scheduler/urls.py | 1 + csm_web/scheduler/views/user.py | 42 ++++++++++++++++++++------------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/csm_web/scheduler/urls.py b/csm_web/scheduler/urls.py index b997222f..4ab2c5a4 100644 --- a/csm_web/scheduler/urls.py +++ b/csm_web/scheduler/urls.py @@ -23,4 +23,5 @@ path("matcher//mentors/", views.matcher.mentors), path("matcher//configure/", views.matcher.configure), path("matcher//create/", views.matcher.create), + path("user//profile/", views.user.profile), ] diff --git a/csm_web/scheduler/views/user.py b/csm_web/scheduler/views/user.py index f4deca7f..d9f71d86 100644 --- a/csm_web/scheduler/views/user.py +++ b/csm_web/scheduler/views/user.py @@ -1,13 +1,11 @@ -from rest_framework.exceptions import PermissionDenied -from rest_framework.response import Response from rest_framework import status from rest_framework.decorators import api_view -from django_ratelimit.decorators import ratelimit - +from rest_framework.exceptions import PermissionDenied +from rest_framework.response import Response +from scheduler.serializers import UserSerializer -from .utils import viewset_with from ..models import Coordinator, User -from scheduler.serializers import UserSerializer +from .utils import viewset_with class UserViewSet(*viewset_with("list")): @@ -15,6 +13,9 @@ class UserViewSet(*viewset_with("list")): queryset = User.objects.all() def list(self, request): + """ + Lists users. + """ if not ( request.user.is_superuser or Coordinator.objects.filter(user=request.user).exists() @@ -39,9 +40,10 @@ def userinfo(request): @api_view(["GET", "PUT"]) def profile(request, pk=None): """ - Function for handling user profile things + Function for handling user profile things GET: Gets the information associated with the user profile. - PUT: Edit the profile of a user specified by the user id. ANY coordinator for ANY course can edit ANY profile. + PUT: Edit the profile of a user specified by the user id. + ANY coordinator for ANY course can edit ANY profile. Request: {'user_id': int} Response: status code """ @@ -58,11 +60,19 @@ def profile(request, pk=None): or coordinators.filter(user=request.user).exists() ): raise PermissionDenied("You're not allowed to edit that user's profile.") - else: - data = {} - data["bio"] = request.data["bio"] - - -@api_view(["GET", "PUT"]) -def upload_image(request, pk=None): - pass + user = queryset.get(pk=pk) + bio = request.data.get("bio") + pronunciation = request.data.get("pronunciation") + pronoun = request.data.get("pronouns") + private = request.data.get("is_private") + if bio is not None: + user.bio = bio + if pronunciation is not None: + user.pronunciation = pronunciation + if pronoun is not None: + user.pronouns = pronoun + if private is not None: + user.is_private = private + user.save() + serializer = UserSerializer(user) + return Response(serializer.data, status=status.HTTP_200_OK) From bf42c0c34a0a9743aadf22c2d323945141114820 Mon Sep 17 00:00:00 2001 From: Jacob Taegon Kim Date: Mon, 11 Sep 2023 21:31:35 -0700 Subject: [PATCH 04/18] Update user profile User profile template --- csm_web/frontend/src/components/App.tsx | 6 + .../frontend/src/components/UserProfile.tsx | 106 ++++++++++++++++++ csm_web/frontend/src/css/profile.scss | 84 ++++++++++++++ 3 files changed, 196 insertions(+) create mode 100644 csm_web/frontend/src/components/UserProfile.tsx create mode 100644 csm_web/frontend/src/css/profile.scss diff --git a/csm_web/frontend/src/components/App.tsx b/csm_web/frontend/src/components/App.tsx index 6799ce7c..6b4d2040 100644 --- a/csm_web/frontend/src/components/App.tsx +++ b/csm_web/frontend/src/components/App.tsx @@ -11,6 +11,7 @@ import Policies from "./Policies"; import { EnrollmentMatcher } from "./enrollment_automation/EnrollmentMatcher"; import { Resources } from "./resource_aggregation/Resources"; import Section from "./section/Section"; +import UserProfile from "./UserProfile"; import LogOutIcon from "../../static/frontend/img/log_out.svg"; import LogoNoText from "../../static/frontend/img/logo_no_text.svg"; @@ -39,6 +40,7 @@ const App = () => { } /> } /> } /> + } /> } /> @@ -85,6 +87,7 @@ function Header(): React.ReactElement { if ( location.pathname.startsWith("/resources") || location.pathname.startsWith("/matcher") || + location.pathname.startsWith("/profile") || location.pathname.startsWith("/policies") ) { isActive = false; @@ -135,6 +138,9 @@ function Header(): React.ReactElement { ) : null}
+ +

Profile

+

Policies

diff --git a/csm_web/frontend/src/components/UserProfile.tsx b/csm_web/frontend/src/components/UserProfile.tsx new file mode 100644 index 00000000..b6958da2 --- /dev/null +++ b/csm_web/frontend/src/components/UserProfile.tsx @@ -0,0 +1,106 @@ +import React from "react"; +import { useUserInfo } from "../utils/queries/base"; +import { UserInfo } from "../utils/types"; + +import "../css/profile.scss"; + +export const UserProfile = (): React.ReactElement => { + const { data: jsonUserInfo, isSuccess: userInfoLoaded } = useUserInfo(); + + let userInfo: UserInfo | null; + if (userInfoLoaded) { + let priorityEnrollment = undefined; + if (jsonUserInfo.priorityEnrollment) { + priorityEnrollment = new Date(Date.parse(jsonUserInfo.priorityEnrollment)); + } + const convertedUserInfo: UserInfo = { + ...jsonUserInfo, + priorityEnrollment + }; + userInfo = convertedUserInfo; + } else { + // not done loading yet + userInfo = null; + } + + return ( + +
{userInfoLoaded ? : <>}
+
+ ); +}; +interface UserInfoProps { + userInfo: UserInfo | null; +} + +const DisplayUser = ({ userInfo }: UserInfoProps) => { + return ( +
+ {userInfo !== null ? ( +
+
+
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+ + +
+
+
+
+ ) : ( + "" + )} +
+ ); +}; + +export default UserProfile; diff --git a/csm_web/frontend/src/css/profile.scss b/csm_web/frontend/src/css/profile.scss new file mode 100644 index 00000000..a3c03d9f --- /dev/null +++ b/csm_web/frontend/src/css/profile.scss @@ -0,0 +1,84 @@ +@use "base/variables" as *; + +.formbold-main-wrapper { + display: flex; + align-items: center; + justify-content: center; + padding: 48px; +} + +.formbold-form-wrapper { + margin: 0 auto; + max-width: 550px; + width: 100%; + background: white; +} + +.formbold-input-flex { + display: flex; + gap: 20px; + margin-bottom: 22px; +} +.formbold-input-flex > div { + width: 50%; + display: flex; + flex-direction: column-reverse; +} +.formbold-textarea { + display: flex; + flex-direction: column-reverse; +} + +.formbold-form-input { + width: 100%; + padding-bottom: 10px; + border: none; + border-bottom: 1px solid #dde3ec; + background: #ffffff; + font-weight: 500; + font-size: 16px; + color: #07074d; + outline: none; + resize: none; +} +.formbold-form-input::placeholder { + color: #536387; +} +.formbold-form-input:focus { + border-color: #6a64f1; +} +.formbold-form-label { + color: #07074d; + font-weight: 500; + font-size: 14px; + line-height: 24px; + display: block; + margin-bottom: 18px; +} +.formbold-form-input:focus + .formbold-form-label { + color: #6a64f1; +} + +.button-wrapper { + display: flex; + align-items: center; + margin: 10px 10px; + padding: 5px 5px; + justify-content: space-evenly; + flex-direction: row-reverse; +} + +.formbold-btn { + font-size: 16px; + border-radius: 5px; + padding: 12px 25px; + border: none; + font-weight: 500; + background-color: var(--csm-green); + color: white; + cursor: pointer; + margin-top: 25px; +} +.formbold-btn:hover { + box-shadow: 0px 3px 8px rgba(0, 0, 0, 0.05); +} From 61b5a195102b329851d50a85497f1140183c641a Mon Sep 17 00:00:00 2001 From: Jacob Taegon Kim Date: Mon, 2 Oct 2023 21:49:30 -0700 Subject: [PATCH 05/18] Added Edit button, Save button, patch form --- .../frontend/src/components/UserProfile.tsx | 133 ++++++++++++++++-- csm_web/frontend/src/css/profile.scss | 2 +- .../frontend/src/utils/queries/profiles.tsx | 51 +++++++ csm_web/frontend/src/utils/types.tsx | 3 + 4 files changed, 179 insertions(+), 10 deletions(-) create mode 100644 csm_web/frontend/src/utils/queries/profiles.tsx diff --git a/csm_web/frontend/src/components/UserProfile.tsx b/csm_web/frontend/src/components/UserProfile.tsx index b6958da2..52a26f24 100644 --- a/csm_web/frontend/src/components/UserProfile.tsx +++ b/csm_web/frontend/src/components/UserProfile.tsx @@ -1,6 +1,7 @@ -import React from "react"; +import React, { useState } from "react"; import { useUserInfo } from "../utils/queries/base"; import { UserInfo } from "../utils/types"; +import { useUserInfoUpdateMutation } from "../utils/queries/profiles"; import "../css/profile.scss"; @@ -29,11 +30,95 @@ export const UserProfile = (): React.ReactElement => { ); }; + interface UserInfoProps { userInfo: UserInfo | null; } const DisplayUser = ({ userInfo }: UserInfoProps) => { + /** + * Mutation to create a new section. + */ + const createSectionMutation = useUserInfoUpdateMutation(userInfo.id); + + const [editing, setEditing] = useState(false); + /** + * User First Name + */ + const [userFirstName, setUserFirstName] = useState(""); + + /** + * User Last Name + */ + const [userLastName, setUserLastName] = useState(""); + /** + * User email + */ + const [userEmail, setUserEmail] = useState(""); + /** + * User Pronoun + */ + const [userPronoun, setUserPronoun] = useState(""); + /** + * User Bio + */ + const [userBio, setUserBio] = useState(""); + + const handleEditing = () => { + setEditing(true); + }; + + const handleCancel = () => { + // Reset form data if necessary + setEditing(false); + }; + + /** + * Handle save. + */ + const handleSave = (event: React.MouseEvent): void => { + event.preventDefault(); + const data = { + userFirstName, + userLastName, + userEmail, + userBio, + userPronoun + }; + + createSectionMutation.mutate(data, { + onSuccess: () => { + setEditing(false); + } + }); + }; + + /** + * Handle the change of a form field. + */ + const handleChange = (name: string, value: string): void => { + switch (name) { + case "firstName": + setUserFirstName(value); + break; + case "lastName": + setUserLastName(value); + break; + case "email": + setUserEmail(value); + break; + case "pronouns": + setUserPronoun(value); + break; + case "bio": + setUserBio(value); + break; + default: + console.error("Unknown input name: " + name); + break; + } + }; + return (
{userInfo !== null ? ( @@ -46,8 +131,10 @@ const DisplayUser = ({ userInfo }: UserInfoProps) => { type="text" name="firstname" id="firstname" - placeholder={userInfo.firstName} + defaultValue={userInfo.firstName} className="formbold-form-input" + disabled={!editing} + onChange={e => handleChange("firstName", e.target.value)} />
@@ -56,8 +143,10 @@ const DisplayUser = ({ userInfo }: UserInfoProps) => { type="text" name="lastname" id="lastname" - placeholder={userInfo.lastName} + defaultValue={userInfo.lastName} className="formbold-form-input" + disabled={!editing} + onChange={e => handleChange("lastName", e.target.value)} />
@@ -69,13 +158,23 @@ const DisplayUser = ({ userInfo }: UserInfoProps) => { type="email" name="email" id="email" - placeholder={userInfo.email} + defaultValue={userInfo.email} className="formbold-form-input" + disabled={!editing} + onChange={e => handleChange("email", e.target.value)} />
- + handleChange("pronouns", e.target.value)} + />
@@ -86,14 +185,30 @@ const DisplayUser = ({ userInfo }: UserInfoProps) => { id="bio" placeholder="Write your bio..." className="formbold-form-input" + disabled={!editing} + onChange={e => handleChange("bio", e.target.value)} > -
- - -
+
+ <> + {!editing ? ( + + ) : ( + <> + + + + )} + +
) : ( diff --git a/csm_web/frontend/src/css/profile.scss b/csm_web/frontend/src/css/profile.scss index a3c03d9f..409003ac 100644 --- a/csm_web/frontend/src/css/profile.scss +++ b/csm_web/frontend/src/css/profile.scss @@ -75,7 +75,7 @@ border: none; font-weight: 500; background-color: var(--csm-green); - color: white; + color: black; cursor: pointer; margin-top: 25px; } diff --git a/csm_web/frontend/src/utils/queries/profiles.tsx b/csm_web/frontend/src/utils/queries/profiles.tsx new file mode 100644 index 00000000..f80df167 --- /dev/null +++ b/csm_web/frontend/src/utils/queries/profiles.tsx @@ -0,0 +1,51 @@ +/** + * Query hooks regarding sections. + */ + +import { useMutation, UseMutationResult, useQuery, useQueryClient, UseQueryResult } from "@tanstack/react-query"; +import { fetchNormalized, fetchWithMethod, HTTP_METHODS } from "../api"; +import { handleError, handlePermissionsError, handleRetry, PermissionError, ServerError } from "./helpers"; +import { DateTime } from "luxon"; + +/* ===== Mutation ===== */ +/** + * Hook to mutate user info + */ +export interface UpdateUserInfo { + id: number; + firstName: string; + lastName: string; + email: string; + priorityEnrollment?: DateTime; + isPrivate: boolean; + bio: string; + pronouns: string; +} + +export const useUserInfoUpdateMutation = (userId: number): UseMutationResult => { + const queryClient = useQueryClient(); + const mutationResult = useMutation( + async (body: UpdateUserInfo) => { + if (isNaN(userId)) { + throw new PermissionError("Invalid user id"); + } + const response = await fetchWithMethod(`/users/${userId}/`, HTTP_METHODS.PATCH, body); + if (response.ok) { + return; + } else { + handlePermissionsError(response.status); + throw new ServerError(`Failed to create section`); + } + }, + { + onSuccess: () => { + // invalidate all queries for the section + queryClient.invalidateQueries(["userinfo"]); + }, + retry: handleRetry + } + ); + + handleError(mutationResult); + return mutationResult; +}; diff --git a/csm_web/frontend/src/utils/types.tsx b/csm_web/frontend/src/utils/types.tsx index 78e9f215..bcc0e442 100644 --- a/csm_web/frontend/src/utils/types.tsx +++ b/csm_web/frontend/src/utils/types.tsx @@ -36,6 +36,9 @@ export interface UserInfo { lastName: string; email: string; priorityEnrollment?: DateTime; + isPrivate: boolean; + bio: string; + pronouns: string; } /** From fcf52e255741c8dea9492e290c23a2393afb5f70 Mon Sep 17 00:00:00 2001 From: Jacob Taegon Kim Date: Mon, 16 Oct 2023 20:18:47 -0700 Subject: [PATCH 06/18] modal for profile other students' pop up view --- .../components/section/MentorSectionInfo.tsx | 10 ++ .../src/components/section/ProfileModal.tsx | 109 ++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 csm_web/frontend/src/components/section/ProfileModal.tsx diff --git a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx index e2e76b30..b3ac1985 100644 --- a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx +++ b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx @@ -8,6 +8,7 @@ import MetaEditModal from "./MetaEditModal"; import { InfoCard, SectionSpacetime } from "./Section"; import SpacetimeDeleteModal from "./SpacetimeDeleteModal"; import SpacetimeEditModal from "./SpacetimeEditModal"; +import ProfileModal from "./ProfileModal"; import StudentDropper from "./StudentDropper"; import PencilIcon from "../../../static/frontend/img/pencil.svg"; @@ -90,6 +91,15 @@ export default function MentorSectionInfo({ /> )} {name || email} + + {showModal === ModalStates.SPACETIME_EDIT && } ) diff --git a/csm_web/frontend/src/components/section/ProfileModal.tsx b/csm_web/frontend/src/components/section/ProfileModal.tsx new file mode 100644 index 00000000..1e16cf86 --- /dev/null +++ b/csm_web/frontend/src/components/section/ProfileModal.tsx @@ -0,0 +1,109 @@ +import React, { useState } from "react"; +import LoadingSpinner from "../LoadingSpinner"; +import Modal from "../Modal"; +import { useUserInfo } from "../../utils/queries/base"; +import { UserInfo } from "../../utils/types"; + +interface ProfileModalProps { + closeModal: () => void; +} + +const ProfileModal = ({ closeModal }: ProfileModalProps): React.ReactElement => { + const { data: jsonUserInfo, isSuccess: userInfoLoaded } = useUserInfo(); + + let userInfo: UserInfo | null; + if (userInfoLoaded) { + let priorityEnrollment = undefined; + if (jsonUserInfo.priorityEnrollment) { + priorityEnrollment = new Date(Date.parse(jsonUserInfo.priorityEnrollment)); + } + const convertedUserInfo: UserInfo = { + ...jsonUserInfo, + priorityEnrollment + }; + userInfo = convertedUserInfo; + } else { + // not done loading yet + userInfo = null; + } + + return ( + +
+ {userInfo !== null ? ( +
+
+
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+
+
+ ) : ( + "" + )} +
+
+ ); +}; + +export default ProfileModal; From b923609792b008bfa9fc232dc7cccb2161152112 Mon Sep 17 00:00:00 2001 From: kevinbu233 Date: Mon, 16 Oct 2023 20:17:44 -0700 Subject: [PATCH 07/18] profile popup --- .../src/components/section/StudentSection.tsx | 109 +++++++++++++++++- .../frontend/src/utils/queries/sections.tsx | 25 +++- 2 files changed, 130 insertions(+), 4 deletions(-) diff --git a/csm_web/frontend/src/components/section/StudentSection.tsx b/csm_web/frontend/src/components/section/StudentSection.tsx index 7ba08464..2b7b2353 100644 --- a/csm_web/frontend/src/components/section/StudentSection.tsx +++ b/csm_web/frontend/src/components/section/StudentSection.tsx @@ -6,7 +6,8 @@ import { DEFAULT_TIMEZONE } from "../../utils/datetime"; import { useDropUserMutation, useStudentAttendances, - useStudentSubmitWordOfTheDayMutation + useStudentSubmitWordOfTheDayMutation, + useSectionStudents } from "../../utils/queries/sections"; import { Mentor, Override, Role, Spacetime } from "../../utils/types"; import LoadingSpinner from "../LoadingSpinner"; @@ -46,7 +47,8 @@ export default function StudentSection({ userRole={Role.STUDENT} links={[ ["Section", ""], - ["Attendance", "attendance"] + ["Attendance", "attendance"], + ["Students", "students"] ]} > @@ -54,6 +56,7 @@ export default function StudentSection({ path="attendance" element={} /> + } />
{mentor.name}
- {mentor.email} + {/* {mentor.email} */} + )} + {spacetimes.map(({ override, ...spacetime }, index) => ( 1} @@ -154,6 +159,46 @@ function DropSection({ profileId }: DropSectionProps) { } } +interface ProfileSectionProps { + profileId: number; +} + +enum ProfileSectionStage { + CLOSED = "CLOSED", + OPENED = "OPENED" +} + +function ProfileSection({ profileId }: ProfileSectionProps) { + // const studentDropMutation = useDropUserMutation(profileId); + const [stage, setStage] = useState(ProfileSectionStage.CLOSED); + + // const performDrop = () => { + // studentDropMutation.mutate(undefined, { + // onSuccess: () => { + // setStage(ProfileSectionStage.CLOSED); + // } + // }); + // }; + + switch (stage) { + case ProfileSectionStage.CLOSED: + return ( + + ); + case ProfileSectionStage.OPENED: + return ( + setStage(ProfileSectionStage.CLOSED)}> +
+
Are you sure you want to drop?
+

You are not guaranteed an available spot in another section!

+
+
+ ); + } +} + interface StudentSectionAttendanceProps { associatedProfileId: number; id: number; @@ -357,3 +402,61 @@ function StudentSectionAttendance({ associatedProfileId, id }: StudentSectionAtt ); } + +interface StudentListProps { + associatedProfileId: number; + id: number; +} + +// Need permission for student to load student list, or new backend point? +function StudentList({ associatedProfileId, id }: StudentListProps) { + const { + data: studentList, + isSuccess: listLoaded, + isError: listLoadError, + refetch: refetchStudentList + } = useSectionStudents(associatedProfileId); + + return listLoaded ? ( + +
+

Submit Word of the Day

+
+
+
+
+
+
+
+ + + + + + + + + {studentList + // convert to a table row + .map(({ name, email }) => { + // const [label, cssSuffix] = ATTENDANCE_LABELS[presence]; + // const attendanceColor = scssColors[`attendance-${cssSuffix}`]; + // const attendanceFgColor = scssColors[`attendance-${cssSuffix}-fg`]; + return ( + + + + + ); + })} + +
NameProfile
{name} +
{email}
+
+
+ ) : listLoadError ? ( +

Student List could not be loaded

+ ) : ( + + ); +} diff --git a/csm_web/frontend/src/utils/queries/sections.tsx b/csm_web/frontend/src/utils/queries/sections.tsx index a4bbcf8c..75b5aaa9 100644 --- a/csm_web/frontend/src/utils/queries/sections.tsx +++ b/csm_web/frontend/src/utils/queries/sections.tsx @@ -4,7 +4,7 @@ import { useMutation, UseMutationResult, useQuery, useQueryClient, UseQueryResult } from "@tanstack/react-query"; import { fetchNormalized, fetchWithMethod, HTTP_METHODS } from "../api"; -import { Attendance, RawAttendance, Section, Spacetime, Student } from "../types"; +import { Attendance, RawAttendance, Section, Spacetime, Student, UserInfo } from "../types"; import { handleError, handlePermissionsError, handleRetry, PermissionError, ServerError } from "./helpers"; /* ===== Queries ===== */ @@ -112,6 +112,29 @@ export const useStudentAttendances = (studentId: number): UseQueryResult => { +// const queryResult = useQuery( +// ["students", studentId, "attendance"], +// async () => { +// if (isNaN(studentId)) { +// throw new PermissionError("Invalid student id"); +// } +// const response = await fetchNormalized(`/students/${studentId}/attendances`); +// if (response.ok) { +// return await response.json(); +// } else { +// handlePermissionsError(response.status); +// throw new ServerError(`Failed to fetch student ${studentId} attendances`); +// } +// }, +// { retry: handleRetry } +// ); +// +// handleError(queryResult); +// return queryResult; +// }; + interface WordOfTheDayResponse { id: number; // section occurrence id wordOfTheDay: string; From cc36b6659c33b16b52e49fb64bab56057abfbef6 Mon Sep 17 00:00:00 2001 From: kevinbu233 Date: Mon, 16 Oct 2023 20:30:22 -0700 Subject: [PATCH 08/18] Center --- csm_web/frontend/src/components/section/StudentSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csm_web/frontend/src/components/section/StudentSection.tsx b/csm_web/frontend/src/components/section/StudentSection.tsx index 2b7b2353..f18d9aa8 100644 --- a/csm_web/frontend/src/components/section/StudentSection.tsx +++ b/csm_web/frontend/src/components/section/StudentSection.tsx @@ -183,7 +183,7 @@ function ProfileSection({ profileId }: ProfileSectionProps) { switch (stage) { case ProfileSectionStage.CLOSED: return ( - ); From a4cf29ee57a27fc1b09933fb5d3ba73f309610fd Mon Sep 17 00:00:00 2001 From: Jacob Taegon Kim Date: Mon, 16 Oct 2023 20:18:47 -0700 Subject: [PATCH 09/18] modal for profile other students' pop up view --- .../src/components/section/MentorSectionInfo.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx index b3ac1985..007116ff 100644 --- a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx +++ b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx @@ -6,13 +6,23 @@ import LoadingSpinner from "../LoadingSpinner"; import { CoordinatorAddStudentModal } from "./CoordinatorAddStudentModal"; import MetaEditModal from "./MetaEditModal"; import { InfoCard, SectionSpacetime } from "./Section"; +<<<<<<< HEAD +======= +import SpacetimeEditModal from "./SpacetimeEditModal"; +import ProfileModal from "./ProfileModal"; +import StudentDropper from "./StudentDropper"; +>>>>>>> f904047 (modal for profile) import SpacetimeDeleteModal from "./SpacetimeDeleteModal"; import SpacetimeEditModal from "./SpacetimeEditModal"; import ProfileModal from "./ProfileModal"; import StudentDropper from "./StudentDropper"; import PencilIcon from "../../../static/frontend/img/pencil.svg"; +<<<<<<< HEAD import XIcon from "../../../static/frontend/img/x.svg"; +======= +import EyeIcon from "../../../static/frontend/img/eye.svg"; +>>>>>>> f904047 (modal for profile) import "../../css/coordinator-add-student.scss"; From 7467124bb3ef43fccab8a5e76c721a85533335f8 Mon Sep 17 00:00:00 2001 From: Veriny <2002.ijustin@gmail.com> Date: Mon, 6 Nov 2023 19:46:45 -0800 Subject: [PATCH 10/18] migrations --- ..._user_is_private_user_pronouns_and_more.py | 42 +++++++++++++++++++ csm_web/scheduler/models.py | 16 +++---- 2 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 csm_web/scheduler/migrations/0033_user_bio_user_is_private_user_pronouns_and_more.py diff --git a/csm_web/scheduler/migrations/0033_user_bio_user_is_private_user_pronouns_and_more.py b/csm_web/scheduler/migrations/0033_user_bio_user_is_private_user_pronouns_and_more.py new file mode 100644 index 00000000..e565fb5b --- /dev/null +++ b/csm_web/scheduler/migrations/0033_user_bio_user_is_private_user_pronouns_and_more.py @@ -0,0 +1,42 @@ +# Generated by Django 4.2.1 on 2023-11-07 03:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("scheduler", "0032_word_of_the_day"), + ] + + operations = [ + migrations.AddField( + model_name="user", + name="bio", + field=models.CharField( + default="Default bio given to everyone~", max_length=300 + ), + ), + migrations.AddField( + model_name="user", + name="is_private", + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name="user", + name="pronouns", + field=models.CharField( + blank=True, + choices=[ + ("he/him", "He"), + ("she/her", "She"), + ("they/them", "They"), + ("", "None"), + ], + ), + ), + migrations.AddField( + model_name="user", + name="pronunciation", + field=models.CharField(default="", max_length=20), + ), + ] diff --git a/csm_web/scheduler/models.py b/csm_web/scheduler/models.py index d22abf01..c4890dcb 100644 --- a/csm_web/scheduler/models.py +++ b/csm_web/scheduler/models.py @@ -59,9 +59,9 @@ class Pronouns(models.TextChoices): pronouns = models.CharField(choices=Pronouns.choices, blank=True) is_private = models.BooleanField(default=False) - pronunciation = models.CharField(max_length=20) + pronunciation = models.CharField(max_length=20, default="") - bio = models.CharField(max_length=300) + bio = models.CharField(max_length=300, default="Default bio given to everyone~") def can_enroll_in_course(self, course, bypass_enrollment_time=False): """Determine whether this user is allowed to enroll in the given course.""" @@ -272,19 +272,15 @@ def save(self, *args, **kwargs): ): if settings.DJANGO_ENV != settings.DEVELOPMENT: logger.info( - ( - " SO automatically created for student" - " %s in course %s for date %s" - ), + " SO automatically created for student" + " %s in course %s for date %s", self.user.email, course.name, now.date(), ) logger.info( - ( - " Attendance automatically created for student" - " %s in course %s for date %s" - ), + " Attendance automatically created for student" + " %s in course %s for date %s", self.user.email, course.name, now.date(), From cfb9b54c2e196e29d64f8d3829f82c66a26d06e6 Mon Sep 17 00:00:00 2001 From: Jacob Taegon Kim Date: Mon, 6 Nov 2023 20:05:59 -0800 Subject: [PATCH 11/18] some changes --- .../components/section/MentorSectionInfo.tsx | 4 +- .../src/components/section/ProfileModal.tsx | 11 ++++- .../frontend/src/utils/queries/profiles.tsx | 49 ++++++++++++++++++- 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx index 007116ff..1a4ff0a4 100644 --- a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx +++ b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx @@ -109,7 +109,9 @@ export default function MentorSectionInfo({ > View - {showModal === ModalStates.SPACETIME_EDIT && } + {showModal === ModalStates.SPACETIME_EDIT && ( + + )} ) diff --git a/csm_web/frontend/src/components/section/ProfileModal.tsx b/csm_web/frontend/src/components/section/ProfileModal.tsx index 1e16cf86..56a8155b 100644 --- a/csm_web/frontend/src/components/section/ProfileModal.tsx +++ b/csm_web/frontend/src/components/section/ProfileModal.tsx @@ -2,14 +2,21 @@ import React, { useState } from "react"; import LoadingSpinner from "../LoadingSpinner"; import Modal from "../Modal"; import { useUserInfo } from "../../utils/queries/base"; +import { UseUserInfoWithId } from "../../utils/queries/profiles"; import { UserInfo } from "../../utils/types"; +import { useSectionStudents } from "../../utils/queries/sections"; interface ProfileModalProps { + id: number; closeModal: () => void; } -const ProfileModal = ({ closeModal }: ProfileModalProps): React.ReactElement => { - const { data: jsonUserInfo, isSuccess: userInfoLoaded } = useUserInfo(); +const ProfileModal = ({ id, closeModal }: ProfileModalProps): React.ReactElement => { + // const { data: jsonUserInfo, isSuccess: userInfoLoaded } = UseUserInfoWithId(id); + const { data: jsonUserInfo, isSuccess: userInfoLoaded } = useSectionStudents(id); + console.log(id); + console.log(); + console.log(jsonUserInfo); let userInfo: UserInfo | null; if (userInfoLoaded) { diff --git a/csm_web/frontend/src/utils/queries/profiles.tsx b/csm_web/frontend/src/utils/queries/profiles.tsx index f80df167..726157b7 100644 --- a/csm_web/frontend/src/utils/queries/profiles.tsx +++ b/csm_web/frontend/src/utils/queries/profiles.tsx @@ -6,6 +6,7 @@ import { useMutation, UseMutationResult, useQuery, useQueryClient, UseQueryResul import { fetchNormalized, fetchWithMethod, HTTP_METHODS } from "../api"; import { handleError, handlePermissionsError, handleRetry, PermissionError, ServerError } from "./helpers"; import { DateTime } from "luxon"; +import { RawUserInfo } from "../types"; /* ===== Mutation ===== */ /** @@ -22,6 +23,28 @@ export interface UpdateUserInfo { pronouns: string; } +/** + * Hook to get the user's info. + */ +export const useStudentsInfo = (): UseQueryResult => { + const queryResult = useQuery( + ["students"], + async () => { + const response = await fetchNormalized("/students"); + if (response.ok) { + return await response.json(); + } else { + handlePermissionsError(response.status); + throw new ServerError("Failed to fetch user info"); + } + }, + { retry: handleRetry } + ); + + handleError(queryResult); + return queryResult; +}; + export const useUserInfoUpdateMutation = (userId: number): UseMutationResult => { const queryClient = useQueryClient(); const mutationResult = useMutation( @@ -29,12 +52,12 @@ export const useUserInfoUpdateMutation = (userId: number): UseMutationResult => { + const queryResult = useQuery( + ["userinfo", id], + async () => { + const response = await fetchNormalized("/userinfo"); + if (response.ok) { + return await response.json(); + } else { + handlePermissionsError(response.status); + throw new ServerError("Failed to fetch user info"); + } + }, + { retry: handleRetry } + ); + + handleError(queryResult); + return queryResult; +}; From 645809b0bde11b3dbfd2c1d8077c8ba9242928b0 Mon Sep 17 00:00:00 2001 From: Jacob Taegon Kim Date: Mon, 6 Nov 2023 20:08:23 -0800 Subject: [PATCH 12/18] changed patch to put --- csm_web/frontend/src/utils/queries/profiles.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csm_web/frontend/src/utils/queries/profiles.tsx b/csm_web/frontend/src/utils/queries/profiles.tsx index 726157b7..4eb2eef3 100644 --- a/csm_web/frontend/src/utils/queries/profiles.tsx +++ b/csm_web/frontend/src/utils/queries/profiles.tsx @@ -52,7 +52,7 @@ export const useUserInfoUpdateMutation = (userId: number): UseMutationResult Date: Fri, 24 Nov 2023 20:03:05 -0500 Subject: [PATCH 13/18] some changes to form some changes to form --- csm_web/frontend/src/components/UserProfile.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/csm_web/frontend/src/components/UserProfile.tsx b/csm_web/frontend/src/components/UserProfile.tsx index 52a26f24..88b0a90a 100644 --- a/csm_web/frontend/src/components/UserProfile.tsx +++ b/csm_web/frontend/src/components/UserProfile.tsx @@ -62,7 +62,7 @@ const DisplayUser = ({ userInfo }: UserInfoProps) => { /** * User Bio */ - const [userBio, setUserBio] = useState(""); + const [bio, setBio] = useState(""); const handleEditing = () => { setEditing(true); @@ -82,7 +82,7 @@ const DisplayUser = ({ userInfo }: UserInfoProps) => { userFirstName, userLastName, userEmail, - userBio, + bio, userPronoun }; @@ -111,7 +111,7 @@ const DisplayUser = ({ userInfo }: UserInfoProps) => { setUserPronoun(value); break; case "bio": - setUserBio(value); + setBio(value); break; default: console.error("Unknown input name: " + name); @@ -160,7 +160,7 @@ const DisplayUser = ({ userInfo }: UserInfoProps) => { id="email" defaultValue={userInfo.email} className="formbold-form-input" - disabled={!editing} + disabled={true} onChange={e => handleChange("email", e.target.value)} /> @@ -172,6 +172,7 @@ const DisplayUser = ({ userInfo }: UserInfoProps) => { id="pronouns" placeholder="" className="formbold-form-input" + defaultValue={userInfo.pronouns} disabled={!editing} onChange={e => handleChange("pronouns", e.target.value)} /> @@ -186,6 +187,7 @@ const DisplayUser = ({ userInfo }: UserInfoProps) => { placeholder="Write your bio..." className="formbold-form-input" disabled={!editing} + defaultValue={userInfo.bio} onChange={e => handleChange("bio", e.target.value)} > From f85ac8751edd9c61ca7708a8b1d0cfe6596c1620 Mon Sep 17 00:00:00 2001 From: kevinbu233 Date: Mon, 27 Nov 2023 21:10:40 -0800 Subject: [PATCH 14/18] refractor frontend and add pronunciation --- .../frontend/src/components/UserProfile.tsx | 88 +++++++++++++------ .../src/components/section/ProfileModal.tsx | 10 +++ .../frontend/src/utils/queries/profiles.tsx | 8 +- csm_web/frontend/src/utils/types.tsx | 1 + 4 files changed, 79 insertions(+), 28 deletions(-) diff --git a/csm_web/frontend/src/components/UserProfile.tsx b/csm_web/frontend/src/components/UserProfile.tsx index 88b0a90a..2c108382 100644 --- a/csm_web/frontend/src/components/UserProfile.tsx +++ b/csm_web/frontend/src/components/UserProfile.tsx @@ -1,45 +1,54 @@ import React, { useState } from "react"; import { useUserInfo } from "../utils/queries/base"; -import { UserInfo } from "../utils/types"; +import { RawUserInfo, UserInfo } from "../utils/types"; import { useUserInfoUpdateMutation } from "../utils/queries/profiles"; +import { DateTime } from "luxon"; import "../css/profile.scss"; export const UserProfile = (): React.ReactElement => { const { data: jsonUserInfo, isSuccess: userInfoLoaded } = useUserInfo(); - let userInfo: UserInfo | null; - if (userInfoLoaded) { - let priorityEnrollment = undefined; - if (jsonUserInfo.priorityEnrollment) { - priorityEnrollment = new Date(Date.parse(jsonUserInfo.priorityEnrollment)); - } - const convertedUserInfo: UserInfo = { - ...jsonUserInfo, - priorityEnrollment - }; - userInfo = convertedUserInfo; - } else { - // not done loading yet - userInfo = null; - } + // let userInfo: UserInfo; + // if (userInfoLoaded) { + // let priorityEnrollment = undefined; + // if (jsonUserInfo.priorityEnrollment) { + // priorityEnrollment = DateTime.fromISO(jsonUserInfo.priorityEnrollment); + // } + // const convertedUserInfo: UserInfo = { + // ...jsonUserInfo, + // priorityEnrollment + // }; + // userInfo = convertedUserInfo; + // } + // else { + // // not done loading yet + // userInfo = null; + // } return ( -
{userInfoLoaded ? : <>}
+
+ {userInfoLoaded ? ( + + ) : ( + <> + )} +
); }; interface UserInfoProps { - userInfo: UserInfo | null; + userInfo: RawUserInfo; + priorityEnrollment?: string; } -const DisplayUser = ({ userInfo }: UserInfoProps) => { +const DisplayUser = ({ userInfo, priorityEnrollment }: UserInfoProps) => { /** * Mutation to create a new section. */ - const createSectionMutation = useUserInfoUpdateMutation(userInfo.id); + const createSectionMutation = useUserInfoUpdateMutation(userInfo?.id); const [editing, setEditing] = useState(false); /** @@ -63,6 +72,17 @@ const DisplayUser = ({ userInfo }: UserInfoProps) => { * User Bio */ const [bio, setBio] = useState(""); + /** + * Pronunciation + */ + const [pronunciation, setPronunciation] = useState(""); + /** + * Pronunciation + */ + let priority: DateTime | undefined; + if (priorityEnrollment) { + priority = DateTime.fromISO(priorityEnrollment); + } const handleEditing = () => { setEditing(true); @@ -79,13 +99,17 @@ const DisplayUser = ({ userInfo }: UserInfoProps) => { const handleSave = (event: React.MouseEvent): void => { event.preventDefault(); const data = { - userFirstName, - userLastName, - userEmail, + id: userInfo.id, + firstName: userFirstName, + lastName: userLastName, + email: userEmail, + isPrivate: userInfo.isPrivate, bio, - userPronoun + priorityEnrollment: priority, + pronouns: userPronoun, + pronunciation }; - + console.log(data); createSectionMutation.mutate(data, { onSuccess: () => { setEditing(false); @@ -113,6 +137,9 @@ const DisplayUser = ({ userInfo }: UserInfoProps) => { case "bio": setBio(value); break; + case "pronunciation": + setPronunciation(value); + break; default: console.error("Unknown input name: " + name); break; @@ -179,7 +206,16 @@ const DisplayUser = ({ userInfo }: UserInfoProps) => { - +
+ + +
+ +
+ +
{ defaultValue={userInfo.email} className="formbold-form-input" disabled={true} - onChange={e => handleChange("email", e.target.value)} />
@@ -201,6 +189,7 @@ const DisplayUser = ({ userInfo, priorityEnrollment }: UserInfoProps) => { className="formbold-form-input" defaultValue={userInfo.pronouns} disabled={!editing} + maxLength={20} onChange={e => handleChange("pronouns", e.target.value)} /> @@ -208,25 +197,16 @@ const DisplayUser = ({ userInfo, priorityEnrollment }: UserInfoProps) => {
- -
-
- - +
diff --git a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx index 1a4ff0a4..5451088e 100644 --- a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx +++ b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx @@ -6,23 +6,14 @@ import LoadingSpinner from "../LoadingSpinner"; import { CoordinatorAddStudentModal } from "./CoordinatorAddStudentModal"; import MetaEditModal from "./MetaEditModal"; import { InfoCard, SectionSpacetime } from "./Section"; -<<<<<<< HEAD -======= -import SpacetimeEditModal from "./SpacetimeEditModal"; -import ProfileModal from "./ProfileModal"; -import StudentDropper from "./StudentDropper"; ->>>>>>> f904047 (modal for profile) import SpacetimeDeleteModal from "./SpacetimeDeleteModal"; import SpacetimeEditModal from "./SpacetimeEditModal"; -import ProfileModal from "./ProfileModal"; +// import ProfileModal from "./ProfileModal"; import StudentDropper from "./StudentDropper"; import PencilIcon from "../../../static/frontend/img/pencil.svg"; -<<<<<<< HEAD +// import EyeIcon from "../../../static/frontend/img/eye.svg"; import XIcon from "../../../static/frontend/img/x.svg"; -======= -import EyeIcon from "../../../static/frontend/img/eye.svg"; ->>>>>>> f904047 (modal for profile) import "../../css/coordinator-add-student.scss"; @@ -101,7 +92,7 @@ export default function MentorSectionInfo({ /> )} {name || email} - {showModal === ModalStates.SPACETIME_EDIT && ( - )} + )} */} ) diff --git a/csm_web/frontend/src/components/section/ProfileModal.tsx b/csm_web/frontend/src/components/section/ProfileModal.tsx index 5173cfdc..21f5c3c4 100644 --- a/csm_web/frontend/src/components/section/ProfileModal.tsx +++ b/csm_web/frontend/src/components/section/ProfileModal.tsx @@ -1,126 +1,126 @@ -import React, { useState } from "react"; -import LoadingSpinner from "../LoadingSpinner"; -import Modal from "../Modal"; -import { useUserInfo } from "../../utils/queries/base"; -import { UseUserInfoWithId } from "../../utils/queries/profiles"; -import { UserInfo } from "../../utils/types"; -import { useSectionStudents } from "../../utils/queries/sections"; +// import React, { useState } from "react"; +// import LoadingSpinner from "../LoadingSpinner"; +// import Modal from "../Modal"; +// import { useUserInfo } from "../../utils/queries/base"; +// import { UseUserInfoWithId } from "../../utils/queries/profiles"; +// import { UserInfo } from "../../utils/types"; +// import { useSectionStudents } from "../../utils/queries/sections"; -interface ProfileModalProps { - id: number; - closeModal: () => void; -} +// interface ProfileModalProps { +// id: number; +// closeModal: () => void; +// } -const ProfileModal = ({ id, closeModal }: ProfileModalProps): React.ReactElement => { - // const { data: jsonUserInfo, isSuccess: userInfoLoaded } = UseUserInfoWithId(id); - const { data: jsonUserInfo, isSuccess: userInfoLoaded } = useSectionStudents(id); - console.log(id); - console.log(); - console.log(jsonUserInfo); +// const ProfileModal = ({ id, closeModal }: ProfileModalProps): React.ReactElement => { +// // const { data: jsonUserInfo, isSuccess: userInfoLoaded } = UseUserInfoWithId(id); +// const { data: jsonUserInfo, isSuccess: userInfoLoaded } = useSectionStudents(id); +// console.log(id); +// console.log(); +// console.log(jsonUserInfo); - let userInfo: UserInfo | null; - if (userInfoLoaded) { - let priorityEnrollment = undefined; - if (jsonUserInfo.priorityEnrollment) { - priorityEnrollment = new Date(Date.parse(jsonUserInfo.priorityEnrollment)); - } - const convertedUserInfo: UserInfo = { - ...jsonUserInfo, - priorityEnrollment - }; - userInfo = convertedUserInfo; - } else { - // not done loading yet - userInfo = null; - } +// let userInfo: UserInfo | null; +// if (userInfoLoaded) { +// let priorityEnrollment = undefined; +// if (jsonUserInfo.priorityEnrollment) { +// priorityEnrollment = new Date(Date.parse(jsonUserInfo.priorityEnrollment)); +// } +// const convertedUserInfo: UserInfo = { +// ...jsonUserInfo, +// priorityEnrollment +// }; +// userInfo = convertedUserInfo; +// } else { +// // not done loading yet +// userInfo = null; +// } - return ( - -
- {userInfo !== null ? ( -
-
-
-
-
- - -
-
- - -
-
+// return ( +// +//
+// {userInfo !== null ? ( +//
+//
+// +//
+//
+// +// +//
+//
+// +// +//
+//
-
-
- - -
-
- - -
-
+//
+//
+// +// +//
+//
+// +// +//
+//
-
- - -
-
- - -
- -
-
-
- ) : ( - "" - )} -
-
- ); -}; +//
+// +// +//
+//
+// +// +//
+// +//
+//
+//
+// ) : ( +// "" +// )} +//
+//
+// ); +// }; -export default ProfileModal; +// export default ProfileModal; diff --git a/csm_web/frontend/src/css/profile.scss b/csm_web/frontend/src/css/profile.scss index 409003ac..eae077e0 100644 --- a/csm_web/frontend/src/css/profile.scss +++ b/csm_web/frontend/src/css/profile.scss @@ -1,16 +1,24 @@ @use "base/variables" as *; +@use "base/button"; + +$default-font: "Montserrat", sans-serif; + +textarea { + height: "auto"; +} .formbold-main-wrapper { display: flex; align-items: center; justify-content: center; padding: 48px; + font-family: $default-font; } .formbold-form-wrapper { - margin: 0 auto; - max-width: 550px; width: 100%; + max-width: 550px; + margin: 0 auto; background: white; } @@ -19,11 +27,13 @@ gap: 20px; margin-bottom: 22px; } + .formbold-input-flex > div { - width: 50%; display: flex; flex-direction: column-reverse; + width: 50%; } + .formbold-textarea { display: flex; flex-direction: column-reverse; @@ -32,53 +42,76 @@ .formbold-form-input { width: 100%; padding-bottom: 10px; + font-family: $default-font; + font-size: 16px; + font-weight: 500; + color: #07074d; + resize: none; + background: #fff; border: none; border-bottom: 1px solid #dde3ec; - background: #ffffff; - font-weight: 500; + outline: none; +} + +.formbold-form-input-bio { + width: 100%; + height: auto; + min-height: 200px; + padding-bottom: 10px; + overflow: "hidden"; + font-family: $default-font; font-size: 16px; + font-weight: 500; color: #07074d; - outline: none; resize: none; + background: #fff; + border: none; + border-bottom: 1px solid #dde3ec; + outline: none; } + .formbold-form-input::placeholder { color: #536387; } + .formbold-form-input:focus { border-color: #6a64f1; } + .formbold-form-label { - color: #07074d; - font-weight: 500; - font-size: 14px; - line-height: 24px; display: block; margin-bottom: 18px; + font-size: 14px; + font-weight: 500; + line-height: 24px; + color: #07074d; } + .formbold-form-input:focus + .formbold-form-label { color: #6a64f1; } .button-wrapper { display: flex; + flex-direction: row-reverse; align-items: center; - margin: 10px 10px; - padding: 5px 5px; justify-content: space-evenly; - flex-direction: row-reverse; + padding: 5px; + margin: 10px; } .formbold-btn { - font-size: 16px; - border-radius: 5px; padding: 12px 25px; - border: none; + margin-top: 25px; + font-size: 16px; font-weight: 500; - background-color: var(--csm-green); color: black; cursor: pointer; - margin-top: 25px; + background-color: var(--csm-green); + border: none; + border-radius: 5px; } + .formbold-btn:hover { - box-shadow: 0px 3px 8px rgba(0, 0, 0, 0.05); + box-shadow: 0 3px 8px rgb(0 0 0 / 5%); } diff --git a/csm_web/frontend/src/utils/queries/profiles.tsx b/csm_web/frontend/src/utils/queries/profiles.tsx index 1f594442..715ca09a 100644 --- a/csm_web/frontend/src/utils/queries/profiles.tsx +++ b/csm_web/frontend/src/utils/queries/profiles.tsx @@ -3,11 +3,11 @@ */ import { useMutation, UseMutationResult, useQuery, useQueryClient, UseQueryResult } from "@tanstack/react-query"; -import { fetchNormalized, fetchWithMethod, HTTP_METHODS } from "../api"; -import { handleError, handlePermissionsError, handleRetry, PermissionError, ServerError } from "./helpers"; +import { isNull } from "lodash"; import { DateTime } from "luxon"; +import { fetchNormalized, fetchWithMethod, HTTP_METHODS } from "../api"; import { RawUserInfo } from "../types"; -import { isNull } from "lodash"; +import { handleError, handlePermissionsError, handleRetry, PermissionError, ServerError } from "./helpers"; /* ===== Mutation ===== */ /** @@ -20,9 +20,9 @@ export interface UpdateUserInfo { email: string; priorityEnrollment?: DateTime; isPrivate: boolean; - bio: string; - pronouns: string; - pronunciation: string; + bio?: string; + pronouns?: string; + pronunciation?: string; } /** diff --git a/csm_web/frontend/src/utils/types.tsx b/csm_web/frontend/src/utils/types.tsx index e9d91466..e6d1f431 100644 --- a/csm_web/frontend/src/utils/types.tsx +++ b/csm_web/frontend/src/utils/types.tsx @@ -39,7 +39,7 @@ export interface UserInfo { isPrivate: boolean; bio: string; pronouns: string; - pronunciation: string; + preferredName: string; } /** diff --git a/csm_web/scheduler/migrations/0033_user_bio_user_is_private_user_pronouns_and_more.py b/csm_web/scheduler/migrations/0033_user_bio_user_is_private_user_pronouns_and_more.py index e565fb5b..fa919a25 100644 --- a/csm_web/scheduler/migrations/0033_user_bio_user_is_private_user_pronouns_and_more.py +++ b/csm_web/scheduler/migrations/0033_user_bio_user_is_private_user_pronouns_and_more.py @@ -12,9 +12,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name="user", name="bio", - field=models.CharField( - default="Default bio given to everyone~", max_length=300 - ), + field=models.CharField(default="", max_length=500), ), migrations.AddField( model_name="user", diff --git a/csm_web/scheduler/models.py b/csm_web/scheduler/models.py index c4890dcb..8aa0f50d 100644 --- a/csm_web/scheduler/models.py +++ b/csm_web/scheduler/models.py @@ -61,7 +61,7 @@ class Pronouns(models.TextChoices): is_private = models.BooleanField(default=False) pronunciation = models.CharField(max_length=20, default="") - bio = models.CharField(max_length=300, default="Default bio given to everyone~") + bio = models.CharField(max_length=500, default="") def can_enroll_in_course(self, course, bypass_enrollment_time=False): """Determine whether this user is allowed to enroll in the given course.""" diff --git a/package-lock.json b/package-lock.json index 6ce9e501..985f4398 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,7 +54,7 @@ "eslint-config-prettier": "^8.10.0", "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-cypress": "^2.12.1", - "eslint-plugin-import": "^2.28.1", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-react": "^7.31.10", "eslint-plugin-react-hooks": "^4.6.0", "jest": "^29.6.2", @@ -88,9 +88,9 @@ } }, "node_modules/@adobe/css-tools": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.0.1.tgz", - "integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.3.tgz", + "integrity": "sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==", "dev": true }, "node_modules/@ampproject/remapping": { @@ -107,12 +107,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" @@ -185,13 +186,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", - "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dev": true, "dependencies": { - "@babel/types": "^7.20.5", + "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "engines": { @@ -310,9 +312,9 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" @@ -331,25 +333,25 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -478,30 +480,30 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -546,13 +548,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -560,9 +562,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz", - "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -1875,34 +1877,34 @@ } }, "node_modules/@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", - "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.5", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.5", - "@babel/types": "^7.20.5", - "debug": "^4.1.0", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -1910,13 +1912,13 @@ } }, "node_modules/@babel/types": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", - "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -4068,9 +4070,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -4194,9 +4196,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -4235,9 +4237,9 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -5932,7 +5934,7 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, "node_modules/colord": { @@ -7500,9 +7502,9 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", - "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "dependencies": { "array-includes": "^3.1.7", @@ -7521,7 +7523,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -8718,7 +8720,7 @@ "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "engines": { "node": ">=4" @@ -12277,9 +12279,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, "funding": [ { @@ -12770,9 +12772,9 @@ } }, "node_modules/postcss": { - "version": "8.4.29", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz", - "integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==", + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", "dev": true, "funding": [ { @@ -12789,7 +12791,7 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -15678,9 +15680,9 @@ } }, "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "dependencies": { "@types/json5": "^0.0.29", @@ -16352,9 +16354,9 @@ "dev": true }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -16577,9 +16579,9 @@ "dev": true }, "@adobe/css-tools": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.0.1.tgz", - "integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.3.tgz", + "integrity": "sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==", "dev": true }, "@ampproject/remapping": { @@ -16593,12 +16595,13 @@ } }, "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" } }, "@babel/compat-data": { @@ -16650,13 +16653,14 @@ } }, "@babel/generator": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", - "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dev": true, "requires": { - "@babel/types": "^7.20.5", + "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "dependencies": { @@ -16744,9 +16748,9 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, "@babel/helper-explode-assignable-expression": { @@ -16759,22 +16763,22 @@ } }, "@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-member-expression-to-functions": { @@ -16870,24 +16874,24 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/helper-validator-option": { @@ -16920,20 +16924,20 @@ } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz", - "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", "dev": true }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { @@ -17805,42 +17809,42 @@ } }, "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", "dev": true, "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" } }, "@babel/traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", - "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.5", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.5", - "@babel/types": "^7.20.5", - "debug": "^4.1.0", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", + "debug": "^4.3.1", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", - "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -19443,9 +19447,9 @@ }, "dependencies": { "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -19509,9 +19513,9 @@ }, "dependencies": { "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -19536,9 +19540,9 @@ }, "dependencies": { "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -20864,7 +20868,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, "colord": { @@ -22191,9 +22195,9 @@ } }, "eslint-plugin-import": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", - "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "requires": { "array-includes": "^3.1.7", @@ -22212,7 +22216,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "dependencies": { "debug": { @@ -22930,7 +22934,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true }, "has-property-descriptors": { @@ -25519,9 +25523,9 @@ "dev": true }, "nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true }, "natural-compare": { @@ -25879,12 +25883,12 @@ } }, "postcss": { - "version": "8.4.29", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz", - "integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==", + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", "dev": true, "requires": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } @@ -27889,9 +27893,9 @@ } }, "tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "requires": { "@types/json5": "^0.0.29", @@ -28373,9 +28377,9 @@ "dev": true }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true }, "wrap-ansi": { diff --git a/package.json b/package.json index 0312eab6..49f5a41c 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "eslint-config-prettier": "^8.10.0", "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-cypress": "^2.12.1", - "eslint-plugin-import": "^2.28.1", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-react": "^7.31.10", "eslint-plugin-react-hooks": "^4.6.0", "jest": "^29.6.2", From 18881bc7389129f4342355be5986ab9bbac71c83 Mon Sep 17 00:00:00 2001 From: Jacob Taegon Kim Date: Sun, 28 Jan 2024 01:09:53 -0800 Subject: [PATCH 16/18] change preferredName --- csm_web/frontend/src/components/UserProfile.tsx | 6 +++--- csm_web/frontend/src/utils/types.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/csm_web/frontend/src/components/UserProfile.tsx b/csm_web/frontend/src/components/UserProfile.tsx index bc65f4e8..0b81d09c 100644 --- a/csm_web/frontend/src/components/UserProfile.tsx +++ b/csm_web/frontend/src/components/UserProfile.tsx @@ -59,7 +59,7 @@ const DisplayUser = ({ userInfo, priorityEnrollment }: UserInfoProps) => { /** * Pronunciation */ - const [userPreferredName, setUserPreferredName] = useState(userInfo.preferredName); + const [userPreferredName, setUserPreferredName] = useState(userInfo.pronunciation); const CHARACTER_LIMIT = 500; @@ -91,7 +91,7 @@ const DisplayUser = ({ userInfo, priorityEnrollment }: UserInfoProps) => { bio: userBio, priorityEnrollment: priority, pronouns: userPronoun, - preferredName: userPreferredName + pronunciation: userPreferredName }; console.log(data); createSectionMutation.mutate(data, { @@ -202,7 +202,7 @@ const DisplayUser = ({ userInfo, priorityEnrollment }: UserInfoProps) => { placeholder="" className="formbold-form-input" disabled={!editing} - defaultValue={userInfo.preferredName} + defaultValue={userInfo.pronunciation} maxLength={50} onChange={e => handleChange("preferredName", e.target.value)} > diff --git a/csm_web/frontend/src/utils/types.tsx b/csm_web/frontend/src/utils/types.tsx index e6d1f431..e9d91466 100644 --- a/csm_web/frontend/src/utils/types.tsx +++ b/csm_web/frontend/src/utils/types.tsx @@ -39,7 +39,7 @@ export interface UserInfo { isPrivate: boolean; bio: string; pronouns: string; - preferredName: string; + pronunciation: string; } /** From 467df89aa2e91a6a527bc3af09036f5fa1d306d9 Mon Sep 17 00:00:00 2001 From: Veriny <2002.ijustin@gmail.com> Date: Wed, 21 Feb 2024 21:31:19 -0800 Subject: [PATCH 17/18] added endpoint for converting mentor, student, and coordinator IDs to user IDs --- csm_web/scheduler/urls.py | 1 + csm_web/scheduler/views/user.py | 27 ++++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/csm_web/scheduler/urls.py b/csm_web/scheduler/urls.py index 4ab2c5a4..a4a1036c 100644 --- a/csm_web/scheduler/urls.py +++ b/csm_web/scheduler/urls.py @@ -24,4 +24,5 @@ path("matcher//configure/", views.matcher.configure), path("matcher//create/", views.matcher.create), path("user//profile/", views.user.profile), + path("user//fetch_user_id/", views.user.fetch_user_pk), ] diff --git a/csm_web/scheduler/views/user.py b/csm_web/scheduler/views/user.py index d9f71d86..0ccdba7c 100644 --- a/csm_web/scheduler/views/user.py +++ b/csm_web/scheduler/views/user.py @@ -4,7 +4,7 @@ from rest_framework.response import Response from scheduler.serializers import UserSerializer -from ..models import Coordinator, User +from ..models import Coordinator, Mentor, Student, User from .utils import viewset_with @@ -76,3 +76,28 @@ def profile(request, pk=None): user.save() serializer = UserSerializer(user) return Response(serializer.data, status=status.HTTP_200_OK) + + +@api_view(["GET"]) +def fetch_user_pk(request, pk=None): + """ + Function for getting user IDs from profile IDs + GET: Gets the user serializer for a user given a mentor ID, student ID, or coordinator ID. + Request: {'type': string} + Response: status code, as well as user serializer + """ + profile_type = request.data.get("type") + queryset = None + if profile_type == "student": + queryset = Student.objects.all() + elif profile_type == "mentor": + queryset = Mentor.objects.all() + elif profile_type == "coordinator": + queryset = Coordinator.objects.all() + else: + return Response(status=status.HTTP_400_BAD_REQUEST) + + person = queryset.get(pk=pk) + + serializer = UserSerializer(person.user.id) + return Response(serializer.data, status=status.HTTP_200_OK) From 35069f4a646b4543a8c6f06c76584ddc1863b77c Mon Sep 17 00:00:00 2001 From: Jacob Taegon Kim Date: Sun, 7 Jul 2024 19:02:18 -0700 Subject: [PATCH 18/18] [WIP] Profile Added some comments about testing endpoint and modal for profile, edit button, etc. --- .../components/section/MentorSectionInfo.tsx | 11 +- .../src/components/section/ProfileModal.tsx | 237 +++++++++--------- .../src/components/section/StudentSection.tsx | 107 ++++++-- csm_web/frontend/src/css/base/autogrid.scss | 5 + csm_web/frontend/src/css/profile 2.scss | 117 +++++++++ .../frontend/src/utils/queries/profiles.tsx | 29 ++- csm_web/scheduler/views/user.py | 10 +- 7 files changed, 367 insertions(+), 149 deletions(-) create mode 100644 csm_web/frontend/src/css/base/autogrid.scss create mode 100644 csm_web/frontend/src/css/profile 2.scss diff --git a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx index 5451088e..0cb6a073 100644 --- a/csm_web/frontend/src/components/section/MentorSectionInfo.tsx +++ b/csm_web/frontend/src/components/section/MentorSectionInfo.tsx @@ -1,18 +1,19 @@ -import React, { useState } from "react"; +import * as React from "react"; +import { useState } from "react"; import { useSectionStudents } from "../../utils/queries/sections"; import { Mentor, Spacetime, Student } from "../../utils/types"; import LoadingSpinner from "../LoadingSpinner"; import { CoordinatorAddStudentModal } from "./CoordinatorAddStudentModal"; import MetaEditModal from "./MetaEditModal"; +import ProfileModal from "./ProfileModal"; import { InfoCard, SectionSpacetime } from "./Section"; import SpacetimeDeleteModal from "./SpacetimeDeleteModal"; import SpacetimeEditModal from "./SpacetimeEditModal"; -// import ProfileModal from "./ProfileModal"; import StudentDropper from "./StudentDropper"; +import EyeIcon from "../../../static/frontend/img/eye.svg"; import PencilIcon from "../../../static/frontend/img/pencil.svg"; -// import EyeIcon from "../../../static/frontend/img/eye.svg"; import XIcon from "../../../static/frontend/img/x.svg"; import "../../css/coordinator-add-student.scss"; @@ -92,7 +93,7 @@ export default function MentorSectionInfo({ /> )} {name || email} - {/* {showModal === ModalStates.SPACETIME_EDIT && ( - )} */} + )} ) diff --git a/csm_web/frontend/src/components/section/ProfileModal.tsx b/csm_web/frontend/src/components/section/ProfileModal.tsx index 21f5c3c4..2fec53f5 100644 --- a/csm_web/frontend/src/components/section/ProfileModal.tsx +++ b/csm_web/frontend/src/components/section/ProfileModal.tsx @@ -1,126 +1,125 @@ -// import React, { useState } from "react"; -// import LoadingSpinner from "../LoadingSpinner"; -// import Modal from "../Modal"; -// import { useUserInfo } from "../../utils/queries/base"; -// import { UseUserInfoWithId } from "../../utils/queries/profiles"; -// import { UserInfo } from "../../utils/types"; -// import { useSectionStudents } from "../../utils/queries/sections"; +import * as React from "react"; +import { useUserInfo } from "../../utils/queries/base"; +import { UseUserInfoWithId } from "../../utils/queries/profiles"; +import { useSectionStudents } from "../../utils/queries/sections"; +import { UserInfo } from "../../utils/types"; +import LoadingSpinner from "../LoadingSpinner"; +import Modal from "../Modal"; -// interface ProfileModalProps { -// id: number; -// closeModal: () => void; -// } +interface ProfileModalProps { + id: number; + closeModal: () => void; +} -// const ProfileModal = ({ id, closeModal }: ProfileModalProps): React.ReactElement => { -// // const { data: jsonUserInfo, isSuccess: userInfoLoaded } = UseUserInfoWithId(id); -// const { data: jsonUserInfo, isSuccess: userInfoLoaded } = useSectionStudents(id); -// console.log(id); -// console.log(); -// console.log(jsonUserInfo); +const ProfileModal = ({ id, closeModal }: ProfileModalProps): React.ReactElement => { + const { data: jsonUserInfo, isSuccess: userInfoLoaded } = useSectionStudents(id); + // console.log(id); + // console.log(); + // console.log(jsonUserInfo); -// let userInfo: UserInfo | null; -// if (userInfoLoaded) { -// let priorityEnrollment = undefined; -// if (jsonUserInfo.priorityEnrollment) { -// priorityEnrollment = new Date(Date.parse(jsonUserInfo.priorityEnrollment)); -// } -// const convertedUserInfo: UserInfo = { -// ...jsonUserInfo, -// priorityEnrollment -// }; -// userInfo = convertedUserInfo; -// } else { -// // not done loading yet -// userInfo = null; -// } + let userInfo: UserInfo | null; + if (userInfoLoaded) { + let priorityEnrollment = undefined; + if (jsonUserInfo.priorityEnrollment) { + priorityEnrollment = new Date(Date.parse(jsonUserInfo.priorityEnrollment)); + } + const convertedUserInfo: UserInfo = { + ...jsonUserInfo, + priorityEnrollment + }; + userInfo = convertedUserInfo; + } else { + // not done loading yet + userInfo = null; + } -// return ( -// -//
-// {userInfo !== null ? ( -//
-//
-//
-//
-//
-// -// -//
-//
-// -// -//
-//
+ return ( + +
+ {userInfo !== null ? ( +
+
+ +
+
+ + +
+
+ + +
+
-//
-//
-// -// -//
-//
-// -// -//
-//
+
+
+ + +
+
+ + +
+
-//
-// -// -//
-//
-// -// -//
-// -//
-//
-//
-// ) : ( -// "" -// )} -//
-//
-// ); -// }; +
+ + +
+
+ + +
+ +
+
+
+ ) : ( + "" + )} +
+
+ ); +}; -// export default ProfileModal; +export default ProfileModal; diff --git a/csm_web/frontend/src/components/section/StudentSection.tsx b/csm_web/frontend/src/components/section/StudentSection.tsx index f18d9aa8..bd74b05c 100644 --- a/csm_web/frontend/src/components/section/StudentSection.tsx +++ b/csm_web/frontend/src/components/section/StudentSection.tsx @@ -1,8 +1,11 @@ import { DateTime } from "luxon"; -import React, { useEffect, useState } from "react"; -import { Navigate, Route, Routes } from "react-router-dom"; +import * as React from "react"; +import { useEffect, useState } from "react"; +import { Navigate, Route, Routes, useParams } from "react-router-dom"; +import { fetchNormalized, fetchWithMethod, HTTP_METHODS } from "../../utils/api"; import { DEFAULT_TIMEZONE } from "../../utils/datetime"; +import { useMentorProfile } from "../../utils/queries/profiles"; import { useDropUserMutation, useStudentAttendances, @@ -40,6 +43,31 @@ export default function StudentSection({ override, associatedProfileId }: StudentSectionType) { + const { sectionId } = useParams(); + // console.log(id); + + // const mentorID = mentor.id; + // let mentorProfile; + // // console.log(mentor) + + // const fetchTemp = async () => { + // await fetchWithMethod(`/user/${mentorID}/fetch_user_id`, HTTP_METHODS.POST, {'type': "mentor"}).then(response => + // response.json().then(data => mentorProfile = data)) + + // // const response = await fetchWithMethod(`/user/${mentorID}/fetch_user_id`, HTTP_METHODS.POST, {'type': "mentor"}); + // // if (response.ok) { + // // console.log(await response.json()) + // // } else { + // // return + // // } + // } + + // console.log(useMentorProfile(mentorID)); + + // fetchTemp(); + + // console.log(mentor); + return (
{mentor.name}
{/* {mentor.email} */} - + )} @@ -171,14 +199,49 @@ enum ProfileSectionStage { function ProfileSection({ profileId }: ProfileSectionProps) { // const studentDropMutation = useDropUserMutation(profileId); const [stage, setStage] = useState(ProfileSectionStage.CLOSED); + const [mentorProfile, setMentorProfile] = useState(); + + useEffect(() => { + fetchData(); + }, []); + + const fetchData = async () => { + const result = fetchWithMethod(`/user/${profileId}/fetch_user_id`, HTTP_METHODS.POST, { type: "mentor" }).then( + response => + response.json().then(data => { + setMentorProfile(data); + console.log(data); + return data; + }) + ); + return result; + }; + + // current user's id from Students + + // console.log(profileId); + + // const { data: jsonUserInfo, isSuccess: userInfoLoaded } = useMentorProfile(profileId); + + // const response = async () => await fetchNormalized(`/user/${profileId}/profile`).then((data) => data.json()); - // const performDrop = () => { - // studentDropMutation.mutate(undefined, { - // onSuccess: () => { - // setStage(ProfileSectionStage.CLOSED); - // } - // }); - // }; + // const mentorID = mentor.id; + // console.log(mentor) + + const fetchTemp = async () => { + // await fetchWithMethod(`/user/${profileId}/fetch_user_id`, HTTP_METHODS.POST, {'type': "mentor"}).then(response => + // response.json().then(data => setMentorProfile(data))) + // const response = await fetchWithMethod(`/user/${mentorID}/fetch_user_id`, HTTP_METHODS.POST, {'type': "mentor"}); + // if (response.ok) { + // console.log(await response.json()) + // } else { + // return + // } + }; + + // fetchTemp(); + + // console.log(mentorProfile); switch (stage) { case ProfileSectionStage.CLOSED: @@ -191,8 +254,17 @@ function ProfileSection({ profileId }: ProfileSectionProps) { return ( setStage(ProfileSectionStage.CLOSED)}>
-
Are you sure you want to drop?
-

You are not guaranteed an available spot in another section!

+ {mentorProfile ? ( + <> +
{mentorProfile.firstName}
+
{mentorProfile.lastName}
+
{mentorProfile.bio}
+
{mentorProfile.pronouns}
+
{mentorProfile.email}
+ + ) : ( + "" + )}
); @@ -415,19 +487,10 @@ function StudentList({ associatedProfileId, id }: StudentListProps) { isSuccess: listLoaded, isError: listLoadError, refetch: refetchStudentList - } = useSectionStudents(associatedProfileId); + } = useSectionStudents(id); return listLoaded ? ( -
-

Submit Word of the Day

-
-
-
-
-
-
-
diff --git a/csm_web/frontend/src/css/base/autogrid.scss b/csm_web/frontend/src/css/base/autogrid.scss new file mode 100644 index 00000000..910ccd6e --- /dev/null +++ b/csm_web/frontend/src/css/base/autogrid.scss @@ -0,0 +1,5 @@ +@use "variables" as *; + +.auto-grid-item { + padding: 0 8px; +} diff --git a/csm_web/frontend/src/css/profile 2.scss b/csm_web/frontend/src/css/profile 2.scss new file mode 100644 index 00000000..eae077e0 --- /dev/null +++ b/csm_web/frontend/src/css/profile 2.scss @@ -0,0 +1,117 @@ +@use "base/variables" as *; +@use "base/button"; + +$default-font: "Montserrat", sans-serif; + +textarea { + height: "auto"; +} + +.formbold-main-wrapper { + display: flex; + align-items: center; + justify-content: center; + padding: 48px; + font-family: $default-font; +} + +.formbold-form-wrapper { + width: 100%; + max-width: 550px; + margin: 0 auto; + background: white; +} + +.formbold-input-flex { + display: flex; + gap: 20px; + margin-bottom: 22px; +} + +.formbold-input-flex > div { + display: flex; + flex-direction: column-reverse; + width: 50%; +} + +.formbold-textarea { + display: flex; + flex-direction: column-reverse; +} + +.formbold-form-input { + width: 100%; + padding-bottom: 10px; + font-family: $default-font; + font-size: 16px; + font-weight: 500; + color: #07074d; + resize: none; + background: #fff; + border: none; + border-bottom: 1px solid #dde3ec; + outline: none; +} + +.formbold-form-input-bio { + width: 100%; + height: auto; + min-height: 200px; + padding-bottom: 10px; + overflow: "hidden"; + font-family: $default-font; + font-size: 16px; + font-weight: 500; + color: #07074d; + resize: none; + background: #fff; + border: none; + border-bottom: 1px solid #dde3ec; + outline: none; +} + +.formbold-form-input::placeholder { + color: #536387; +} + +.formbold-form-input:focus { + border-color: #6a64f1; +} + +.formbold-form-label { + display: block; + margin-bottom: 18px; + font-size: 14px; + font-weight: 500; + line-height: 24px; + color: #07074d; +} + +.formbold-form-input:focus + .formbold-form-label { + color: #6a64f1; +} + +.button-wrapper { + display: flex; + flex-direction: row-reverse; + align-items: center; + justify-content: space-evenly; + padding: 5px; + margin: 10px; +} + +.formbold-btn { + padding: 12px 25px; + margin-top: 25px; + font-size: 16px; + font-weight: 500; + color: black; + cursor: pointer; + background-color: var(--csm-green); + border: none; + border-radius: 5px; +} + +.formbold-btn:hover { + box-shadow: 0 3px 8px rgb(0 0 0 / 5%); +} diff --git a/csm_web/frontend/src/utils/queries/profiles.tsx b/csm_web/frontend/src/utils/queries/profiles.tsx index 715ca09a..ca34a0ad 100644 --- a/csm_web/frontend/src/utils/queries/profiles.tsx +++ b/csm_web/frontend/src/utils/queries/profiles.tsx @@ -6,7 +6,7 @@ import { useMutation, UseMutationResult, useQuery, useQueryClient, UseQueryResul import { isNull } from "lodash"; import { DateTime } from "luxon"; import { fetchNormalized, fetchWithMethod, HTTP_METHODS } from "../api"; -import { RawUserInfo } from "../types"; +import { RawUserInfo, Section } from "../types"; import { handleError, handlePermissionsError, handleRetry, PermissionError, ServerError } from "./helpers"; /* ===== Mutation ===== */ @@ -47,6 +47,33 @@ export const useStudentsInfo = (): UseQueryResult => { return queryResult; }; +/** + * Hook to get a mentor profile within section + */ +export const useMentorProfile = (id: number): UseQueryResult => { + const queryResult = useQuery( + ["sections", id], + async () => { + if (isNaN(id)) { + throw new PermissionError("Invalid section id"); + } + const response = await fetchNormalized(`/sections/${id}`); + if (response.ok) { + const section = await response.json(); + const mentor = section.mentor; + return mentor; + } else { + handlePermissionsError(response.status); + throw new ServerError(`Failed to fetch section ${id}`); + } + }, + { retry: handleRetry } + ); + + handleError(queryResult); + return queryResult; +}; + export const useUserInfoUpdateMutation = ( userId: number | undefined ): UseMutationResult => { diff --git a/csm_web/scheduler/views/user.py b/csm_web/scheduler/views/user.py index 0ccdba7c..82774b98 100644 --- a/csm_web/scheduler/views/user.py +++ b/csm_web/scheduler/views/user.py @@ -78,7 +78,7 @@ def profile(request, pk=None): return Response(serializer.data, status=status.HTTP_200_OK) -@api_view(["GET"]) +@api_view(["POST"]) def fetch_user_pk(request, pk=None): """ Function for getting user IDs from profile IDs @@ -92,12 +92,18 @@ def fetch_user_pk(request, pk=None): queryset = Student.objects.all() elif profile_type == "mentor": queryset = Mentor.objects.all() + print(queryset) elif profile_type == "coordinator": queryset = Coordinator.objects.all() else: return Response(status=status.HTTP_400_BAD_REQUEST) + print(pk) person = queryset.get(pk=pk) - serializer = UserSerializer(person.user.id) + print(person) + + serializer = UserSerializer(person.user) + + print(serializer.data) return Response(serializer.data, status=status.HTTP_200_OK)