Skip to content

Commit

Permalink
Merge branch 'main' into andy/remove-stale-cookies
Browse files Browse the repository at this point in the history
  • Loading branch information
alexzhang1618 committed Feb 4, 2025
2 parents 4e744cc + 5d79316 commit 503802c
Show file tree
Hide file tree
Showing 21 changed files with 494 additions and 14 deletions.
4 changes: 0 additions & 4 deletions .github/workflows/backend-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ on:
branches: [ "main" ]
paths: "server/**"

pull_request:
branches: [ "main" ]
paths: "server/**"

jobs:
build-and-deploy:
runs-on: ubuntu-latest
Expand Down
12 changes: 10 additions & 2 deletions client/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { FAQ_QUESTIONS, TIMELINE } from '@/config';
import styles from './page.module.scss';
import Dashboard from '@/components/Dashboard';
import { UserAPI } from '@/lib/api';
import AdminDashboard from '@/components/admin/AdminDashboard';
import { UserAPI, AdminAPI } from '@/lib/api';
import { redirect } from 'next/navigation';
import { getCookie } from '@/lib/services/CookieService';
import { CookieType } from '@/lib/types/enums';
Expand All @@ -16,9 +17,16 @@ export default async function Home() {

try {
const fetchedUser = await UserAPI.getCurrentUser(accessToken);
const accessType = fetchedUser.accessType;
const applications = accessType === 'ADMIN' ? await AdminAPI.getApplications(accessToken) : [];

return (
<main className={styles.main}>
<Dashboard faq={FAQ_QUESTIONS} timeline={TIMELINE} user={fetchedUser} />
{accessType == 'ADMIN' ? (
<AdminDashboard timeline={TIMELINE} user={fetchedUser} applications={applications} />
) : (
<Dashboard faq={FAQ_QUESTIONS} timeline={TIMELINE} user={fetchedUser} />
)}
</main>
);
} catch (error) {
Expand Down
67 changes: 67 additions & 0 deletions client/src/components/admin/AdminDashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import Image from 'next/image';
import Card from '@/components/Card';
import styles from './style.module.scss';
import Banner from '@/../public/assets/banner.png';
import Typography from '@/components/Typography';
import Button from '@/components/Button';
import TimelineItem from '@/components/TimelineItem';
import ApplicationCount from '../ApplicationCount';
import { PrivateProfile, ResponseModel } from '@/lib/types/apiResponses';

/** Dates should be at 12 am UTC. */
export interface Deadlines {
application: Date;
decisions: Date;
acceptance: Date;
hackathon: Date;
}

interface AdminDashboardProps {
timeline: Deadlines;
user: PrivateProfile;
applications: ResponseModel[];
}

const AdminDashboard = ({ timeline, user, applications }: AdminDashboardProps) => {
// Haven't implemented reviewing apps so placeholder for pending apps
const pendingApplications = applications.length;
const totalApplications = applications.length;

return (
<div className={styles.container}>
<Card gap={1.5} className={`${styles.card} ${styles.banner}`}>
<Typography variant="headline/heavy/large" component="h1" className={styles.title}>
Welcome, {user.firstName + ' ' + user.lastName}!
</Typography>
<Typography variant="body/medium" component="p" className={styles.subtitle}>
Review applications and send application status updates below.
</Typography>
<Image
src={Banner}
alt="Two diamond critters find a large jewel in a vault"
quality={100}
className={styles.bannerImage}
/>
</Card>
<Card gap={1.5} className={`${styles.card} ${styles.status}`}>
<ApplicationCount pendingApps={pendingApplications} totalApps={totalApplications} />
<Button href="/review">Continue Reviewing</Button>
</Card>
<Card gap={1.5} className={`${styles.card} ${styles.timeline}`}>
<Typography variant="headline/heavy/small" component="h2">
Timeline
</Typography>
<div className={styles.timelineItemWrapper}>
<TimelineItem date={timeline.application} first>
Application Deadline
</TimelineItem>
<TimelineItem date={timeline.decisions}>Decisions Released</TimelineItem>
<TimelineItem date={timeline.acceptance}>Acceptance Deadline</TimelineItem>
<TimelineItem date={timeline.hackathon}>Hackathon Day!</TimelineItem>
</div>
</Card>
</div>
);
};

export default AdminDashboard;
127 changes: 127 additions & 0 deletions client/src/components/admin/AdminDashboard/style.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
@use '@/styles/vars.scss' as vars;
@use 'sass:list';

.container {
display: grid;
grid-template-areas:
'banner banner'
'status timeline';
grid-template-columns: 1fr 1fr;
gap: 3.5rem;
align-items: start;

@media screen and (max-width: vars.$breakpoint-md) {
display: flex;
flex-direction: column;
gap: vars.$side-padding-mobile;
align-items: stretch;
}

.card {
animation: card-in 0.5s backwards;

@for $i from 1 through 4 {
&:nth-child(#{$i}) {
animation-delay: calc($i * 100ms);
}
}

@keyframes card-in {
from {
transform: translateY(1rem);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
}

.banner {
grid-area: banner;
position: relative;
z-index: 0;
height: 350px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
overflow: hidden;

.title {
background-image: linear-gradient(
to left,
transparent,
transparent,
vars.$orange,
vars.$light-blue,
vars.$white,
vars.$white
);
color: transparent;
-webkit-background-clip: text;
background-clip: text;
background-size: 500%;
animation: gradient-in 2.5s 0.5s both;

// Wrap each shadow in drop-shadow()
$drop-shadows: ();
@each $shadow in vars.$text-shadow {
$drop-shadows: list.append($drop-shadows, drop-shadow(#{$shadow}));
}
filter: #{$drop-shadows};

@keyframes gradient-in {
from {
background-position: right;
}
to {
background-position: left;
}
}
}

.subtitle {
text-shadow: vars.$text-shadow;
animation: fade-in 1s 1.5s backwards;
}

.bannerImage {
position: absolute;
top: 0;
right: 0;
z-index: -1;
width: auto;
height: 100%;
animation: fade-in 3.5s 1.5s backwards;
}

@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
}

.status {
grid-area: status;
display: flex;
align-items: center;
}

.timeline {
grid-area: timeline;

.timelineItemWrapper {
display: flex;
flex-direction: column;
gap: 1.5rem;
position: relative;
z-index: 0;
}
}
}
34 changes: 34 additions & 0 deletions client/src/components/admin/ApplicationCount/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Typography from '@/components/Typography';
import { CircularProgress } from '@mui/material';
import styles from './style.module.scss';

interface ApplicationCountProps {
pendingApps: number;
totalApps: number;
}

const ApplicationCount = ({ pendingApps, totalApps }: ApplicationCountProps) => {
const progress = (totalApps - pendingApps / totalApps) * 100;

return (
<div className={styles.container}>
<CircularProgress
size="250px"
variant="determinate"
value={progress}
sx={{
color: '#9ECAFF',
thickness: 5,
borderRadius: '50%',
boxShadow: `inset 0 0 0 21px #DFE2EB`,
}}
/>
<div className={styles.info}>
<Typography variant="headline/heavy/large">{pendingApps}</Typography>
<Typography variant="label/small">of {totalApps} applications pending review</Typography>
</div>
</div>
);
};

export default ApplicationCount;
13 changes: 13 additions & 0 deletions client/src/components/admin/ApplicationCount/style.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.container {
position: relative;

.info {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
padding: 2rem;
text-align: center;
}
}
19 changes: 19 additions & 0 deletions client/src/lib/api/AdminAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import config from '@/lib/config';
import type { GetApplicationsResponse, ResponseModel } from '@/lib/types/apiResponses';
import axios from 'axios';

/**
* Get all applications
* @returns All users application
*/
export const getApplications = async (token: string): Promise<ResponseModel[]> => {
const response = await axios.get<GetApplicationsResponse>(
`${config.api.baseUrl}${config.api.endpoints.admin.applications}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
return response.data.responses;
};
1 change: 1 addition & 0 deletions client/src/lib/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * as AuthAPI from './AuthAPI';
export * as ResponseAPI from './ResponseAPI';
export * as UserAPI from './UserAPI';
export * as AdminAPI from './AdminAPI';
3 changes: 3 additions & 0 deletions client/src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ const config = {
response: '/response',
application: '/response/application',
},
admin: {
applications: '/admin/applications',
},
},
},
};
Expand Down
6 changes: 6 additions & 0 deletions client/src/lib/types/apiResponses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,9 @@ export interface SubmitApplicationResponse extends ApiResponse {
}

export interface DeleteApplicationResponse extends ApiResponse {}

// Admin FormResponses

export interface GetApplicationsResponse extends ApiResponse {
responses: ResponseModel[];
}
Loading

0 comments on commit 503802c

Please sign in to comment.