Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions APIClients/ReviewPageAPIClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { fetchGraphql } from "@utils/makegqlrequest";
import { mutations } from "graphql/queries";
import BaseAPIClient from "./BaseAPIClient";
import { ReviewedApplicantRecordDTO } from "types/review";

export const reportReviewConflict = async (
applicantRecordId: string,
reviewerId: number,
): Promise<ReviewedApplicantRecordDTO> => {
BaseAPIClient.handleAuthRefresh();
const rawResponse = await fetchGraphql(mutations.reportReviewConflict, {
applicantRecordId,
reviewerId,
});
const response = rawResponse?.data?.reportReviewConflict;

if (!response) {
throw new Error("Conflict report request returned no data");
}

return response;
};
62 changes: 62 additions & 0 deletions components/common/Dialogue.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { ReactNode } from "react";
import Dialog from "@mui/material/Dialog";
import { DialogActions } from "@mui/material";
import { useTheme } from "@mui/material/styles";

type DialogueProps = {
open: boolean;
onClose: () => void;
header: string;
text: string;
errorText?: string;
children?: ReactNode;
};

export const Dialogue = ({
open,
header,
text,
errorText,
children,
}: DialogueProps) => {
const theme = useTheme();

return (
<Dialog
open={open}
PaperProps={{
sx: {
borderRadius: 2,
overflow: "hidden",
boxShadow: "none",
opacity: 1,
},
}}
>
<div
className="flex flex-col justify-center items-center p-6 w-[310px] w-full"
style={{
backgroundColor: theme.palette.background.paper,
}}
>
<div className="flex flex-col justify-center items-center gap-2">
<h2
className="font-poppins text-[20px] font-medium leading-[1.4] text-center"
style={{ color: theme.palette.primary.main }}
>
{header}
</h2>
<div className="font-source text-[14px] font-normal leading-[140%] text-center">
<div style={{ color: theme.palette.text.primary }}> {text} </div>
{errorText && (
<div style={{ color: theme.palette.error.main }}>{errorText}</div>
)}
</div>
</div>
<DialogActions className="w-full h-[36px] !p-0 mt-9">
{children}
</DialogActions>
</div>
</Dialog>
);
};
31 changes: 6 additions & 25 deletions components/common/SplitPageLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { LongLeftIcon } from "@components/icons/long-left.icon";
Comment thread
SaqAsh marked this conversation as resolved.
import { useTheme } from "@mui/material/styles";
import Link from "next/link";
import { ReactElement, ReactNode } from "react";

