Skip to content
Open
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
26 changes: 0 additions & 26 deletions client/src/components/useLoading.tsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,24 +1,33 @@
import { message } from 'antd';
import { useRequest } from 'ahooks';
import { useState } from 'react';
import { CourseTaskVerificationsApi, TaskVerificationAttemptDto } from '@client/api';
import { AxiosError } from 'axios';
import { useLoading } from '@client/components/useLoading';

export function useVerificationsAnswers(courseId: number, courseTaskId: number) {
const [answers, setAnswers] = useState<TaskVerificationAttemptDto[] | null>(null);
const [loading, withLoading] = useLoading(false, e => {
const error = e as AxiosError<Error>;
message.error(error.response?.data?.message || error?.message);
});

const showAnswers = withLoading(async () => {
const result = await new CourseTaskVerificationsApi().getAnswers(courseId, courseTaskId);
setAnswers(result.data);
});
const showAnswersRequest = useRequest(
async () => {
const result = await new CourseTaskVerificationsApi().getAnswers(courseId, courseTaskId);
setAnswers(result.data);
},
{
manual: true,
onError: e => {
const error = e as AxiosError<Error>;
message.error(error.response?.data?.message || error?.message);
},
},
);

const hideAnswers = () => {
setAnswers(null);
};

return { loading, answers, showAnswers, hideAnswers };
return {
loading: showAnswersRequest.loading,
answers,
showAnswers: showAnswersRequest.runAsync,
hideAnswers,
};
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CoursesInterviewsApi, InterviewFeedbackDto, ProfileCourseDto } from '@client/api';
import { FeedbackStep, Feedback } from '@client/data/interviews/technical-screening';
import { createContext, PropsWithChildren, useCallback, useMemo, useState } from 'react';
import { useLoading } from '@client/components/useLoading';
import { useRequest } from 'ahooks';
import { message } from 'antd';
import { useRouter } from 'next/router';
import {
Expand Down Expand Up @@ -35,10 +35,6 @@ export const StepContext = createContext<StepApi>({} as StepApi);
export function StepContextProvider(props: PropsWithChildren<ContextProps>) {
const { interviewFeedback, children, course, interviewId, type, interviewMaxScore } = props;
const router = useRouter();
const [loading, withLoading] = useLoading(false, error => {
message.error('An unexpected error occurred. Please try later.');
throw error;
});

const [feedback, setFeedback] = useState<Feedback>(() =>
getFeedbackFromTemplate(interviewFeedback, interviewMaxScore),
Expand All @@ -51,28 +47,37 @@ export function StepContextProvider(props: PropsWithChildren<ContextProps>) {
);
const isFinalStep = activeStepIndex === feedback.steps.length - 1 || isFinished;

const saveFeedback = withLoading(async (values: InterviewFeedbackValues) => {
const { feedbackValues, steps, isCompleted, score, decision, isGoodCandidate } = getUpdatedFeedback({
feedback,
newValues: values,
activeStepIndex,
interviewMaxScore,
});
await new CoursesInterviewsApi().createInterviewFeedback(course.id, interviewId, type, {
isCompleted,
score,
decision,
isGoodCandidate,
json: feedbackValues,
version: feedback.version,
});
const saveFeedbackRequest = useRequest(
async (values: InterviewFeedbackValues) => {
const { feedbackValues, steps, isCompleted, score, decision, isGoodCandidate } = getUpdatedFeedback({
feedback,
newValues: values,
activeStepIndex,
interviewMaxScore,
});
await new CoursesInterviewsApi().createInterviewFeedback(course.id, interviewId, type, {
isCompleted,
score,
decision,
isGoodCandidate,
json: feedbackValues,
version: feedback.version,
});

setFeedback({
isCompleted,
steps,
version: feedback.version,
});
});
setFeedback({
isCompleted,
steps,
version: feedback.version,
});
},
{
manual: true,
onError: error => {
message.error('An unexpected error occurred. Please try later.');
throw error;
},
},
);

const onValuesChange = useCallback(
(_: Partial<InterviewFeedbackValues>, values: InterviewFeedbackValues) => {
Expand All @@ -86,7 +91,7 @@ export function StepContextProvider(props: PropsWithChildren<ContextProps>) {
const next = useCallback(
async (values: InterviewFeedbackValues) => {
try {
await saveFeedback(values);
await saveFeedbackRequest.runAsync(values);
} catch {
return;
}
Expand Down Expand Up @@ -123,10 +128,10 @@ export function StepContextProvider(props: PropsWithChildren<ContextProps>) {
next,
prev,
onValuesChange,
loading,
loading: saveFeedbackRequest.loading,
isFinalStep,
}),
[activeStepIndex, feedback.steps, isFinalStep, loading, onValuesChange],
[activeStepIndex, feedback.steps, isFinalStep, onValuesChange, saveFeedbackRequest.loading],
);

return <StepContext.Provider value={api}>{children}</StepContext.Provider>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Button, message } from 'antd';
import { useLoading } from '@client/components/useLoading';
import { useRequest } from 'ahooks';
import { MentorsApi } from '@client/api';
import { AxiosError } from 'axios';
import { EyeOutlined } from '@ant-design/icons';
Expand All @@ -11,22 +11,32 @@ interface Props {
}

function ReviewRandomTask({ mentorId, courseId, onClick }: Props) {
const [loading, withLoading] = useLoading(false, e => {
const error = e as AxiosError;
const reviewRandomTaskRequest = useRequest(
async () => {
const service = new MentorsApi();
await service.getRandomTask(mentorId, courseId);
onClick();
},
{
manual: true,
onError: e => {
const error = e as AxiosError;

if (error.response?.status === 404) {
message.info('Task for review was not found. Please try later.');
}
});

const handleClick = withLoading(async () => {
const service = new MentorsApi();
await service.getRandomTask(mentorId, courseId);
onClick();
});
if (error.response?.status === 404) {
message.info('Task for review was not found. Please try later.');
}
},
},
);

return (
<Button type="primary" icon={<EyeOutlined />} loading={loading} disabled={loading} onClick={handleClick}>
<Button
type="primary"
icon={<EyeOutlined />}
loading={reviewRandomTaskRequest.loading}
disabled={reviewRandomTaskRequest.loading}
onClick={reviewRandomTaskRequest.runAsync}
>
Review random task
</Button>
);
Expand Down
108 changes: 73 additions & 35 deletions client/src/modules/Mentor/pages/InterviewWaitingList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button, Table } from 'antd';
import { Button, message, Table } from 'antd';
import { PageLayout } from '@client/shared/components/PageLayout';
import {
getColumnSearchProps,
Expand All @@ -9,9 +9,7 @@ import {
PersonCell,
dateRenderer,
} from '@client/shared/components/Table';
import { useLoading } from '@client/components/useLoading';
import { useMemo, useState, useContext } from 'react';
import { useAsync } from 'react-use';
import { CourseService } from '@client/services/course';
import { CoursePageProps } from '@client/services/models';
import { isCourseManager, isMentor } from '@client/domain/user';
Expand All @@ -34,48 +32,88 @@ export function InterviewWaitingList() {
const courseId = course.id;
const router = useRouter();
const interviewId = Number(router.query.interviewId);
const isInterviewReady = router.isReady && Number.isFinite(interviewId);
const isPowerUser = useMemo(() => isCourseManager(session, courseId), [session, courseId]);
const [loading, withLoading] = useLoading(false);
const [availableStudents, setAvailableStudents] = useState<AvailableStudentDto[]>([]);
const courseService = useMemo(() => new CourseService(courseId), [courseId]);

const { data: interview } = useRequest(async () => {
const { data } = await api.getInterview(interviewId, courseId);
const isStage = data.type === TaskDtoTypeEnum.StageInterview;
if (!isStage && dayjs(data.startDate).isAfter(dayjs())) {
router.push(`/403`);
}
return data;
});
const interviewRequest = useRequest(
async () => {
const { data } = await api.getInterview(interviewId, courseId);
const isStage = data.type === TaskDtoTypeEnum.StageInterview;
if (!isStage && dayjs(data.startDate).isAfter(dayjs())) {
router.push(`/403`);
}
return data;
},
{ ready: isInterviewReady },
);
const interview = interviewRequest.data;

const isStageInterview = interview?.type === TaskDtoTypeEnum.StageInterview;

useAsync(
withLoading(async () => {
const availableStudentsRequest = useRequest(
async () => {
const { data } = await api.getAvailableStudents(courseId, interviewId);
setAvailableStudents(data);
}),
[],
},
{
ready: isInterviewReady,
refreshDeps: [courseId, interviewId],
onError: () => {
message.error('An unexpected error occurred. Please try later.');
},
},
);

const inviteStudent = withLoading(async (githubId: string) => {
if (isStageInterview) {
await courseService.createInterview(githubId, session.githubId);
} else {
await courseService.addInterviewPair(`${interviewId}`, session.githubId, githubId);
}
removeStudentFromList(githubId);
});
const inviteStudentRequest = useRequest(
async (githubId: string) => {
if (isStageInterview) {
await courseService.createInterview(githubId, session.githubId);
} else {
await courseService.addInterviewPair(`${interviewId}`, session.githubId, githubId);
}
removeStudentFromList(githubId);
},
{
manual: true,
onError: () => {
message.error('An unexpected error occurred. Please try later.');
},
},
);

const assignStudentToMentor = withLoading(async (studentId: string) => {
await courseService.updateStudent(studentId, { mentorGithuId: session.githubId });
removeStudentFromList(studentId);
});
const assignStudentToMentorRequest = useRequest(
async (studentId: string) => {
await courseService.updateStudent(studentId, { mentorGithuId: session.githubId });
removeStudentFromList(studentId);
},
{
manual: true,
onError: () => {
message.error('An unexpected error occurred. Please try later.');
},
},
);

const removeFromListRequest = useRequest(
async (githubId: string) => {
await courseService.updateMentoringAvailability(githubId, false);
removeStudentFromList(githubId);
},
{
manual: true,
onError: () => {
message.error('An unexpected error occurred. Please try later.');
},
},
);

const removeFromList = withLoading(async (githubId: string) => {
await courseService.updateMentoringAvailability(githubId, false);
removeStudentFromList(githubId);
});
const loading =
availableStudentsRequest.loading ||
inviteStudentRequest.loading ||
assignStudentToMentorRequest.loading ||
removeFromListRequest.loading;

return (
<PageLayout loading={loading} title={`${interview?.name.trim()}: Wait list`} showCourseName>
Expand Down Expand Up @@ -152,7 +190,7 @@ export function InterviewWaitingList() {
</>
}
okText="Yes"
onConfirm={() => inviteStudent(record.githubId)}
onConfirm={() => inviteStudentRequest.runAsync(record.githubId)}
>
<Button type="link">Want to interview</Button>
</CustomPopconfirm>
Expand All @@ -164,7 +202,7 @@ export function InterviewWaitingList() {
</>
}
okText="Yes"
onConfirm={() => assignStudentToMentor(record.githubId)}
onConfirm={() => assignStudentToMentorRequest.runAsync(record.githubId)}
>
<Button type="link">Assign student to me</Button>
</CustomPopconfirm>
Expand All @@ -173,7 +211,7 @@ export function InterviewWaitingList() {
<CustomPopconfirm
title={<>Are you sure to remove {record.githubId} from the wait list?</>}
okText="Yes"
onConfirm={() => removeFromList(record.githubId)}
onConfirm={() => removeFromListRequest.runAsync(record.githubId)}
>
<Button type="link">Remove from list</Button>
</CustomPopconfirm>
Expand Down
Loading
Loading