From 63267ac6796972173dfe08e48b8462b16d5d9e14 Mon Sep 17 00:00:00 2001 From: ajdj100 Date: Mon, 8 Dec 2025 21:28:02 -0500 Subject: [PATCH] set up viewing of users application history --- api/src/routes/applications.ts | 43 ++++++- api/src/services/applicationService.ts | 16 ++- ui/src/api/application.ts | 20 ++++ ui/src/components/Navigation/Navbar.vue | 1 + ui/src/pages/Application.vue | 143 +++++++++++------------ ui/src/pages/MyApplications.vue | 147 ++++++++++++++++++++++++ ui/src/router/index.js | 7 +- 7 files changed, 296 insertions(+), 81 deletions(-) create mode 100644 ui/src/pages/MyApplications.vue diff --git a/api/src/routes/applications.ts b/api/src/routes/applications.ts index a5970d5..bf877e0 100644 --- a/api/src/routes/applications.ts +++ b/api/src/routes/applications.ts @@ -2,7 +2,7 @@ const express = require('express'); const router = express.Router(); import pool from '../db'; -import { approveApplication, createApplication, denyApplication, getApplicationByID, getApplicationComments, getApplicationList, getMemberApplication } from '../services/applicationService'; +import { approveApplication, createApplication, denyApplication, getAllMemberApplications, getApplicationByID, getApplicationComments, getApplicationList, getMemberApplication } from '../services/applicationService'; import { MemberState, setUserState } from '../services/memberService'; import { getRankByName, insertMemberRank } from '../services/rankService'; import { ApplicationFull, CommentRow } from "@app/shared/types/application" @@ -38,6 +38,20 @@ router.get('/all', async (req, res) => { } }); +router.get('/meList', async (req, res) => { + + let userID = req.user.id; + + try { + let application = await getAllMemberApplications(userID); + + return res.status(200).json(application); + } catch (error) { + console.error('Failed to load applications: \n', error); + return res.status(500).json(error); + } +}) + router.get('/me', async (req, res) => { let userID = req.user.id; @@ -62,6 +76,33 @@ router.get('/me', async (req, res) => { } }) +// GET /application/:id +router.get('/me/:id', async (req: Request, res: Response) => { + let appID = Number(req.params.id); + let member = req.user.id; + try { + const application = await getApplicationByID(appID); + if (application === undefined) + return res.sendStatus(204); + console.log(application.member_id, member) + if (application.member_id != member) { + return res.sendStatus(403); + } + + const comments: CommentRow[] = await getApplicationComments(appID); + + const output: ApplicationFull = { + application, + comments, + } + return res.status(200).json(output); + } + catch (err) { + console.error('Query failed:', err); + return res.status(500).json({ error: 'Failed to load application' }); + } +}); + // GET /application/:id router.get('/:id', async (req, res) => { let appID = req.params.id; diff --git a/api/src/services/applicationService.ts b/api/src/services/applicationService.ts index 5a8bf1b..3224479 100644 --- a/api/src/services/applicationService.ts +++ b/api/src/services/applicationService.ts @@ -19,9 +19,6 @@ export async function getMemberApplication(memberID: number): Promise { - -// } export async function getApplicationByID(appID: number): Promise { const sql = @@ -49,6 +46,19 @@ export async function getApplicationList(): Promise { return rows; } +export async function getAllMemberApplications(memberID: number): Promise { + const sql = `SELECT + app.id, + app.member_id, + app.submitted_at, + app.app_status + FROM applications AS app WHERE app.member_id = ? ORDER BY submitted_at DESC;`; + + const rows: ApplicationListRow[] = await pool.query(sql, [memberID]) + return rows; +} + + export async function approveApplication(id: number) { const sql = ` UPDATE applications diff --git a/ui/src/api/application.ts b/ui/src/api/application.ts index 6e8fd4a..ef5c543 100644 --- a/ui/src/api/application.ts +++ b/ui/src/api/application.ts @@ -122,6 +122,26 @@ export async function getAllApplications(): Promise { } } +export async function loadMyApplications(): Promise { + const res = await fetch(`${addr}/application/meList`, { credentials: 'include' }) + + if (res.ok) { + return res.json() + } else { + console.error("Something went wrong approving the application") + } +} + +export async function getMyApplication(id: number): Promise { + const res = await fetch(`${addr}/application/me/${id}`, { credentials: 'include' }) + if (res.status === 204) return null + if (res.status === 403) throw new Error("Unauthorized"); + if (!res.ok) throw new Error('Failed to load application') + const json = await res.json() + // Accept either the object at root or under `application` + return json; +} + export async function approveApplication(id: Number) { const res = await fetch(`${addr}/application/approve/${id}`, { method: 'POST' }) diff --git a/ui/src/components/Navigation/Navbar.vue b/ui/src/components/Navigation/Navbar.vue index 72c5bd4..66b690d 100644 --- a/ui/src/components/Navigation/Navbar.vue +++ b/ui/src/components/Navigation/Navbar.vue @@ -170,6 +170,7 @@ function blurAfter() { My Application + Application History Logout diff --git a/ui/src/pages/Application.vue b/ui/src/pages/Application.vue index 433f6ca..091c926 100644 --- a/ui/src/pages/Application.vue +++ b/ui/src/pages/Application.vue @@ -2,10 +2,11 @@ import ApplicationChat from '@/components/application/ApplicationChat.vue'; import ApplicationForm from '@/components/application/ApplicationForm.vue'; import { onMounted, ref } from 'vue'; -import { ApplicationData, approveApplication, denyApplication, loadApplication, postApplication, postChatMessage, ApplicationStatus } from '@/api/application'; +import { ApplicationData, approveApplication, denyApplication, loadApplication, postApplication, postChatMessage, ApplicationStatus, getMyApplication, ApplicationFull } from '@/api/application'; import { useRoute } from 'vue-router'; import Button from '@/components/ui/button/Button.vue'; import { CheckIcon, XIcon } from 'lucide-vue-next'; +import Unauthorized from './Unauthorized.vue'; const appData = ref(null); const appID = ref(null); @@ -19,13 +20,12 @@ const loading = ref(true); const member_name = ref(); const props = defineProps<{ - mode?: "create" | "view-self" | "view-recruiter" + mode?: "create" | "view-self" | "view-recruiter" | "view-self-id" }>() -const finalMode = ref<"create" | "view-self" | "view-recruiter">("create"); +const finalMode = ref<"create" | "view-self" | "view-recruiter" | "view-self-id">("create"); -async function loadByID(id: number | string) { - const raw = await loadApplication(id); +function loadData(raw: ApplicationFull) { const data = raw.application; @@ -40,20 +40,20 @@ async function loadByID(id: number | string) { readOnly.value = true; } -const router = useRoute(); +const route = useRoute(); +const unauthorized = ref(false); onMounted(async () => { - //recruiter mode if (props.mode === 'view-recruiter') { finalMode.value = 'view-recruiter'; - await loadByID(Number(router.params.id)); + loadData(await loadApplication(Number(route.params.id))) } //viewer mode if (props.mode === 'view-self') { finalMode.value = 'view-self'; - await loadByID('me'); + loadData(await loadApplication("me")) } //creator mode @@ -64,34 +64,23 @@ onMounted(async () => { newApp.value = true; } + if (props.mode === 'view-self-id') { + finalMode.value = 'view-self-id'; + try { + let raw = await getMyApplication(Number(route.params.id)) + loadData(raw); + unauthorized.value = false; + + } catch (error) { + if (error.message === "Unauthorized") { + unauthorized.value = true; + } else { + console.error(error); + } + } + } + loading.value = false; - - // try { - // //get app ID from URL param - // if (appIDRaw === undefined) { - // //new app - // appData.value = null - // readOnly.value = false; - // newApp.value = true; - // } else { - // //load app - // const raw = await loadApplication(appIDRaw.toString()); - - // const data = raw.application; - - // appID.value = data.id; - // appData.value = data.app_data; - // chatData.value = raw.comments; - // status.value = data.app_status; - // decisionDate.value = new Date(data.decision_at); - // submitDate.value = data.submitted_at ? new Date(data.submitted_at) : null; - // member_name.value = data.member_name; - // newApp.value = false; - // readOnly.value = true; - // } - // } catch (e) { - // console.error(e); - // } }) async function postComment(comment) { @@ -107,7 +96,7 @@ async function postApp(appData) { newApp.value = false; emit('submit'); } - // TODO: Handle fail to post + // TODO: Handle fail to post } async function handleApprove(id) { @@ -122,52 +111,58 @@ async function handleDeny(id) {