Skip to content

Commit 9a07b50

Browse files
committed
PEER-234: Add nav blocking for editor
Signed-off-by: SeeuSim <[email protected]>
1 parent 90205e8 commit 9a07b50

File tree

10 files changed

+104
-59
lines changed

10 files changed

+104
-59
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
export * from './main-layout';
2+
export * from './with-nav-banner';
3+
export * from './with-nav-blocker';
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { FC, PropsWithChildren } from 'react';
2+
import { useBlocker } from 'react-router-dom';
3+
4+
import { Button } from '@/components/ui/button';
5+
import { Dialog, DialogContent } from '@/components/ui/dialog';
6+
7+
export const WithNavBlocker: FC<PropsWithChildren> = ({ children }) => {
8+
const blocker = useBlocker(
9+
({ currentLocation, nextLocation }) => currentLocation.pathname !== nextLocation.pathname
10+
);
11+
return (
12+
<>
13+
{blocker.state === 'blocked' && (
14+
<Dialog modal open>
15+
<DialogContent className='text-primary border-secondary-foreground/40 flex flex-col gap-8'>
16+
<h1 className='text-lg font-medium'>
17+
Are you sure you want to navigate away from this page?
18+
</h1>
19+
<div className='flex flex-row justify-between'>
20+
<Button onClick={blocker.reset}>
21+
<span>Cancel</span>
22+
</Button>
23+
<Button variant='destructive' onClick={blocker.proceed}>
24+
<span>Leave Page</span>
25+
</Button>
26+
</div>
27+
<div
28+
id='blockDialogClose'
29+
className='bg-background absolute right-4 top-4 z-50 size-4'
30+
/>
31+
</DialogContent>
32+
</Dialog>
33+
)}
34+
{children}
35+
</>
36+
);
37+
};

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

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ export const Editor = ({ room }: EditorProps) => {
3636
return (
3737
<div className='flex flex-col gap-4 p-4'>
3838
{isLoading ? (
39-
<div className='flex h-16 w-full flex-row justify-between pt-4'>
39+
<div className='flex h-[60px] w-full flex-row justify-between pt-3'>
4040
<div className='flex h-10 flex-row gap-4'>
4141
<Skeleton className='h-10 w-16' />
4242
<Skeleton className='h-10 w-32' />
4343
</div>
4444
<div className='flex flex-row items-center gap-2'>
4545
<Skeleton className='size-10 rounded-full' />
46-
<Skeleton className='h-8 w-24' />
46+
<Skeleton className='h-8 w-24 rounded-sm' />
4747
</div>
4848
</div>
4949
) : (
@@ -52,10 +52,10 @@ export const Editor = ({ room }: EditorProps) => {
5252
<div className='flex flex-col gap-2'>
5353
<Label>Language</Label>
5454
<Select value={language} onValueChange={(val) => setLanguage(val as LanguageName)}>
55-
<SelectTrigger className='max-w-[150px]'>
55+
<SelectTrigger className='focus-visible:ring-secondary-foreground/60 max-w-[150px]'>
5656
<SelectValue />
5757
</SelectTrigger>
58-
<SelectContent>
58+
<SelectContent className='border-secondary-foreground/30'>
5959
{languages.map((lang, idx) => (
6060
<SelectItem value={lang} key={idx}>
6161
{lang}
@@ -67,10 +67,10 @@ export const Editor = ({ room }: EditorProps) => {
6767
<div className='flex flex-col gap-2'>
6868
<Label>Theme</Label>
6969
<Select value={theme} onValueChange={(val) => setTheme(val as IEditorTheme)}>
70-
<SelectTrigger className='max-w-[150px]'>
70+
<SelectTrigger className='focus-visible:ring-secondary-foreground/60 max-w-[150px]'>
7171
<SelectValue />
7272
</SelectTrigger>
73-
<SelectContent>
73+
<SelectContent className='border-secondary-foreground/30'>
7474
{themeOptions.map((theme, idx) => (
7575
<SelectItem value={theme} key={idx}>
7676
{theme}
@@ -85,7 +85,7 @@ export const Editor = ({ room }: EditorProps) => {
8585
{/* TODO: Get user avatar and display */}
8686
{members.map((member, index) => (
8787
<div
88-
className='grid size-8 place-items-center !overflow-clip rounded-full border p-1 text-xs'
88+
className='grid size-8 place-items-center !overflow-clip rounded-full border-2 p-1 text-xs'
8989
style={{
9090
borderColor: member.color,
9191
}}

frontend/src/lib/hooks/use-collab.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export const useCollab = (roomId: string) => {
108108
});
109109
_setLanguage((yState.get('language') as LanguageName) ?? 'python');
110110
setSharedDocRef(yState);
111-
setTimeout(() => setIsLoading(false), 100); // Flush to next render
111+
setTimeout(() => setIsLoading(false), 300);
112112

113113
return () => {
114114
doc.destroy();

frontend/src/lib/routes.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ export const ROUTES = {
77
QUESTIONS: '/questions',
88
QUESTION_DETAILS: '/questions/:questionId',
99

10-
INTERVIEW: '/interview/:roomId',
1110
MATCH: '/match',
12-
COLLAB: '/collab/:roomId',
11+
INTERVIEW: '/interview/:roomId',
1312
};
1413

1514
const TOP_LEVEL_AUTHED_ROUTES = {
@@ -25,10 +24,10 @@ const TOP_LEVEL_AUTHED_ROUTES = {
2524
title: 'Start Match',
2625
},
2726
],
28-
[ROUTES.COLLAB]: [
27+
[ROUTES.INTERVIEW.replace(':roomId', '')]: [
2928
{
30-
path: ROUTES.COLLAB,
31-
title: 'Collab',
29+
path: ROUTES.INTERVIEW,
30+
title: 'Interview',
3231
},
3332
],
3433
};
@@ -63,7 +62,7 @@ const TITLES: Record<string, string> = {
6362
[ROUTES.HOME]: 'Peerprep',
6463
[ROUTES.QUESTIONS]: 'Browse Questions',
6564
[ROUTES.MATCH]: 'Match With A Partner',
66-
[ROUTES.COLLAB]: 'Collaborate',
65+
[ROUTES.INTERVIEW]: 'Interview',
6766
};
6867

6968
export const getPageTitle = (path: string) => {

frontend/src/routes/interview/[room]/main.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import { QueryClient, useSuspenseQuery } from '@tanstack/react-query';
22
import { useMemo } from 'react';
33
import { type LoaderFunctionArgs, Navigate, useLoaderData } from 'react-router-dom';
44

5+
import { WithNavBanner, WithNavBlocker } from '@/components/blocks/authed';
56
import { Editor } from '@/components/blocks/interview/editor';
67
import { QuestionDetails } from '@/components/blocks/questions/details';
78
import { Card } from '@/components/ui/card';
9+
import { useCrumbs } from '@/lib/hooks';
810
import { questionDetailsQuery } from '@/lib/queries/question-details';
911
import { ROUTES } from '@/lib/routes';
1012

@@ -23,20 +25,25 @@ export const loader =
2325

2426
export const InterviewRoom = () => {
2527
const { questionId, roomId } = useLoaderData() as Awaited<ReturnType<ReturnType<typeof loader>>>;
28+
const { crumbs } = useCrumbs();
2629
const { data: details } = useSuspenseQuery(questionDetailsQuery(questionId));
2730
const questionDetails = useMemo(() => {
2831
return details.question;
2932
}, [details]);
3033
return !questionId || !roomId ? (
3134
<Navigate to={ROUTES.HOME} />
3235
) : (
33-
<div className='flex flex-1 overflow-hidden'>
34-
<Card className='border-border m-4 w-1/3 max-w-[500px] overflow-hidden p-4 md:w-2/5'>
35-
<QuestionDetails {...{ questionDetails }} />
36-
</Card>
37-
<div className='flex flex-1 flex-col overflow-hidden'>
38-
<Editor room={roomId as string} />
39-
</div>
40-
</div>
36+
<WithNavBlocker>
37+
<WithNavBanner crumbs={crumbs}>
38+
<div className='flex flex-1 overflow-hidden'>
39+
<Card className='border-border m-4 w-1/3 max-w-[500px] overflow-hidden p-4 md:w-2/5'>
40+
<QuestionDetails {...{ questionDetails }} />
41+
</Card>
42+
<div className='flex flex-1 flex-col overflow-hidden'>
43+
<Editor room={roomId as string} />
44+
</div>
45+
</div>
46+
</WithNavBanner>
47+
</WithNavBlocker>
4148
);
4249
};

frontend/src/routes/match/match-form.tsx

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -66,29 +66,29 @@ export const MatchForm = ({ topics }: MatchFormProps) => {
6666
render={({ field }) => (
6767
<FormItem className='md:w-[350px]'>
6868
<FormLabel>Topics</FormLabel>
69-
<MultiSelector
70-
values={field.value}
71-
onValuesChange={field.onChange}
72-
className='whitespace-nowrap'
73-
>
74-
<FormControl>
75-
<MultiSelectorTrigger className='bg-popover max-h-24 overflow-y-auto'>
69+
<FormControl>
70+
<MultiSelector
71+
values={field.value}
72+
onValuesChange={field.onChange}
73+
className='whitespace-nowrap'
74+
>
75+
<MultiSelectorTrigger className='bg-popover focus-within:ring-secondary-foreground/50 max-h-24 overflow-y-auto'>
7676
<MultiSelectorInput placeholder='Select topic(s)' />
7777
</MultiSelectorTrigger>
78-
</FormControl>
79-
<MultiSelectorContent>
80-
<MultiSelectorList>
81-
{topics.map((topic) => {
82-
return (
83-
<MultiSelectorItem value={topic} key={topic}>
84-
{topic}
85-
</MultiSelectorItem>
86-
);
87-
})}
88-
</MultiSelectorList>
89-
</MultiSelectorContent>
90-
<FormMessage />
91-
</MultiSelector>
78+
<MultiSelectorContent>
79+
<MultiSelectorList className='border-secondary-foreground/40 border'>
80+
{topics.map((topic) => {
81+
return (
82+
<MultiSelectorItem value={topic} key={topic}>
83+
{topic}
84+
</MultiSelectorItem>
85+
);
86+
})}
87+
</MultiSelectorList>
88+
</MultiSelectorContent>
89+
</MultiSelector>
90+
</FormControl>
91+
<FormMessage />
9292
</FormItem>
9393
)}
9494
/>
@@ -100,20 +100,20 @@ export const MatchForm = ({ topics }: MatchFormProps) => {
100100
render={({ field }) => (
101101
<FormItem className='mt-2'>
102102
<FormLabel>Difficulty</FormLabel>
103-
<Select value={field.value} onValueChange={field.onChange}>
104-
<FormControl>
105-
<SelectTrigger className='bg-popover'>
103+
<FormControl>
104+
<Select value={field.value} onValueChange={field.onChange}>
105+
<SelectTrigger className='bg-popover focus:ring-secondary-foreground/50'>
106106
<SelectValue placeholder='Select difficulty' />
107107
</SelectTrigger>
108-
</FormControl>
109-
<SelectContent>
110-
{DIFFICULTIES.map((value, index) => (
111-
<SelectItem key={index} value={value}>
112-
{value}
113-
</SelectItem>
114-
))}
115-
</SelectContent>
116-
</Select>
108+
<SelectContent className='border-secondary-foreground/40'>
109+
{DIFFICULTIES.map((value, index) => (
110+
<SelectItem key={index} value={value}>
111+
{value}
112+
</SelectItem>
113+
))}
114+
</SelectContent>
115+
</Select>
116+
</FormControl>
117117
<FormMessage />
118118
</FormItem>
119119
)}

frontend/src/routes/questions/details/main.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { QueryClient,useSuspenseQuery } from '@tanstack/react-query';
1+
import { QueryClient, useSuspenseQuery } from '@tanstack/react-query';
22
import { useMemo } from 'react';
33
import { LoaderFunctionArgs, useLoaderData } from 'react-router-dom';
44

5-
import { WithNavBanner } from '@/components/blocks/authed/with-nav-banner';
5+
import { WithNavBanner } from '@/components/blocks/authed';
66
import { QuestionDetails } from '@/components/blocks/questions/details';
77
import { Card } from '@/components/ui/card';
88
import { useCrumbs } from '@/lib/hooks/use-crumbs';

frontend/src/routes/questions/main.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { type QueryClient, queryOptions, useInfiniteQuery } from '@tanstack/reac
22
import { Suspense, useEffect, useMemo } from 'react';
33
import { Await, defer, type LoaderFunctionArgs, useLoaderData } from 'react-router-dom';
44

5-
import { WithNavBanner } from '@/components/blocks/authed/with-nav-banner';
5+
import { WithNavBanner } from '@/components/blocks/authed';
66
import { Loading } from '@/components/blocks/loading';
77
import { ScrollArea } from '@/components/ui/scroll-area';
88
import { useCrumbs } from '@/lib/hooks/use-crumbs';

frontend/src/stores/match-request-store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createContext, useContext } from 'react';
22
import { UseFormReturn } from 'react-hook-form';
33

4-
import { IRequestMatchFormSchema } from '@/routes/match/logic';
4+
import type { IRequestMatchFormSchema } from '@/routes/match/logic';
55

66
const matchRequestContext = createContext<{ form?: UseFormReturn<IRequestMatchFormSchema> }>({});
77

0 commit comments

Comments
 (0)