type SplitRatio = "equal";
Expand Down Expand Up @@ -60,10 +58,8 @@ interface PanelLayoutProps {
borderLeft?: boolean;
/** "xlarge" = left panel (28px, 600), "medium" = right panel (20px, 500) */
titleVariant?: "xlarge" | "medium";
/** Renders pill-shaped "Back to home" link with arrow icon */
backToHomeHref?: string;
/** Optional action (e.g. Report button) shown on the right of the Back to home row on the left panel */
headerRightAction?: ReactNode;
Comment thread
SaqAsh marked this conversation as resolved.
/** Optional top row above title (e.g. back link + actions) */
header?: ReactNode;
/** When false, hides subtitle (e.g. for INFO stage) */
showApplicationTitle?: boolean;
contentClassName?: string;
Expand All @@ -79,8 +75,7 @@ export const PanelLayout = ({
borderRight = false,
borderLeft = false,
titleVariant = "xlarge",
backToHomeHref,
headerRightAction,
header,
showApplicationTitle = true,
contentClassName,
children,
Expand Down Expand Up @@ -121,25 +116,11 @@ export const PanelLayout = ({
}}
>
<div className="flex flex-col h-full overflow-hidden px-9 py-8">
{(backToHomeHref || headerRightAction) && (
{header ? (
<div className="flex justify-between items-center w-full mb-8 shrink-0 gap-4">
{backToHomeHref ? (
<Link href={backToHomeHref} passHref>
<a className="font-source no-underline inline-flex justify-center items-center gap-2 w-fit cursor-pointer shrink-0 hover:opacity-90 rounded-full py-2 px-4 border-2 border-blue bg-white text-blue text-base font-normal leading-[1.4] hover:bg-sky-100 hover:border-blue hover:text-blue">
<LongLeftIcon />
Back to home
</a>
</Link>
) : (
<span />
)}
{headerRightAction != null ? (
<div className="shrink-0 flex items-center">
{headerRightAction}
</div>
) : null}
{header}
</div>
)}
) : null}
{showApplicationTitle && subtitle && (
<p
className="font-poppins text-charcoal-500 mb-4 shrink-0 text-[15px]"
Expand Down
12 changes: 10 additions & 2 deletions components/interview/nav/InterviewNavPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ interface InterviewNavPanelProps {
candidateName: string;
}

export const InterviewNavPanel = ({ candidateName }: InterviewNavPanelProps) => {
export const InterviewNavPanel = ({
candidateName,
}: InterviewNavPanelProps) => {
const router = useRouter();
const theme = useTheme();

Expand Down Expand Up @@ -38,7 +40,13 @@ export const InterviewNavPanel = ({ candidateName }: InterviewNavPanelProps) =>
const active = isActive(item.path);
return (
<li key={item.step}>
<Link href={interviewedApplicantRecordId ? `${item.path}?interviewedApplicantRecordId=${interviewedApplicantRecordId}` : item.path}>
<Link
href={
interviewedApplicantRecordId
? `${item.path}?interviewedApplicantRecordId=${interviewedApplicantRecordId}`
: item.path
}
>
<a
className={`flex items-center justify-between self-stretch rounded-lg px-5 py-2.5 hover:bg-[#F1F1F1] ${
active ? "bg-[#F1F1F1] font-semibold" : "font-normal"
Expand Down
6 changes: 3 additions & 3 deletions components/interview/shared/constants.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { NavItem, HeaderStepConfig } from "./types";

export const InterviewStep = {
PROFILE : "PROFILE",
ASSESSMENT : "ASSESSMENT",
REPORT : "REPORT",
PROFILE: "PROFILE",
ASSESSMENT: "ASSESSMENT",
REPORT: "REPORT",
} as const;

export const InterviewHeaderStep = {
Expand Down
51 changes: 51 additions & 0 deletions components/review/dialogues/ReportConflictDialogue.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import Button from "@components/common/Button";
import { Dialogue } from "@components/common/Dialogue";

type ConflictDialogueProps = {
open: boolean;
hasError: boolean;
onClose: () => void;
onConfirm: () => void;
};

export const ReportConflictDialogue = ({
open,
hasError,
onClose,
onConfirm,
}: ConflictDialogueProps) => {
return (
<Dialogue
open={open}
onClose={onClose}
header="Report as conflict of interest?"
text="Clicking yes will notify admins and cannot be undone."
errorText={
hasError
? "An error occurred while reporting. Please try again."
: undefined
}
>
<div className="flex gap-4 w-full">
<Button
variant="secondary"
size="sm"
onClick={onClose}
className="flex-1 min-w-0 flex justify-center items-center whitespace-nowrap !m-0"
>
<span className="text-[16px] font-normal font-source">Cancel</span>
</Button>
<Button
variant="primary"
size="sm"
onClick={onConfirm}
className="flex-1 min-w-0 flex justify-center items-center whitespace-nowrap !m-0"
>
<span className="text-[16px] font-normal font-source">
Yes, report
</span>
</Button>
</div>
</Dialogue>
);
};
34 changes: 34 additions & 0 deletions components/review/dialogues/ReportConflictSuccessDialogue.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Button from "@components/common/Button";
import { Dialogue } from "@components/common/Dialogue";

type ReportConflictSuccessDialogueProps = {
open: boolean;
onClose: () => void;
};

export const ReportConflictSuccessDialogue = ({
open,
onClose,
}: ReportConflictSuccessDialogueProps) => {
return (
<Dialogue
open={open}
onClose={onClose}
header="Conflict reported!"
text="This applicant has been reported as a conflict of interest and will be re-assigned to another reviewer."
>
<div className="flex gap-4 w-full">
<Button
variant="primary"
size="sm"
onClick={onClose}
className="flex-1 min-w-0 flex justify-center items-center whitespace-nowrap !m-0"
>
<span className="text-[16px] font-normal font-source">
Back to homepage
</span>
</Button>
</div>
</Dialogue>
);
};
27 changes: 27 additions & 0 deletions components/review/shared/ReviewStageHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { LongLeftIcon } from "@components/icons/long-left.icon";
import Link from "next/link";
import { ReactNode } from "react";

interface ReviewStageHeaderProps {
backHref: string;
right?: ReactNode;
}

export const ReviewStageHeader = ({
backHref,
right,
}: ReviewStageHeaderProps) => {
return (
<>
<Link href={backHref} passHref>
<a className="font-source no-underline inline-flex justify-center items-center gap-2 w-fit cursor-pointer shrink-0 hover:opacity-90 rounded-full py-2 px-4 border-2 border-blue bg-white text-blue text-base font-normal leading-[1.4] hover:bg-sky-100 hover:border-blue hover:text-blue">
<LongLeftIcon />
Back to home
</a>
</Link>
{right != null ? (
<div className="shrink-0 flex items-center">{right}</div>
) : null}
</>
);
};
17 changes: 11 additions & 6 deletions components/review/shared/ReviewStepper.tsx
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these changes are not really relevant tbh, because these endpoints are deprecated, changes are just made to avoid typing errors

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you thank you

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useRouter } from "next/router";
import { useContext, useState } from "react";
import { REVIEW_STAGES, ReviewStage } from "./constants";
import { ReviewSetStageContext } from "./ReviewContext";
import { getReviewId } from "./reviewUtils";
import { getApplicantRecordId } from "./reviewUtils";
import { ReviewEndData, ReviewScores } from "./types";
import { useTheme } from "@mui/material/styles";
import { ReactElement } from "react";
Expand All @@ -18,7 +18,7 @@ const STAGE_RATING_FIELDS: [ReviewStage, string][] = [
];

const sendRatingData = (
id: number,
id: string,
ratingToBeChanged: string,
newValue: number | undefined,
) => {
Expand All @@ -30,7 +30,7 @@ const sendRatingData = (
};

const sendFinalComments = (
id: number,
id: string,
newComments: string,
newSkillCategory: string,
newRecommendedSecondChoice: string,
Expand Down Expand Up @@ -78,11 +78,11 @@ export const ReviewStepper = ({
if (!router.isReady) return null;
if (currentStage === ReviewStage.END_SUCCESS) return null;

const reviewId = getReviewId(router.query);
const applicantRecordId = getApplicantRecordId(router.query);

const updateAllData = () => {
const ratingPromises = STAGE_RATING_FIELDS.map(([stage, field]) =>
sendRatingData(reviewId, field, scores[stage]),
sendRatingData(applicantRecordId, field, scores[stage]),
);

const {
Expand All @@ -93,7 +93,12 @@ export const ReviewStepper = ({

return Promise.all([
...ratingPromises,
sendFinalComments(reviewId, comments, skillsCategory, secondChoiceRole),
sendFinalComments(
applicantRecordId,
comments,
skillsCategory,
secondChoiceRole,
),
]);
};

Expand Down
26 changes: 14 additions & 12 deletions components/review/shared/reviewUtils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ParsedUrlQuery } from "node:querystring";

export const extractShortAnswerData = (shortAnswerJSON: any) => {
const extractedQuestions = shortAnswerJSON.map(
(dict: { [key: string]: string }) => dict.question,
Expand All @@ -10,17 +12,17 @@ export const extractShortAnswerData = (shortAnswerJSON: any) => {
return { extractedQuestions, extractedAnswers };
};

export const getReviewId = (
query: Record<string, string | string[] | undefined>,
): number => {
const reviewId =
typeof query["reviewId"] === "string"
? parseInt(query["reviewId"])
: (() => {
throw new Error("reviewId must be a String");
})();
if (Number.isNaN(reviewId))
throw Error("reviewId must be parsable into an int");
Comment on lines -13 to -23
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still kinda janky tbh, idk if there is a good pattern to parse URLs

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the cleanest way to do something like this is to use a query validator that is typed like zod

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apart from that tho it's fine

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oooh i kinda want to look in this and maybe some other native react options when we move to new repo. let's punt this for after migration?

export const getApplicantRecordId = (query: ParsedUrlQuery): string => {
const parsedQuery = query.applicantRecordId;
if (!parsedQuery) {
throw new Error("Applicant record ID is required to access review page.");
}

if (Array.isArray(parsedQuery)) {
throw new Error(
"Multiple applicant record IDs provided. Only one is expected.",
);
}

return reviewId;
return parsedQuery;
};
Loading