diff --git a/.github/workflows/unit-testing.yml b/.github/workflows/unit-testing.yml index 40fd5b77..bc2151d9 100644 --- a/.github/workflows/unit-testing.yml +++ b/.github/workflows/unit-testing.yml @@ -35,3 +35,4 @@ jobs: GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} SENDGRID_API_KEY: ${{ secrets.SENDGRID_API_KEY }} + GOOGLE_BASEFOLDER: ${{ secrets.GOOGLE_BASEFOLDER }} diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0d4da84c..fd063652 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -57,7 +57,7 @@ model User { email String? @unique emailVerified DateTime? image String? - position String? + position String @default("") // @deprecated Pending for removal when we expand to multiple universities admin Boolean @default(false) @@ -70,6 +70,8 @@ model User { approved Approval @default(PENDING) ChapterID String? @db.ObjectId Chapter Chapter? @relation(fields: [ChapterID], references: [id]) + + userRequest UserRequest? } enum Approval { @@ -92,7 +94,7 @@ model Senior { description String StudentIDs String[] @db.ObjectId Students User[] @relation(fields: [StudentIDs], references: [id]) - folder String + folder String @default("") Files File[] ChapterID String @db.ObjectId chapter Chapter @relation(fields: [ChapterID], references: [id]) @@ -126,12 +128,13 @@ model ChapterRequest { } model Chapter { - id String @id @default(auto()) @map("_id") @db.ObjectId - chapterName String - location String - dateCreated DateTime @default(now()) - students User[] - seniors Senior[] + id String @id @default(auto()) @map("_id") @db.ObjectId + chapterName String + location String + chapterFolder String @default("") + dateCreated DateTime @default(now()) + students User[] + seniors Senior[] } model Resource { @@ -151,4 +154,5 @@ model UserRequest { approved Approval @default(PENDING) uid String @unique @db.ObjectId chapterId String @db.ObjectId + user User @relation(fields: [uid], references: [id]) } diff --git a/src/app/api/handle-chapter-request/route.ts b/src/app/api/handle-chapter-request/route.ts index ee815149..aee19033 100644 --- a/src/app/api/handle-chapter-request/route.ts +++ b/src/app/api/handle-chapter-request/route.ts @@ -3,9 +3,12 @@ import { HandleChapterRequest, HandleChapterRequestResponse, } from "./route.schema"; +import { withSession } from "@server/decorator"; +import { createDriveService } from "@server/service"; +import { env } from "@env/server.mjs"; import { prisma } from "@server/db/client"; -export const POST = async (request: NextRequest) => { +export const POST = withSession(async ({ req, session }) => { // Validate the data in the request // If the data is invalid, return a 400 response // with a JSON body containing the validation errors @@ -13,7 +16,7 @@ export const POST = async (request: NextRequest) => { // Validate a proper JSON was passed in as well try { const handleChapterRequest = HandleChapterRequest.safeParse( - await request.json() + await req.json() ); if (!handleChapterRequest.success) { return NextResponse.json( @@ -53,7 +56,7 @@ export const POST = async (request: NextRequest) => { } // If approved, create a new chapter and update approved field of chapter request if (body.approved === true) { - await prisma.chapter.create({ + const createdChapter = await prisma.chapter.create({ data: { chapterName: chapterRequest.university, location: chapterRequest.universityAddress, @@ -67,6 +70,33 @@ export const POST = async (request: NextRequest) => { approved: "APPROVED", }, }); + + const baseFolder = env.GOOGLE_BASEFOLDER; // TODO: make env variable + const fileMetadata = { + name: [`${chapterRequest.university}-${createdChapter.id}`], + mimeType: "application/vnd.google-apps.folder", + parents: [baseFolder], + }; + const fileCreateData = { + resource: fileMetadata, + fields: "id", + }; + + const service = await createDriveService(session.user.id); + const file = await ( + service as NonNullable + ).files.create(fileCreateData); + const googleFolderId = (file as any).data.id; + + await prisma.chapter.update({ + where: { + id: createdChapter.id, + }, + data: { + chapterFolder: googleFolderId, + }, + }); + return NextResponse.json( HandleChapterRequestResponse.parse({ code: "SUCCESS", @@ -101,4 +131,4 @@ export const POST = async (request: NextRequest) => { { status: 500 } ); } -}; +}); diff --git a/src/app/api/route.schema.ts b/src/app/api/route.schema.ts index 6db30c7c..c5ec230d 100644 --- a/src/app/api/route.schema.ts +++ b/src/app/api/route.schema.ts @@ -39,4 +39,14 @@ export const invalidFormReponse = invalidFormErrorSchema.parse({ message: "The form is not valid", }); +export const invalidRequestSchema = z.object({ + code: z.literal("INVALID_REQUEST"), + message: z.string(), +}); + +export const invalidRequestResponse = invalidRequestSchema.parse({ + code: "INVALID_REQUEST", + message: "Request body is invalid", +}); + export type IUnauthorizedErrorSchema = z.infer; diff --git a/src/app/api/senior/route.ts b/src/app/api/senior/route.ts index 69a8d750..6569728b 100644 --- a/src/app/api/senior/route.ts +++ b/src/app/api/senior/route.ts @@ -2,9 +2,7 @@ import { withSessionAndRole } from "@server/decorator"; import { NextResponse } from "next/server"; import { seniorPostResponse, postSeniorSchema } from "./route.schema"; import { prisma } from "@server/db/client"; -import { randomUUID } from "crypto"; -import { google } from "googleapis"; -import { env } from "process"; +import { createDriveService } from "@server/service"; // @TODO - Use google drive service to create folder export const POST = withSessionAndRole( @@ -47,11 +45,36 @@ export const POST = withSessionAndRole( }) ); } - const baseFolder = "1MVyWBeKCd1erNe9gkwBf7yz3wGa40g9a"; // TODO: make env variable + + const chapter = await prisma.chapter.findFirst({ + where: { + id: session.user.ChapterID, + }, + }); + if (!chapter) { + return NextResponse.json( + seniorPostResponse.parse({ + code: "UNKNOWN", + message: "Chapter not found", + }), + { status: 400 } + ); + } + + const senior = await prisma.senior.create({ + data: { + firstname: seniorBody.firstname, + lastname: seniorBody.lastname, + location: seniorBody.location, + description: seniorBody.description, + ChapterID: session.user.ChapterID, + StudentIDs: seniorBody.StudentIDs, + }, + }); + + const baseFolder = chapter.chapterFolder; // TODO: make env variable const fileMetadata = { - name: [ - `${seniorBody.firstname}_${seniorBody.lastname}-${randomUUID()}`, - ], + name: [`${seniorBody.firstname}_${seniorBody.lastname}_${senior.id}`], mimeType: "application/vnd.google-apps.folder", parents: [baseFolder], }; @@ -60,37 +83,18 @@ export const POST = withSessionAndRole( fields: "id", }; - const { access_token, refresh_token } = (await prisma.account.findFirst({ - where: { - userId: session.user.id, - }, - })) ?? { access_token: null }; - const auth = new google.auth.OAuth2({ - clientId: env.GOOGLE_CLIENT_ID, - clientSecret: env.GOOGLE_CLIENT_SECRET, - }); - auth.setCredentials({ - access_token, - refresh_token, - }); - const service = google.drive({ - version: "v3", - auth, - }); + const service = await createDriveService(session.user.id); const file = await (service as NonNullable).files.create( fileCreateData ); const googleFolderId = (file as any).data.id; - const senior = await prisma.senior.create({ + await prisma.senior.update({ + where: { + id: senior.id, + }, data: { - firstname: seniorBody.firstname, - lastname: seniorBody.lastname, - location: seniorBody.location, - description: seniorBody.description, - ChapterID: session.user.ChapterID, - StudentIDs: seniorBody.StudentIDs, folder: googleFolderId, }, }); diff --git a/src/app/api/user-request/route.ts b/src/app/api/user-request/route.ts index 0b92423e..23fbb16d 100644 --- a/src/app/api/user-request/route.ts +++ b/src/app/api/user-request/route.ts @@ -7,6 +7,7 @@ import { } from "./route.schema"; import { prisma } from "@server/db/client"; import { withSession } from "@server/decorator/index"; +import { createDriveService } from "@server/service"; export const POST = withSession(async ({ req, session }) => { try { @@ -216,7 +217,7 @@ export const PATCH = withSession(async ({ req, session }) => { approved: "APPROVED", }, }); - await prisma.user.update({ + const user = await prisma.user.update({ where: { id: targetUID, }, @@ -224,7 +225,48 @@ export const PATCH = withSession(async ({ req, session }) => { ChapterID: approveChapterRequest.chapterId, }, }); + const chapter = await prisma.chapter.findFirst({ + where: { + id: approveChapterRequest.chapterId, + }, + }); + + if (chapter == null || user == null || user.email == null) { + return NextResponse.json( + ManageChapterRequestResponse.parse({ + code: "INVALID_REQUEST", + message: "Chapter or user (or email) doesn't exist", + }), + { status: 400 } + ); + } + + const folderId = chapter.chapterFolder; + // Next, share the folder with the user that is accepted + const shareFolder = async (folderId: string, userEmail: string) => { + const service = await createDriveService(session.user.id); + + try { + // Define the permission + const permission = { + type: "user", + role: "writer", // Change role as per your requirement + emailAddress: userEmail, + }; + + // Share the folder + await service.permissions.create({ + fileId: folderId, + requestBody: permission, + }); + + console.log("Folder shared successfully!"); + } catch (error) { + console.error("Error sharing folder:", error); + } + }; + await shareFolder(folderId, user.email); return NextResponse.json( ManageChapterRequestResponse.parse({ code: "SUCCESS" }) ); diff --git a/src/app/api/user/[uid]/edit-position/route.client.ts b/src/app/api/user/[uid]/edit-position/route.client.ts new file mode 100644 index 00000000..c90058f8 --- /dev/null +++ b/src/app/api/user/[uid]/edit-position/route.client.ts @@ -0,0 +1,16 @@ +import { TypedRequest } from "@server/type"; +import { z } from "zod"; +import { editPositionRequest, editPositionResponse } from "./route.schema"; + +export const editPosition = async ( + request: TypedRequest>, + uid: string +) => { + const { body, ...options } = request; + const response = await fetch(`/api/user/${uid}/edit-position`, { + method: "PATCH", + body: JSON.stringify(body), + ...options, + }); + return editPositionResponse.parse(await response.json()); +}; diff --git a/src/app/api/user/[uid]/edit-position/route.schema.ts b/src/app/api/user/[uid]/edit-position/route.schema.ts new file mode 100644 index 00000000..38a1b39c --- /dev/null +++ b/src/app/api/user/[uid]/edit-position/route.schema.ts @@ -0,0 +1,11 @@ +import { z } from "zod"; + +export const editPositionRequest = z.object({ + position: z.string(), +}); + +export const editPositionResponse = z.discriminatedUnion("code", [ + z.object({ + code: z.literal("SUCCESS"), + }), +]); diff --git a/src/app/api/user/[uid]/edit-position/route.ts b/src/app/api/user/[uid]/edit-position/route.ts new file mode 100644 index 00000000..7d4421d0 --- /dev/null +++ b/src/app/api/user/[uid]/edit-position/route.ts @@ -0,0 +1,38 @@ +import { prisma } from "@server/db/client"; +import { withRole, withSession } from "@server/decorator"; +import { NextResponse } from "next/server"; +import { editPositionRequest, editPositionResponse } from "./route.schema"; +import { + invalidRequestResponse, + unauthorizedErrorResponse, +} from "@api/route.schema"; + +export const PATCH = withSession( + withRole(["CHAPTER_LEADER"], async ({ session, req, params }) => { + const { user: me } = session; + const otherUid: string = params.params.uid; + const other = await prisma.user.findUnique({ + where: { id: otherUid }, + }); + const request = editPositionRequest.safeParse(await req.json()); + + if (other == null || !request.success) { + return NextResponse.json(invalidRequestResponse, { status: 400 }); + } + + if (me.role !== "CHAPTER_LEADER" || me.ChapterID !== other.ChapterID) { + return NextResponse.json(unauthorizedErrorResponse, { status: 401 }); + } + + await prisma.user.update({ + where: { + id: otherUid, + }, + data: { + position: request.data.position, + }, + }); + + return NextResponse.json(editPositionResponse.parse({ code: "SUCCESS" })); + }) +); diff --git a/src/app/private/[uid]/admin/home/chapters/[chapterId]/page.tsx b/src/app/private/[uid]/admin/home/chapters/[chapterId]/page.tsx index 78c4b7f6..5f459324 100644 --- a/src/app/private/[uid]/admin/home/chapters/[chapterId]/page.tsx +++ b/src/app/private/[uid]/admin/home/chapters/[chapterId]/page.tsx @@ -1,5 +1,5 @@ import { prisma } from "@server/db/client"; -import { DisplayChapter } from "@components/admin"; +import DisplayChapterInfo from "@components/DisplayChapterInfo"; interface ChapterPageParams { params: { @@ -18,18 +18,19 @@ const ChapterPage = async ({ params }: ChapterPageParams) => { }, }); - const chapterRequests = await prisma.userRequest.findMany({ - where: { chapterId: params.chapterId, approved: "PENDING" }, - }); - const requestUsers = await prisma.user.findMany({ - where: { id: { in: chapterRequests.map((req) => req.uid) } }, + const userRequests = await prisma.userRequest.findMany({ + where: { + approved: "PENDING", + }, + include: { user: true }, }); + const resources = await prisma.resource.findMany(); return ( - ); }; diff --git a/src/app/private/[uid]/admin/layout.tsx b/src/app/private/[uid]/admin/layout.tsx index b8064229..42524e0c 100644 --- a/src/app/private/[uid]/admin/layout.tsx +++ b/src/app/private/[uid]/admin/layout.tsx @@ -1,6 +1,6 @@ "use client"; -import { CollapsableSidebarContainer } from "@components/container"; +import { CollapsibleSidebarContainer } from "@components/container"; import { faEnvelope, faHome, @@ -43,9 +43,9 @@ const AdminLayout = ({ children }: IAdminLayout) => { ); return ( - + {children} - + ); }; diff --git a/src/app/private/[uid]/chapter-leader/home/page.tsx b/src/app/private/[uid]/chapter-leader/home/page.tsx index af6218d3..1a39f94f 100644 --- a/src/app/private/[uid]/chapter-leader/home/page.tsx +++ b/src/app/private/[uid]/chapter-leader/home/page.tsx @@ -18,12 +18,35 @@ const UserHomePage = async ({ params }: UserHomePageParams) => { id: user.ChapterID ?? "", }, include: { - students: true, + students: { + where: { + position: { + not: "", + }, + }, + }, }, }); + const resources = await prisma.resource.findMany(); - return ; + const userRequests = await prisma.userRequest.findMany({ + where: { + chapterId: chapter.id, + approved: "PENDING", + }, + include: { + user: true, + }, + }); + + return ( + + ); }; export default UserHomePage; diff --git a/src/app/private/[uid]/chapter-leader/layout.tsx b/src/app/private/[uid]/chapter-leader/layout.tsx index abd0fe60..a1eb7045 100644 --- a/src/app/private/[uid]/chapter-leader/layout.tsx +++ b/src/app/private/[uid]/chapter-leader/layout.tsx @@ -1,7 +1,7 @@ "use client"; import Sidebar, { ISideBar } from "@components/Sidebar"; -import { CollapsableSidebarContainer } from "@components/container"; +import { CollapsibleSidebarContainer } from "@components/container"; import { faHome, faUser, @@ -35,11 +35,6 @@ const UserLayout = ({ children }: IUserLayout) => { link: `/private/${user.id}/chapter-leader/seniors`, icon: faUserGroup, }, - { - name: "Pending", - link: `/private/${user.id}/chapter-leader/pending`, - icon: faUserPlus, - }, { name: "Profile", link: `/private/${user.id}/chapter-leader/edit-profile`, @@ -50,9 +45,9 @@ const UserLayout = ({ children }: IUserLayout) => { ); return ( - + {children} - + ); }; diff --git a/src/app/private/[uid]/chapter-leader/pending/PendingHomePage.tsx b/src/app/private/[uid]/chapter-leader/pending/PendingHomePage.tsx deleted file mode 100644 index 963bf477..00000000 --- a/src/app/private/[uid]/chapter-leader/pending/PendingHomePage.tsx +++ /dev/null @@ -1,34 +0,0 @@ -"use client"; - -import PendingCard from "@components/PendingCard"; -import { CardGrid } from "@components/container"; -import { User } from "@prisma/client"; - -type MembersHomePageProps = { - users: User[]; -}; - -const PendingHomePage = ({ users }: MembersHomePageProps) => { - return ( - <> -

- {`Pending (${users.length})`} -

- {users.length > 0 ? ( - { - return ( - - ); - })} - /> - ) : ( -

- {"This chapter has no pending members."} -

- )} - - ); -}; - -export default PendingHomePage; diff --git a/src/app/private/[uid]/chapter-leader/pending/layout.tsx b/src/app/private/[uid]/chapter-leader/pending/layout.tsx deleted file mode 100644 index b52667ac..00000000 --- a/src/app/private/[uid]/chapter-leader/pending/layout.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { HeaderContainer } from "@components/container"; -import { faUserGroup } from "@fortawesome/free-solid-svg-icons"; - -interface LayoutProps { - children?: React.ReactNode; -} - -const Layout = (props: LayoutProps) => { - return ( - - {props.children} - - ); -}; - -export default Layout; diff --git a/src/app/private/[uid]/chapter-leader/pending/page.tsx b/src/app/private/[uid]/chapter-leader/pending/page.tsx deleted file mode 100644 index 42a97be7..00000000 --- a/src/app/private/[uid]/chapter-leader/pending/page.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from "react"; -import { prisma } from "@server/db/client"; -import PendingHomePage from "./PendingHomePage"; -import { User } from "@prisma/client"; - -const PendingPage = async ({ params }: { params: { uid: string } }) => { - const user = await prisma.user.findFirstOrThrow({ - where: { - id: params.uid, - }, - }); - - const userRequests = await prisma.userRequest.findMany({ - where: { - chapterId: user.ChapterID ?? "", - approved: "PENDING", - }, - }); - - const users: User[] = []; - - for (let i = 0; i < userRequests.length; i++) { - const user = await prisma.user.findFirstOrThrow({ - where: { - id: userRequests[i]?.uid ?? "", - }, - }); - - users.push(user); - } - - return ; -}; - -export default PendingPage; diff --git a/src/app/private/[uid]/chapter-leader/users/MembersHomePage.tsx b/src/app/private/[uid]/chapter-leader/users/MembersHomePage.tsx index f9dcf72c..60e3f651 100644 --- a/src/app/private/[uid]/chapter-leader/users/MembersHomePage.tsx +++ b/src/app/private/[uid]/chapter-leader/users/MembersHomePage.tsx @@ -1,31 +1,103 @@ "use client"; -import { UserTile } from "@components/TileGrid"; +import { TileEdit, UserTile } from "@components/TileGrid"; import SearchableContainer from "@components/SearchableContainer"; import { User } from "@prisma/client"; +import { useContext, useState } from "react"; +import { UserContext } from "@context/UserProvider"; +import { editPosition } from "@api/user/[uid]/edit-position/route.client"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faArrowUpFromBracket } from "@fortawesome/free-solid-svg-icons"; +import { Dropdown } from "@components/selector"; +import { Popup } from "@components/container"; +import { useRouter } from "next/navigation"; +import { sortedStudents } from "@utils"; type MembersHomePageProps = { members: User[]; - user: User; }; -const MembersHomePage = ({ members, user }: MembersHomePageProps) => { +const EBOARD_POSITIONS = [ + "Social Coordinator", + "Senior Outreach Coordinator", + "Head of Media", + "Secretary", + "Treasurer", + "Match Coordinator", +].map((position, idx) => ({ id: idx.toString(), position: position })); + +const MembersHomePage = ({ members }: MembersHomePageProps) => { + const { user } = useContext(UserContext); + const [uidToEdit, setUidToEdit] = useState(null); + const [selectedPosition, setSelectedPosition] = useState< + typeof EBOARD_POSITIONS + >([]); + const router = useRouter(); + + const resetAssignment = () => { + setUidToEdit(null); + setSelectedPosition([]); + }; + const displayMembers = (elem: User, index: number) => ( { + e.stopPropagation(); + setUidToEdit(elem.id); + setSelectedPosition( + EBOARD_POSITIONS.filter( + (position) => position.position === elem.position + ) + ); + }, + icon: , + color: "#22555A", + }, + ]} + /> + } /> ); return ( - <> +

{`Members (${members.length})`}

+ {uidToEdit != null && ( + +
Assign to E-board
+ <>{element.position}} + selected={selectedPosition} + setSelected={setSelectedPosition} + onSave={async () => { + await editPosition( + { + body: { position: selectedPosition[0]?.position ?? "" }, + }, + uidToEdit + ); + resetAssignment(); + router.refresh(); + }} + multipleChoice={false} + /> +
+ )} This chapter has no members. @@ -37,7 +109,7 @@ const MembersHomePage = ({ members, user }: MembersHomePageProps) => { .includes(filter.toLowerCase()) } /> - +
); }; diff --git a/src/app/private/[uid]/chapter-leader/users/page.tsx b/src/app/private/[uid]/chapter-leader/users/page.tsx index 1165f864..93e30e13 100644 --- a/src/app/private/[uid]/chapter-leader/users/page.tsx +++ b/src/app/private/[uid]/chapter-leader/users/page.tsx @@ -3,22 +3,20 @@ import { prisma } from "@server/db/client"; import MembersHomePage from "./MembersHomePage"; const MembersPage = async ({ params }: { params: { uid: string } }) => { - const user = await prisma.user.findFirstOrThrow({ - where: { - id: params.uid, - }, - }); - const chapter = await prisma.chapter.findFirstOrThrow({ where: { - id: user.ChapterID ?? "", + students: { + some: { + id: params.uid, + }, + }, }, include: { students: true, }, }); - return ; + return ; }; export default MembersPage; diff --git a/src/app/private/[uid]/layout.tsx b/src/app/private/[uid]/layout.tsx index 3fbc0d3d..7910e696 100644 --- a/src/app/private/[uid]/layout.tsx +++ b/src/app/private/[uid]/layout.tsx @@ -1,21 +1,37 @@ +import { authOptions } from "@api/auth/[...nextauth]/route"; +import { RootNavigation } from "@components/navigation"; import UserProvider from "@context/UserProvider"; import { prisma } from "@server/db/client"; +import { getServerSession } from "next-auth"; +import { redirect } from "next/navigation"; -interface IPrivateLayout { +interface LayoutProps { children: React.ReactNode; - params: { - uid: string; - }; } -const PrivateLayout = async ({ children, params }: IPrivateLayout) => { - const user = await prisma.user.findFirstOrThrow({ - where: { id: params.uid }, - include: { - Chapter: true, - }, - }); - return {children}; +const Layout = async ({ children }: LayoutProps) => { + const session = await getServerSession(authOptions); + + try { + if (session == null) { + throw new Error(); + } + + const user = await prisma.user.findFirstOrThrow({ + where: { id: session.user?.id }, + include: { + Chapter: true, + }, + }); + + return ( + + {children} + + ); + } catch { + redirect("/public"); + } }; -export default PrivateLayout; +export default Layout; diff --git a/src/app/private/[uid]/user/home/@joinChapter/page.tsx b/src/app/private/[uid]/user/home/@joinChapter/page.tsx deleted file mode 100644 index 6440defe..00000000 --- a/src/app/private/[uid]/user/home/@joinChapter/page.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { UserJoinRequest } from "@components/user"; -import { prisma } from "@server/db/client"; - -interface UserHomePageParams { - params: { - uid: string; - }; -} - -const UserJoinChapterPage = async ({ params }: UserHomePageParams) => { - const chapters = await prisma.chapter.findMany({ - include: { - students: true, - }, - }); - const joinRequest = await prisma.userRequest.findFirst({ - where: { - uid: params.uid, - }, - }); - - return ( - - ); -}; - -export default UserJoinChapterPage; diff --git a/src/app/private/[uid]/user/home/layout.tsx b/src/app/private/[uid]/user/home/layout.tsx index db94c44b..90038e3d 100644 --- a/src/app/private/[uid]/user/home/layout.tsx +++ b/src/app/private/[uid]/user/home/layout.tsx @@ -1,22 +1,21 @@ import { prisma } from "@server/db/client"; import { HeaderContainer } from "@components/container/index"; import { faHouse } from "@fortawesome/free-solid-svg-icons"; +import { UserJoinRequest } from "@components/user"; interface LayoutProps { children: React.ReactNode; - joinChapter: React.ReactNode; params: { uid: string; }; } -const Layout = async ({ children, params, joinChapter }: LayoutProps) => { +const Layout = async ({ children, params }: LayoutProps) => { const user = await prisma.user.findFirstOrThrow({ where: { id: params.uid, }, }); - if (user.ChapterID != null) { return ( { ); } else { - return joinChapter; + const chapters = await prisma.chapter.findMany({ + include: { students: true }, + }); + const joinRequest = await prisma.userRequest.findFirst({ + where: { + uid: params.uid, + }, + }); + return ; } }; diff --git a/src/app/private/[uid]/user/home/page.tsx b/src/app/private/[uid]/user/home/page.tsx index 87dd88ab..506cb40b 100644 --- a/src/app/private/[uid]/user/home/page.tsx +++ b/src/app/private/[uid]/user/home/page.tsx @@ -13,12 +13,30 @@ const UserHomePage = async ({ params }: UserHomePageParams) => { id: params.uid, }, }); + + if (user.ChapterID == null) { + return null; + } + const chapter = await prisma.chapter.findFirstOrThrow({ where: { id: user.ChapterID ?? "", }, include: { - students: true, + students: { + where: { + OR: [ + { + position: { + not: "", + }, + }, + { + role: "CHAPTER_LEADER", + }, + ], + }, + }, }, }); const resources = await prisma.resource.findMany({ @@ -29,14 +47,7 @@ const UserHomePage = async ({ params }: UserHomePageParams) => { }, }); - return ( -
-
- {chapter.chapterName} -
- -
- ); + return ; }; export default UserHomePage; diff --git a/src/app/private/[uid]/user/layout.tsx b/src/app/private/[uid]/user/layout.tsx index 7277efa2..30a987cb 100644 --- a/src/app/private/[uid]/user/layout.tsx +++ b/src/app/private/[uid]/user/layout.tsx @@ -1,6 +1,6 @@ "use client"; -import { CollapsableSidebarContainer } from "@components/container"; +import { CollapsibleSidebarContainer } from "@components/container"; import { faHome, faUsers, faUser } from "@fortawesome/free-solid-svg-icons"; import React, { useContext } from "react"; import { UserContext } from "src/context/UserProvider"; @@ -33,9 +33,9 @@ const UserLayout = ({ children }: IUserLayout) => { ); return ( - + {children} - + ); }; diff --git a/src/components/AddSenior.tsx b/src/components/AddSenior.tsx index e5ee092e..3330e89e 100644 --- a/src/components/AddSenior.tsx +++ b/src/components/AddSenior.tsx @@ -6,7 +6,6 @@ import React, { useState, } from "react"; import Image, { StaticImageData } from "next/legacy/image"; -import cn from "classnames"; import FilterDropdown from "@components/FilterDropdown"; import { Senior, User } from "@prisma/client"; import ImageIcon from "../../public/icons/icon_add_photo.png"; @@ -15,6 +14,7 @@ import { postSenior } from "src/app/api/senior/route.client"; import z from "zod/lib"; import { seniorSchema } from "@server/model"; import { fullName } from "@utils"; +import { Popup } from "./container"; type AddSeniorProps = { seniors: Senior[]; @@ -75,7 +75,7 @@ const StudentSelector = ({ }) => { return (
-
+
Assign students
@@ -84,7 +84,7 @@ const StudentSelector = ({ display={(usr: User) => (
{fullName(usr)} -
+