Skip to content

Commit c2ff542

Browse files
committed
chore(tidy-code/UI): Add breadcrumb utility
Signed-off-by: SeeuSim <[email protected]>
1 parent 76e68bb commit c2ff542

File tree

6 files changed

+135
-116
lines changed

6 files changed

+135
-116
lines changed

frontend/src/components/blocks/authed/main-layout.tsx

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,8 @@
1-
import { Fragment, useCallback, useEffect, useState } from 'react';
2-
import { Link, Outlet } from 'react-router-dom';
1+
import { Outlet } from 'react-router-dom';
32

4-
import { getBreadCrumbs } from '@/lib/routes';
53
import { cn } from '@/lib/utils';
6-
import { useRouterLocation } from '@/lib/hooks';
7-
import {
8-
Breadcrumb,
9-
BreadcrumbItem,
10-
BreadcrumbLink,
11-
BreadcrumbList,
12-
BreadcrumbSeparator,
13-
} from '@/components/ui/breadcrumb';
14-
import { BreadCrumb, BreadCrumbProvider } from '@/stores/breadcrumb-store';
154

165
export const AuthedLayout = () => {
17-
const [breadcrumbs, setCrumbs] = useState<Array<BreadCrumb>>([]);
18-
const {
19-
location: { pathname },
20-
} = useRouterLocation();
21-
22-
useEffect(() => {
23-
setCrumbs(getBreadCrumbs(pathname));
24-
}, [pathname]);
25-
const isLast = useCallback((index: number) => index === breadcrumbs.length - 1, [breadcrumbs]);
26-
276
return (
287
<div
298
id='main'
@@ -32,36 +11,6 @@ export const AuthedLayout = () => {
3211
'h-[calc(100dvh-64px)]' // The nav is 64px
3312
)}
3413
>
35-
<BreadCrumbProvider
36-
value={{
37-
breadcrumbs,
38-
setCrumbs,
39-
}}
40-
>
41-
{breadcrumbs.length > 0 && (
42-
<div className='bg-secondary/50 flex w-full p-4 px-6'>
43-
<Breadcrumb>
44-
<BreadcrumbList>
45-
{breadcrumbs.map(({ path, title }, index) => (
46-
<Fragment key={index}>
47-
<BreadcrumbItem>
48-
<BreadcrumbLink asChild>
49-
<Link
50-
to={path}
51-
className={cn(isLast(index) && 'text-secondary-foreground')}
52-
>
53-
{title}
54-
</Link>
55-
</BreadcrumbLink>
56-
</BreadcrumbItem>
57-
{!isLast(index) && <BreadcrumbSeparator />}
58-
</Fragment>
59-
))}
60-
</BreadcrumbList>
61-
</Breadcrumb>
62-
</div>
63-
)}
64-
</BreadCrumbProvider>
6514
<Outlet />
6615
</div>
6716
);
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import {
2+
Breadcrumb,
3+
BreadcrumbItem,
4+
BreadcrumbLink,
5+
BreadcrumbList,
6+
BreadcrumbSeparator,
7+
} from '@/components/ui/breadcrumb';
8+
import { cn } from '@/lib/utils';
9+
import { BreadCrumb } from '@/stores/breadcrumb-store';
10+
import { FC, Fragment, PropsWithChildren } from 'react';
11+
import { Link } from 'react-router-dom';
12+
13+
type IBreadCrumbBannerProps = {
14+
crumbs: Array<BreadCrumb>;
15+
};
16+
17+
export const WithNavBanner: FC<PropsWithChildren<IBreadCrumbBannerProps>> = ({
18+
children,
19+
crumbs,
20+
}) => {
21+
const isLast = (index: number) => index === crumbs.length - 1;
22+
return (
23+
<>
24+
<div className='bg-secondary/50 flex w-full p-4 px-6'>
25+
<Breadcrumb>
26+
<BreadcrumbList>
27+
{crumbs.map(({ path, title }, index) => (
28+
<Fragment key={index}>
29+
<BreadcrumbItem>
30+
<BreadcrumbLink asChild>
31+
<Link to={path} className={cn(isLast(index) && 'text-secondary-foreground')}>
32+
{title}
33+
</Link>
34+
</BreadcrumbLink>
35+
</BreadcrumbItem>
36+
{!isLast(index) && <BreadcrumbSeparator />}
37+
</Fragment>
38+
))}
39+
</BreadcrumbList>
40+
</Breadcrumb>
41+
</div>
42+
{children}
43+
</>
44+
);
45+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { BreadCrumb } from '@/stores/breadcrumb-store';
2+
import { getBreadCrumbs } from '../routes';
3+
import { useRouterLocation } from './use-router-location';
4+
5+
export const useCrumbs = (...extraCrumbs: Array<BreadCrumb>) => {
6+
const {
7+
location: { pathname },
8+
} = useRouterLocation();
9+
const crumbs = getBreadCrumbs(pathname);
10+
return {
11+
crumbs: [...crumbs, ...extraCrumbs].map((v) => ({
12+
...v,
13+
path: v.path.replace('<CURRENT>', pathname),
14+
})),
15+
};
16+
};

frontend/src/lib/routes.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import type { BreadCrumb } from '@/stores/breadcrumb-store';
22

33
export const ROUTES = {
4-
HOME: '/',
54
LOGIN: '/login',
65
SIGNUP: '/signup',
76
FORGOT_PASSWORD: '/forgot-password',
7+
8+
HOME: '/',
89
QUESTIONS: '/questions',
910
QUESTION_DETAILS: '/questions/:questionId',
1011
};
@@ -39,6 +40,8 @@ const TITLES: Record<string, string> = {
3940
[ROUTES.LOGIN]: 'Start Interviewing Today',
4041
[ROUTES.SIGNUP]: 'Create an Account',
4142
[ROUTES.FORGOT_PASSWORD]: 'Forgot Password',
43+
[ROUTES.HOME]: 'Peerprep',
44+
[ROUTES.QUESTIONS]: 'Browse Questions',
4245
};
4346

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

frontend/src/routes/questions/details.tsx

Lines changed: 60 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import rehypeKatex from 'rehype-katex';
55
import remarkGfm from 'remark-gfm';
66
import remarkMath from 'remark-math';
77

8+
import { WithNavBanner } from '@/components/blocks/authed/with-nav-banner';
89
import { Badge } from '@/components/ui/badge';
910
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
1011
import { ScrollArea } from '@/components/ui/scroll-area';
1112
import { Separator } from '@/components/ui/separator';
13+
14+
import { useCrumbs } from '@/lib/hooks/use-crumbs';
15+
import { usePageTitle } from '@/lib/hooks/use-page-title';
1216
import { getQuestionDetails } from '@/services/question-service';
13-
import { useBreadCrumbs } from '@/stores/breadcrumb-store';
14-
import { useEffect } from 'react';
1517

1618
const questionDetailsQuery = (id: number) =>
1719
queryOptions({
@@ -30,68 +32,66 @@ export const loader =
3032
export const QuestionDetails = () => {
3133
const { questionId } = useLoaderData() as Awaited<ReturnType<ReturnType<typeof loader>>>;
3234
const { data: details } = useSuspenseQuery(questionDetailsQuery(questionId));
33-
const { setCrumbs } = useBreadCrumbs();
34-
useEffect(() => {
35-
if (details && setCrumbs) {
36-
setCrumbs((c) => [...c, { path: '', title: `${questionId}. ${details.title}` }]);
37-
}
38-
}, []);
35+
const { crumbs } = useCrumbs({ path: '<CURRENT>', title: `${questionId}. ${details.title}` });
36+
usePageTitle(details.title);
3937

4038
return (
41-
<div className='flex flex-1 overflow-hidden'>
42-
<Card className='border-border m-4 w-1/3 max-w-[500px] overflow-hidden p-4 md:w-2/5'>
43-
<ScrollArea className='h-full'>
44-
<CardHeader>
45-
<div className='flex flex-col gap-4'>
46-
<div className='flex w-full items-center gap-4'>
47-
<CardTitle className='text-2xl'>
48-
{details.id}.&nbsp;{details.title}
49-
</CardTitle>
50-
</div>
51-
<div className='flex flex-wrap items-center gap-1'>
52-
<Badge variant='secondary' className='flex w-min grow-0'>
53-
{details.difficulty}
54-
</Badge>
55-
<Separator orientation='vertical' className='mx-2 h-4' />
56-
<span className='text-sm font-medium'>Topics:</span>
57-
{details.topics.map((v, i) => (
58-
<Badge
59-
variant='secondary'
60-
className='flex w-min grow-0 whitespace-nowrap'
61-
key={i}
62-
>
63-
{v}
39+
<WithNavBanner crumbs={[...crumbs]}>
40+
<div className='flex flex-1 overflow-hidden'>
41+
<Card className='border-border m-4 w-1/3 max-w-[500px] overflow-hidden p-4 md:w-2/5'>
42+
<ScrollArea className='h-full'>
43+
<CardHeader>
44+
<div className='flex flex-col gap-4'>
45+
<div className='flex w-full items-center gap-4'>
46+
<CardTitle className='text-2xl'>
47+
{details.id}.&nbsp;{details.title}
48+
</CardTitle>
49+
</div>
50+
<div className='flex flex-wrap items-center gap-1'>
51+
<Badge variant='secondary' className='flex w-min grow-0'>
52+
{details.difficulty}
6453
</Badge>
65-
))}
54+
<Separator orientation='vertical' className='mx-2 h-4' />
55+
<span className='text-sm font-medium'>Topics:</span>
56+
{details.topics.map((v, i) => (
57+
<Badge
58+
variant='secondary'
59+
className='flex w-min grow-0 whitespace-nowrap'
60+
key={i}
61+
>
62+
{v}
63+
</Badge>
64+
))}
65+
</div>
6666
</div>
67-
</div>
68-
</CardHeader>
69-
<CardContent>
70-
<Markdown
71-
rehypePlugins={[rehypeKatex]}
72-
remarkPlugins={[remarkMath, remarkGfm]}
73-
className='prose prose-neutral text-card-foreground prose-strong:text-card-foreground leading-normal'
74-
components={{
75-
code: ({ children, className, ...rest }) => {
76-
// const isCodeBlock = /language-(\w+)/.exec(className || '');
67+
</CardHeader>
68+
<CardContent>
69+
<Markdown
70+
rehypePlugins={[rehypeKatex]}
71+
remarkPlugins={[remarkMath, remarkGfm]}
72+
className='prose prose-neutral text-card-foreground prose-strong:text-card-foreground leading-normal'
73+
components={{
74+
code: ({ children, className, ...rest }) => {
75+
// const isCodeBlock = /language-(\w+)/.exec(className || '');
7776

78-
return (
79-
<code
80-
{...rest}
81-
className='bg-secondary text-secondary-foreground rounded px-1.5 py-1 font-mono'
82-
>
83-
{children}
84-
</code>
85-
);
86-
},
87-
}}
88-
>
89-
{details.description}
90-
</Markdown>
91-
</CardContent>
92-
</ScrollArea>
93-
</Card>
94-
<div className='flex flex-1 flex-col' />
95-
</div>
77+
return (
78+
<code
79+
{...rest}
80+
className='bg-secondary text-secondary-foreground rounded px-1.5 py-1 font-mono'
81+
>
82+
{children}
83+
</code>
84+
);
85+
},
86+
}}
87+
>
88+
{details.description}
89+
</Markdown>
90+
</CardContent>
91+
</ScrollArea>
92+
</Card>
93+
<div className='flex flex-1 flex-col' />
94+
</div>
95+
</WithNavBanner>
9696
);
9797
};
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
import { WithNavBanner } from '@/components/blocks/authed/with-nav-banner';
2+
import { useCrumbs } from '@/lib/hooks/use-crumbs';
3+
14
export const QuestionsList = () => {
5+
const { crumbs } = useCrumbs();
26
return (
3-
<div>
4-
<span>Questions Page</span>
5-
</div>
7+
<WithNavBanner crumbs={crumbs}>
8+
<div>
9+
<span>Questions Page</span>
10+
</div>
11+
</WithNavBanner>
612
);
713
};

0 commit comments

Comments
 (0)