Skip to content

Commit

Permalink
Account Archival Frontend & Backend (#107)
Browse files Browse the repository at this point in the history
* feat: add create program notes backend

* fix: THead unique key prop warning

* feat: add edited by field to notes

* fix: fix styling issues

* feat: add edit and delete functionality to progress notes and change userId to uid for backend

* fix: fix calendar styling

* feat: add download progress notes functionality using react-pdf

* feat: add mobile responsiveness

* feat: create program context provider shared across Home, Programs, and Notes page

* fix: fix styling to be consistent across pages

* feat: add progress note filtering and add loading spinner to pages

* feat: add account type authorization checks on frontend and backend

* refactor: refactor notes logic and fix styling

* feat: add logic to check for no students and refactor filter

* feat: add shadows using overflow clip

* fix: fix styling for search filter

* fix: fix small style inconsistencies

* fix: update poppins font url to use https

* fix: fix modal close button styling to be more consistent

* fix: fix escape key overriding dialog close

* Squashed commit of the following:

commit 0b133b8
Author: Michael Sullivan <[email protected]>
Date:   Tue May 14 09:09:16 2024 -0700

    Feature/mraysu/program form v2 (#100)

    * Update Backend Program Schema

    * V2 UI

    * Disabled Editing Program Type

    * Frontend-backend integration

    * Lint fixes

    ---------

    Co-authored-by: mraysu <[email protected]>
    Co-authored-by: Adhithya Ananthan <[email protected]>

commit e17b509
Author: parth4apple <[email protected]>
Date:   Tue May 14 09:01:15 2024 -0700

    Student and Enrollment Schema modifications (#101)

    * feat: initial schema

    * feat: edit routes

    * feat: test and fix routes

* feat: add frontend and backend for account archival

* feat: ensure ui updates and refactor code

* Squashed merge with main

* fix: update type of VerifyUser to be same as User

* fix: fix user type error
  • Loading branch information
aaronchan32 authored Jun 16, 2024
1 parent fe61637 commit a16cf00
Show file tree
Hide file tree
Showing 24 changed files with 663 additions and 142 deletions.
4 changes: 4 additions & 0 deletions backend/src/controllers/types/userTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export type EditLastChangedPasswordRequestBody = UserId & {
currentDate: string;
};

export type UpdateAccountTypeRequestBody = UserId & {
updateUserId: string;
};

export type SaveImageRequest = {
body: {
previousImageId: string;
Expand Down
106 changes: 97 additions & 9 deletions backend/src/controllers/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
EditNameRequestBody,
EditPhotoRequestBody,
LoginUserRequestBody,
UpdateAccountTypeRequestBody,
} from "./types/userTypes";

export const createUser = async (
Expand Down Expand Up @@ -162,15 +163,7 @@ export const loginUser = async (
if (!user) {
throw ValidationError.USER_NOT_FOUND;
}
res.status(200).json({
uid: user._id,
role: user.accountType,
approvalStatus: user.approvalStatus,
profilePicture: user.profilePicture,
name: user.name,
email: user.email,
lastChangedPassword: user.lastChangedPassword,
});
res.status(200).json(user);
return;
} catch (e) {
nxt();
Expand Down Expand Up @@ -317,3 +310,98 @@ export const editLastChangedPassword = async (
});
}
};

export const getAllTeamAccounts = async (
req: Request<Record<string, never>, Record<string, never>, UserIdRequestBody>,
res: Response,
nxt: NextFunction,
) => {
try {
const { uid } = req.body;

const user = await UserModel.findById(uid);
if (!user) {
throw ValidationError.USER_NOT_FOUND;
}

if (user.accountType !== "admin") {
throw ValidationError.UNAUTHORIZED_USER;
}

const allTeamAccounts = await UserModel.find({ accountType: "team" });

return res.status(200).json(allTeamAccounts);
} catch (error) {
nxt(error);
return res.status(400).json({
error,
});
}
};

export const editAccountType = async (
req: Request<Record<string, never>, Record<string, never>, UpdateAccountTypeRequestBody>,
res: Response,
nxt: NextFunction,
) => {
try {
const { uid, updateUserId } = req.body;

const user = await UserModel.findById(uid);
const updatedUser = await UserModel.findById(updateUserId);
if (!user || !updatedUser) {
throw ValidationError.USER_NOT_FOUND;
}

if (user.accountType !== "admin") {
throw ValidationError.UNAUTHORIZED_USER;
}

const updatedUserAdmin = await UserModel.findByIdAndUpdate(updateUserId, {
accountType: "admin",
});

return res.status(200).json(updatedUserAdmin);
} catch (error) {
nxt(error);
return res.status(400).json({
error,
});
}
};

export const editArchiveStatus = async (
req: Request<Record<string, never>, Record<string, never>, UpdateAccountTypeRequestBody>,
res: Response,
nxt: NextFunction,
) => {
try {
const { uid, updateUserId } = req.body;

const user = await UserModel.findById(uid);
const updatedUser = await UserModel.findById(updateUserId);
if (!user || !updatedUser) {
throw ValidationError.USER_NOT_FOUND;
}

if (updatedUser.accountType === "admin") {
throw ValidationError.UNAUTHORIZED_USER;
}

if (user.accountType !== "admin") {
throw ValidationError.UNAUTHORIZED_USER;
}

// Disable Firebase account to prevent login
await firebaseAdminAuth.updateUser(updateUserId, { disabled: true });

const updatedUserAdmin = await UserModel.findByIdAndUpdate(updateUserId, { archived: true });

return res.status(200).json(updatedUserAdmin);
} catch (error) {
nxt(error);
return res.status(400).json({
error,
});
}
};
1 change: 1 addition & 0 deletions backend/src/models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const userSchema = new mongoose.Schema({
email: { type: String, required: true },
profilePicture: { type: String, required: false, default: "default" },
lastChangedPassword: { type: Date, required: false, default: Date.now() },
archived: { type: Boolean, required: false, default: false },
});

type User = InferSchemaType<typeof userSchema>;
Expand Down
13 changes: 13 additions & 0 deletions backend/src/routes/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,18 @@ router.patch(
UserValidator.editLastChangedPassword,
UserController.editLastChangedPassword,
);
router.get("/getAllTeamAccounts", [verifyAuthToken], UserController.getAllTeamAccounts);
router.patch(
"/editAccountType",
[verifyAuthToken],
UserValidator.editAccountType,
UserController.editAccountType,
);
router.patch(
"/editArchiveStatus",
[verifyAuthToken],
UserValidator.editArchiveStatus,
UserController.editArchiveStatus,
);

export default router;
1 change: 0 additions & 1 deletion backend/src/validators/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ const verifyAuthToken = async (req: RequestWithUserId, res: Response, next: Next
let userInfo: DecodedIdToken;
try {
userInfo = await decodeAuthToken(token);
// req.userId = userInfo.uid;
} catch (e) {
return res
.status(AuthError.INVALID_AUTH_TOKEN.status)
Expand Down
16 changes: 16 additions & 0 deletions backend/src/validators/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,19 @@ export const editLastChangedPassword: ValidationChain[] = [
.isISO8601()
.withMessage("Invalid Date format"),
];

export const editAccountType: ValidationChain[] = [
body("updateUserId")
.exists()
.withMessage("ID of User to be updated is required")
.notEmpty()
.withMessage("User ID cannot be empty"),
];

export const editArchiveStatus: ValidationChain[] = [
body("updateUserId")
.exists()
.withMessage("ID of User to be updated is required")
.notEmpty()
.withMessage("User ID cannot be empty"),
];
3 changes: 3 additions & 0 deletions frontend/public/icons/archive.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 48 additions & 4 deletions frontend/src/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
import { APIResult, DELETE, GET, PATCH, POST, handleAPIError } from "@/api/requests";

export type User = {
uid: string;
role: "admin" | "team";
approvalStatus: boolean;
profilePicture: string;
_id: string;
name: string;
accountType: "admin" | "team";
approvalStatus: boolean;
email: string;
profilePicture: string;
lastChangedPassword: Date;
archived: boolean;
};

export const createAuthHeader = (firebaseToken: string) => ({
Expand Down Expand Up @@ -195,3 +196,46 @@ export async function editLastChangedPassword(firebaseToken: string): Promise<AP
return handleAPIError(error);
}
}

export async function getAllTeamAccounts(firebaseToken: string): Promise<APIResult<User[]>> {
try {
const headers = createAuthHeader(firebaseToken);
const response = await GET(`/user/getAllTeamAccounts`, headers);

const json = (await response.json()) as User[];
return { success: true, data: json };
} catch (error) {
return handleAPIError(error);
}
}

export async function editAccountType(
updateUserId: string,
firebaseToken: string,
): Promise<APIResult<User>> {
try {
const updateAccountData = { updateUserId };
const headers = createAuthHeader(firebaseToken);
const response = await PATCH(`/user/editAccountType`, updateAccountData, headers);

const json = (await response.json()) as User;
return { success: true, data: json };
} catch (error) {
return handleAPIError(error);
}
}

export async function editArchiveStatus(
updateUserId: string,
firebaseToken: string,
): Promise<APIResult<User>> {
try {
const headers = createAuthHeader(firebaseToken);
const response = await PATCH(`/user/editArchiveStatus`, { updateUserId }, headers);

const json = (await response.json()) as User;
return { success: true, data: json };
} catch (error) {
return handleAPIError(error);
}
}
2 changes: 1 addition & 1 deletion frontend/src/components/Modals/ModalConfirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const ModalConfirmation = forwardRef<HTMLDivElement, ModalConfirmationProps>(
<div className="grid place-items-center p-3 min-[450px]:p-10">
<div className="mb-8">{icon}</div>
<h3 className="text-bold mb-2 text-lg font-bold">{title}</h3>
{description ? <p>{description}</p> : null}
{description ? <p className="text-center">{description}</p> : null}
<div className="grid justify-center gap-5 pt-6 min-[450px]:flex min-[450px]:w-[70%] min-[450px]:justify-between min-[450px]:[&>*]:basis-full">
<DialogClose asChild>
<Button
Expand Down
1 change: 1 addition & 0 deletions frontend/src/components/Modals/SaveCancelButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export default function SaveCancelButtons({
if (onLeave) onLeave();
}}
title="Are you sure you want to leave?"
description="Your changes will not be saved."
confirmText="Leave"
kind="destructive"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import Image from "next/image";
import { useEffect, useRef, useState } from "react";
import { useForm } from "react-hook-form";

import { cn } from "../../lib/utils";
import { FrameProps } from "../../pages/profile";
import { Button } from "../Button";
import SaveCancelButtons from "../Modals/SaveCancelButtons";
import { Textfield } from "../Textfield";
import { Dialog, DialogTrigger } from "../ui/dialog";
import { cn } from "../../../../lib/utils";
import { Button } from "../../../Button";
import { Textfield } from "../../../Textfield";
import { Dialog, DialogTrigger } from "../../../ui/dialog";
import { FrameProps } from "../PersonalInfo";

import ProfileDialogContent from "./ProfileDialogContent";

import { editName, editPhoto } from "@/api/user";
import SaveCancelButtons from "@/components/Modals/SaveCancelButtons";

type ProfileBasicData = {
name: string;
Expand Down Expand Up @@ -134,20 +134,22 @@ export function BasicInfoFrame({
return (
<section className={cn(frameFormat, className)}>
{/*Info header*/}
<div className="ml-3 flex pb-2 pt-6 text-base sm:ml-10 sm:pt-8 sm:text-2xl">Basic Info</div>
<div className=" ml-3 flex pb-2 pt-6 text-base sm:ml-10 sm:pt-8 sm:text-xl lg:text-2xl">
Basic Info
</div>
{/*Info Fields*/}
<div className=" h-auto w-full flex-grow">
<div className="flex h-full flex-col divide-y-2">
{/*Profile picture*/}
<Dialog open={openProfileForm} onOpenChange={setOpenProfileForm}>
<DialogTrigger asChild>
<div
className="cursor-pointer text-xs hover:bg-pia_accent_green sm:text-base"
className="cursor-pointer text-xs hover:bg-[#e7f0f0] sm:text-base"
onClick={() => {
setOpenProfileForm(true);
}}
>
<div className="ml-3 flex h-full w-auto flex-row py-5 pr-5 sm:ml-14">
<div className="ml-3 flex h-full w-auto flex-row gap-3 py-5 pr-5 sm:ml-14">
<div className="flex w-1/3 flex-none items-center sm:w-1/5">Profile Picture</div>
<div className="flex flex-grow items-center text-[#6C6C6C]">
{isMobile
Expand Down Expand Up @@ -226,7 +228,7 @@ export function BasicInfoFrame({
<Dialog open={openNameForm} onOpenChange={setOpenNameForm}>
<DialogTrigger asChild>
<div
className=" flex-grow cursor-pointer py-6 text-xs transition-colors hover:bg-pia_accent_green sm:text-base"
className=" flex-grow cursor-pointer py-6 text-xs transition-colors hover:bg-[#e7f0f0] sm:text-base"
onClick={() => {
setOpenNameForm(true);
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import { useRouter } from "next/navigation";
import { useState } from "react";
import { useForm } from "react-hook-form";

import { cn } from "../../lib/utils";
import { FrameProps } from "../../pages/profile";
import SaveCancelButtons from "../Modals/SaveCancelButtons";
import { Textfield } from "../Textfield";
import { Dialog, DialogTrigger } from "../ui/dialog";
import { cn } from "../../../../lib/utils";
import { Textfield } from "../../../Textfield";
import { Dialog, DialogTrigger } from "../../../ui/dialog";
import { FrameProps } from "../PersonalInfo";

import ProfileDialogContent from "./ProfileDialogContent";

import { editEmail } from "@/api/user";
import SaveCancelButtons from "@/components/Modals/SaveCancelButtons";
import { initFirebase } from "@/firebase/firebase";

type ContactFrameProps = {
Expand Down Expand Up @@ -70,13 +70,13 @@ export function ContactFrame({
return (
<section className={cn(frameFormat, className)}>
{/*Info header*/}
<div className=" ml-3 flex pb-2 pt-6 text-base sm:ml-10 sm:pt-8 sm:text-2xl">
<div className=" ml-3 flex pb-2 pt-6 text-base sm:ml-10 sm:pt-8 sm:text-xl lg:text-2xl">
Contact Info
</div>
{/*Info Fields */}
<Dialog open={openEmailForm} onOpenChange={setOpenEmailForm}>
<DialogTrigger asChild>
<div className=" flex-grow cursor-pointer py-6 text-xs hover:bg-pia_accent_green sm:text-base">
<div className=" flex-grow cursor-pointer py-6 text-xs hover:bg-[#e7f0f0] sm:text-base">
<div className="ml-3 flex h-full w-auto flex-row pr-5 sm:ml-14">
<div className="flex w-1/3 flex-none items-center sm:w-1/5">Email</div>
<div className="flex flex-grow items-center">{data.email}</div>
Expand Down
Loading

0 comments on commit a16cf00

Please sign in to comment.