Skip to content

Commit b7be5d8

Browse files
committed
PEER-228,229: Add navigation
Signed-off-by: SeeuSim <[email protected]>
1 parent e5df979 commit b7be5d8

File tree

7 files changed

+125
-17
lines changed

7 files changed

+125
-17
lines changed

backend/question/src/controller/attempted-controller.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,17 @@ interface AddAttemptRequestBody {
99
userId2?: string; // Optional if userId2 is not always required
1010
code: string;
1111
language: string;
12-
topic: string[]; // Assuming topic is an array of strings
13-
difficulty: string;
1412
}
1513

1614
// Controller function to handle creating an attempt
1715
export const createAttempt = async (
1816
req: Request<unknown, unknown, AddAttemptRequestBody>,
1917
res: Response
2018
) => {
21-
const { questionId, userId1, userId2, code, language, topic, difficulty } = req.body;
19+
const { questionId, userId1, userId2, code, language } = req.body;
2220

2321
// Basic validation for required fields
24-
if (!questionId || !userId1 || !code || !language || !topic || !difficulty) {
22+
if (!questionId || !userId1 || !code || !language) {
2523
return res.status(400).json({ error: 'Missing required fields' });
2624
}
2725

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const COMPLETION_STATES = {
2+
PENDING: 'pending',
3+
SUCCESS: 'success',
4+
ERROR: 'error',
5+
EMPTY: '',
6+
};

frontend/src/components/blocks/interview/editor.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,19 @@ export const Editor = ({ questionId, room, onAIClick, onPartnerClick }: EditorPr
5151
return getTheme(theme);
5252
}, [theme]);
5353

54-
const [isOtherUserCompletingDialogOpen, setIsOtherUserCompletingDialogOpen] = useState('');
54+
const [completionState, setCompletionState] = useState('');
5555

5656
useEffect(() => {
5757
if (isCompleting.userId !== userId && isCompleting.state) {
58-
setIsOtherUserCompletingDialogOpen(isCompleting.state);
58+
setCompletionState(isCompleting.state);
5959
} else {
60-
setIsOtherUserCompletingDialogOpen('');
60+
setCompletionState('');
6161
}
6262
}, [isCompleting]);
6363

6464
return (
6565
<div className='flex w-full flex-col gap-4 p-4'>
66-
{isOtherUserCompletingDialogOpen && <OtherUserCompletingDialog />}
66+
{completionState && <OtherUserCompletingDialog status={completionState} />}
6767
{isLoading ? (
6868
<div className='flex h-[60px] w-full flex-row justify-between pt-3'>
6969
<div className='flex h-10 flex-row gap-4'>
@@ -126,7 +126,16 @@ export const Editor = ({ questionId, room, onAIClick, onPartnerClick }: EditorPr
126126
))}
127127
</div>
128128
</div>
129-
<CompleteDialog {...{ setCompleting: setIsCompleting, questionId, code, members }}>
129+
<CompleteDialog
130+
{...{
131+
setCompleting: setIsCompleting,
132+
userId: userId as string,
133+
questionId,
134+
code,
135+
members,
136+
language,
137+
}}
138+
>
130139
<Button size='sm' variant='destructive' disabled={!code} className='mx-4'>
131140
Complete question
132141
</Button>

frontend/src/components/blocks/interview/room/complete-dialog.tsx

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { useMutation } from '@tanstack/react-query';
2+
import { Loader2 } from 'lucide-react';
23
import { FC, PropsWithChildren, useCallback, useState } from 'react';
4+
import { useNavigate } from 'react-router-dom';
35

46
import { Button } from '@/components/ui/button';
57
import {
@@ -9,19 +11,31 @@ import {
911
DialogHeader,
1012
DialogTrigger,
1113
} from '@/components/ui/dialog';
14+
import { addQuestionAttempt } from '@/services/question-service';
1215
import { IYjsUserState } from '@/types/collab-types';
1316

17+
import { COMPLETION_STATES } from '../constants';
18+
1419
type CompleteDialogProps = {
20+
userId: string;
1521
questionId: number;
1622
code: string;
23+
language: string;
1724
members: Array<IYjsUserState['user']>;
1825
setCompleting: (state: string, resetId?: boolean) => void;
1926
};
2027

2128
export const CompleteDialog: FC<PropsWithChildren<CompleteDialogProps>> = ({
2229
children,
2330
setCompleting,
31+
questionId,
32+
userId,
33+
code,
34+
language,
35+
members,
2436
}) => {
37+
const navigate = useNavigate();
38+
2539
const [isOpen, _setIsOpen] = useState(false);
2640
const setIsOpen = useCallback(
2741
(openState: boolean) => {
@@ -36,13 +50,28 @@ export const CompleteDialog: FC<PropsWithChildren<CompleteDialogProps>> = ({
3650
[isOpen]
3751
);
3852

39-
const { mutate: _m } = useMutation({
40-
mutationFn: async () => {},
53+
const { mutate: sendCompleteRequest, isPending } = useMutation({
54+
mutationFn: async () => {
55+
return await addQuestionAttempt({
56+
questionId,
57+
code,
58+
language,
59+
userId1: userId,
60+
userId2:
61+
members.length < 2 ? undefined : members.filter((v) => v.userId !== userId)[0].userId,
62+
});
63+
},
4164
onSuccess: () => {
42-
setCompleting('success');
65+
setCompleting(COMPLETION_STATES.SUCCESS);
4366
// Navigate to home page
67+
setTimeout(() => {
68+
setCompleting(COMPLETION_STATES.EMPTY, true);
69+
navigate('/');
70+
}, 200);
71+
},
72+
onError: () => {
73+
setCompleting(COMPLETION_STATES.ERROR);
4474
},
45-
onError: () => {},
4675
});
4776

4877
return (
@@ -62,7 +91,14 @@ export const CompleteDialog: FC<PropsWithChildren<CompleteDialogProps>> = ({
6291
>
6392
Go Back
6493
</Button>
65-
<Button>Complete Question</Button>
94+
<Button
95+
disabled={isPending}
96+
onClick={() => sendCompleteRequest()}
97+
className='flex flex-row items-center gap-2'
98+
>
99+
<span>Complete Question</span>
100+
{isPending && <Loader2 className='animate-spin' />}
101+
</Button>
66102
</div>
67103
</DialogFooter>
68104
</DialogContent>

frontend/src/components/blocks/interview/room/other-user-completing-dialog.tsx

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,38 @@
1+
import { FC, useEffect } from 'react';
2+
import { useNavigate } from 'react-router-dom';
3+
14
import { Dialog, DialogContent, DialogHeader } from '@/components/ui/dialog';
25

3-
export const OtherUserCompletingDialog = () => {
6+
import { COMPLETION_STATES } from '../constants';
7+
8+
type OtherUserCompletingDialogProps = {
9+
status: string;
10+
};
11+
12+
export const OtherUserCompletingDialog: FC<OtherUserCompletingDialogProps> = ({ status }) => {
13+
const navigate = useNavigate();
14+
15+
useEffect(() => {
16+
if (status === COMPLETION_STATES.SUCCESS) {
17+
setTimeout(() => {
18+
navigate('/');
19+
}, 200);
20+
}
21+
}, [status]);
22+
423
return (
524
<Dialog open>
625
<DialogContent className='text-primary border-border'>
726
<DialogHeader className='text-lg font-medium'>
8-
The other user is marking this question attempt as complete. Please wait...
27+
{status === COMPLETION_STATES.PENDING
28+
? 'The other user is marking this question attempt as complete. Please wait...'
29+
: status === COMPLETION_STATES.SUCCESS
30+
? 'Question marked as completed. Navigating to home page...'
31+
: 'An Error occurred.'}
932
</DialogHeader>
10-
<div className='bg-background absolute right-3 top-3 z-50 size-6' />
33+
{status !== COMPLETION_STATES.ERROR && ( // Block exit if not error
34+
<div className='bg-background absolute right-3 top-3 z-50 size-6' />
35+
)}
1136
</DialogContent>
1237
</Dialog>
1338
);

frontend/src/services/question-service.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import type {
33
IGetQuestionDetailsResponse,
44
IGetQuestionsResponse,
55
IGetTopicsResponse,
6+
IPostAddQuestionAttemptParams,
7+
IPostAddQuestionAttemptResponse,
68
} from '@/types/question-types';
79

810
import { questionApiClient } from './api-clients';
@@ -12,6 +14,7 @@ const QUESTION_SERVICE_ROUTES = {
1214
GET_QUESTION_DETAILS: '/questions/<questionId>',
1315
GET_TOPICS: '/questions/topics',
1416
GET_DIFFICULTIES: '/questions/difficulties',
17+
POST_ADD_ATTEMPT: '/questions/newAttempt',
1518
};
1619

1720
export const getQuestionDetails = (questionId: number): Promise<IGetQuestionDetailsResponse> => {
@@ -57,3 +60,17 @@ export const fetchDifficulties = (): Promise<IGetDifficultiesResponse> => {
5760
return res.data as IGetDifficultiesResponse;
5861
});
5962
};
63+
64+
export const addQuestionAttempt = (
65+
params: IPostAddQuestionAttemptParams
66+
): Promise<IPostAddQuestionAttemptResponse> => {
67+
return questionApiClient
68+
.post(QUESTION_SERVICE_ROUTES.POST_ADD_ATTEMPT, params, {
69+
headers: {
70+
'Content-Type': 'application/json',
71+
},
72+
})
73+
.then((res) => {
74+
return res.data as IPostAddQuestionAttemptResponse;
75+
});
76+
};

frontend/src/types/question-types.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,20 @@ export type IGetDifficultiesResponse = {
3030
export type IGetQuestionDetailsResponse = {
3131
question: QuestionDetails;
3232
};
33+
34+
export type IPostAddQuestionAttemptParams = {
35+
questionId: number;
36+
userId1: string;
37+
userId2?: string; // Optional if userId2 is not always required
38+
code: string;
39+
language: string;
40+
};
41+
42+
export type IPostAddQuestionAttemptResponse =
43+
| {
44+
message: string;
45+
}
46+
| {
47+
error: string;
48+
details: string;
49+
};

0 commit comments

Comments
 (0)