Skip to content

Commit d40e61c

Browse files
authored
Merge pull request #16 from CS3219-AY2425S1/PEER-249-UI-Question-Details
PEER-249 UI Question Details page
2 parents cb19e69 + 7913392 commit d40e61c

File tree

15 files changed

+5563
-6887
lines changed

15 files changed

+5563
-6887
lines changed

frontend/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
"@radix-ui/react-icons": "^1.3.0",
1818
"@radix-ui/react-label": "^2.1.0",
1919
"@radix-ui/react-navigation-menu": "^1.2.0",
20+
"@radix-ui/react-scroll-area": "^1.1.0",
21+
"@radix-ui/react-separator": "^1.1.0",
2022
"@radix-ui/react-slot": "^1.1.0",
2123
"@radix-ui/react-tabs": "^1.1.0",
2224
"@tanstack/react-query": "^5.56.2",
@@ -31,13 +33,19 @@
3133
"react": "^18.3.1",
3234
"react-dom": "^18.3.1",
3335
"react-hook-form": "^7.53.0",
36+
"react-katex": "^3.0.1",
37+
"react-markdown": "^9.0.1",
3438
"react-router-dom": "^6.26.2",
39+
"rehype-katex": "^7.0.1",
40+
"remark-gfm": "^4.0.0",
41+
"remark-math": "^6.0.0",
3542
"tailwind-merge": "^2.5.2",
3643
"tailwindcss-animate": "^1.0.7",
3744
"zod": "^3.23.8"
3845
},
3946
"devDependencies": {
4047
"@eslint/js": "^9.9.0",
48+
"@tailwindcss/typography": "^0.5.15",
4149
"@tanstack/eslint-plugin-query": "^5.56.1",
4250
"@types/node": "^22.5.5",
4351
"@types/react": "^18.3.3",

frontend/src/assets/questions.ts

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
export const questions = [
2+
{
3+
id: 1,
4+
title: 'Reverse a String',
5+
description:
6+
'Write a function that reverses a string. The input string is given as an array of characters `s`. You must do this by modifying the input array in-place with O(1) extra memory.\n\n**Example 1:**\n\nInput: `s = ["h","e","l","l","o"]`\n\nOutput: `["o","l","l","e","h"]`\n\n**Example 2:**\n\nInput: `s = ["H","a","n","n","a","h"]`\n\nOutput: `["h","a","n","n","a","H"]`\n\n**Constraints:**\n\n* `1 <= s.length <= 105`\n\n* `s[i]` is a printable ASCII character.',
7+
topics: ['Strings', 'Algorithms'],
8+
difficulty: 'Easy',
9+
leetcode: 'https://leetcode.com/problems/reverse-string/',
10+
},
11+
{
12+
id: 2,
13+
title: 'Linked List Cycle Detection',
14+
description: 'Implement a function to detect if a linked list contains a cycle.',
15+
topics: ['Data Structures', 'Algorithms'],
16+
difficulty: 'Easy',
17+
leetcode: 'https://leetcode.com/problems/linked-list-cycle/',
18+
},
19+
{
20+
id: 3,
21+
title: 'Roman to Integer',
22+
description: 'Given a Roman numeral, convert it to an integer.',
23+
topics: ['Algorithms'],
24+
difficulty: 'Easy',
25+
leetcode: 'https://leetcode.com/problems/roman-to-integer/',
26+
},
27+
{
28+
id: 4,
29+
title: 'Add Binary',
30+
description: 'Given two binary strings `a` and `b`, return their sum as a binary string.',
31+
topics: ['Bit Manipulation', 'Algorithms'],
32+
difficulty: 'Easy',
33+
leetcode: 'https://leetcode.com/problems/add-binary/',
34+
},
35+
{
36+
id: 5,
37+
title: 'Fibonacci Number',
38+
description:
39+
'The Fibonacci numbers, commonly denoted `F(n)`, form a sequence such that each number is the sum of the two preceding ones, starting from 0 and 1. That is:\n\n* `F(0) = 0`, `F(1) = 1`\n\n* `F(n) = F(n - 1) + F(n - 2)`, for `n > 1`\n\nGiven `n`, calculate `F(n)`.',
40+
topics: ['Recursion', 'Algorithms'],
41+
difficulty: 'Easy',
42+
leetcode: 'https://leetcode.com/problems/fibonacci-number/',
43+
},
44+
{
45+
id: 6,
46+
title: 'Implement Stack using Queues',
47+
description:
48+
'Implement a last-in-first-out (LIFO) stack using only two queues. The implemented stack should support all the functions of a normal stack (push, top, pop, and empty).',
49+
topics: ['Data Structures'],
50+
difficulty: 'Easy',
51+
leetcode: 'https://leetcode.com/problems/implement-stack-using-queues/',
52+
},
53+
{
54+
id: 7,
55+
title: 'Combine Two Tables',
56+
description:
57+
'Given table `Person` with columns `personId`, `lastName`, and `firstName`, and table `Address` with columns `addressId`, `personId`, `city`, and `state`, write a solution to report the first name, last name, city, and state of each person in the `Person` table. If the address of a `personId` is not present in the `Address` table, report `null` instead.',
58+
topics: ['Databases'],
59+
difficulty: 'Easy',
60+
leetcode: 'https://leetcode.com/problems/combine-two-tables/',
61+
},
62+
{
63+
id: 8,
64+
title: 'Repeated DNA Sequences',
65+
description:
66+
'Given a string `s` that represents a DNA sequence, return all the 10-letter-long sequences (substrings) that occur more than once in a DNA molecule. You may return the answer in any order.',
67+
topics: ['Algorithms', 'Bit Manipulation'],
68+
difficulty: 'Medium',
69+
leetcode: 'https://leetcode.com/problems/repeated-dna-sequences/',
70+
},
71+
{
72+
id: 9,
73+
title: 'Course Schedule',
74+
description:
75+
'There are a total of `numCourses` courses you have to take, labeled from 0 to `numCourses - 1`. You are given an array `prerequisites` where `prerequisites[i] = [ai, bi]` indicates that you must take course `bi` first if you want to take course `ai`. Return true if you can finish all courses. Otherwise, return false.',
76+
topics: ['Data Structures', 'Algorithms'],
77+
difficulty: 'Medium',
78+
leetcode: 'https://leetcode.com/problems/course-schedule/',
79+
},
80+
{
81+
id: 10,
82+
title: 'LRU Cache Design',
83+
description: 'Design and implement an LRU (Least Recently Used) cache.',
84+
topics: ['Data Structures'],
85+
difficulty: 'Medium',
86+
leetcode: 'https://leetcode.com/problems/lru-cache/',
87+
},
88+
{
89+
id: 11,
90+
title: 'Longest Common Subsequence',
91+
description:
92+
'Given two strings `text1` and `text2`, return the length of their longest common subsequence. If there is no common subsequence, return 0.\n\nA subsequence of a string is a new string generated from the original string with some characters (can be none) deleted without changing the relative order of the remaining characters.\n\nFor example, "ace" is a subsequence of "abcde". A common subsequence of two strings is a subsequence that is common to both strings.',
93+
topics: ['Strings', 'Algorithms'],
94+
difficulty: 'Medium',
95+
leetcode: 'https://leetcode.com/problems/longest-common-subsequence/',
96+
},
97+
{
98+
id: 12,
99+
title: 'Rotate Image',
100+
description:
101+
'You are given an `n x n` 2D matrix representing an image, rotate the image by 90 degrees (clockwise).',
102+
topics: ['Arrays', 'Algorithms'],
103+
difficulty: 'Medium',
104+
leetcode: 'https://leetcode.com/problems/rotate-image/',
105+
},
106+
{
107+
id: 13,
108+
title: 'Airplane Seat Assignment Probability',
109+
description:
110+
'n passengers board an airplane with exactly n seats. The first passenger has lost the ticket and picks a seat randomly. After that, the rest of the passengers will:\n\n- Take their own seat if it is still available\n- Pick other seats randomly when they find their seat occupied\n\nReturn the probability that the nth person gets their own seat.',
111+
topics: ['Brainteaser'],
112+
difficulty: 'Medium',
113+
leetcode: 'https://leetcode.com/problems/airplane-seat-assignment-probability/',
114+
},
115+
{
116+
id: 14,
117+
title: 'Validate Binary Search Tree',
118+
description:
119+
'Given the root of a binary tree, determine if it is a valid binary search tree (BST).',
120+
topics: ['Data Structures', 'Algorithms'],
121+
difficulty: 'Medium',
122+
leetcode: 'https://leetcode.com/problems/validate-binary-search-tree/',
123+
},
124+
{
125+
id: 15,
126+
title: 'Sliding Window Maximum',
127+
description:
128+
'You are given an array of integers `nums`. There is a sliding window of size `k` which is moving from the very left of the array to the very right. You can only see the `k` numbers in the window. Each time the sliding window moves right by one position.\n\nReturn the max sliding window.',
129+
topics: ['Arrays', 'Algorithms'],
130+
difficulty: 'Hard',
131+
leetcode: 'https://leetcode.com/problems/sliding-window-maximum/',
132+
},
133+
{
134+
id: 16,
135+
title: 'N-Queen Problem',
136+
description:
137+
"The n-queens puzzle is the problem of placing n queens on an `n x n` chessboard such that no two queens attack each other.\n\nGiven an integer `n`, return all distinct solutions to the n-queens puzzle. You may return the answer in any order.\n\nEach solution contains a distinct board configuration of the n-queens' placement, where 'Q' and '.' both indicate a queen and an empty space, respectively.",
138+
topics: ['Algorithms'],
139+
difficulty: 'Hard',
140+
leetcode: 'https://leetcode.com/problems/n-queens/',
141+
},
142+
{
143+
id: 17,
144+
title: 'Serialize and Deserialize a Binary Tree',
145+
description:
146+
'Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer or transmitted across a network connection link to be reconstructed later in the same or another computer environment.\n\nDesign an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure.',
147+
topics: ['Data Structures', 'Algorithms'],
148+
difficulty: 'Hard',
149+
leetcode: 'https://leetcode.com/problems/serialize-and-deserialize-binary-tree/',
150+
},
151+
{
152+
id: 18,
153+
title: 'Wildcard Matching',
154+
description:
155+
"Given an input string `s` and a pattern `p`, implement wildcard pattern matching with support for '?' and '*' where:\n\n- '?' Matches any single character\n- '*' Matches any sequence of characters (including the empty sequence)\n\nThe matching should cover the entire input string (not partial).",
156+
topics: ['Strings', 'Algorithms'],
157+
difficulty: 'Hard',
158+
leetcode: 'https://leetcode.com/problems/wildcard-matching/',
159+
},
160+
{
161+
id: 19,
162+
title: 'Chalkboard XOR Game',
163+
description:
164+
'You are given an array of integers `nums` representing the numbers written on a chalkboard. Alice and Bob take turns erasing exactly one number from the chalkboard, with Alice starting first. If erasing a number causes the bitwise XOR of all the elements of the chalkboard to become 0, then that player loses. The bitwise XOR of one element is that element itself, and the bitwise XOR of no elements is 0.\n\nAlso, if any player starts their turn with the bitwise XOR of all the elements of the chalkboard equal to 0, then that player wins.\n\nReturn `true` if and only if Alice wins the game, assuming both players play optimally.',
165+
topics: ['Brainteaser'],
166+
difficulty: 'Hard',
167+
leetcode: 'https://leetcode.com/problems/chalkboard-xor-game/',
168+
},
169+
{
170+
id: 20,
171+
title: 'Trips and Users',
172+
description:
173+
"Given table `Trips` with columns `id`, `client_id`, `driver_id`, `city_id`, `status`, and `request_at`, where `id` is the primary key. The table holds all taxi trips. Each trip has a unique `id`, while `client_id` and `driver_id` are foreign keys to the `users_id` in the `Users` table.\n\nStatus is an ENUM (category) type of ('completed', 'cancelled_by_driver', 'cancelled_by_client').\n\nGiven table `Users` with columns `users_id`, `banned`, and `role`, users_id is the primary key (column with unique values) for htis table. The table holds all users. Each user has a unique `users_id` and `role` is an ENUM type of ('client', 'driver', 'partner'). `banned` is an ENUM category of type ('Yes', 'No'). The cancellation rate is computed by dividing the number of canceled (by client or driver) requests with unbanned users by the total number of requests with unbanned users on that day.\n\nWrite a solution to find the cancellation rate of requests with unbanned users (both client and driver must not be banned) each day between \"2013-10-01\" and \"2013-10-03\". Round the cancellation rate to two decimal points.",
174+
topics: ['Databases'],
175+
difficulty: 'Hard',
176+
leetcode: 'https://leetcode.com/problems/trips-and-users/',
177+
},
178+
];
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { observer } from 'mobx-react';
2+
import { Outlet } from 'react-router-dom';
3+
4+
export const AuthedLayout = observer(() => {
5+
return (
6+
<div id='main' className='flex h-[calc(100dvh-64px)] w-full flex-col overscroll-contain'>
7+
<Outlet />
8+
</div>
9+
// <div id='main' className='bg-background flex size-full min-h-screen flex-col'>
10+
// {/* Body */}
11+
// <div className='container'>
12+
// <Outlet />
13+
// </div>
14+
// </div>
15+
);
16+
});

frontend/src/components/blocks/layout.tsx renamed to frontend/src/components/blocks/root-layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { Outlet } from 'react-router-dom';
22
import NavBar from './nav-bar';
33

4-
export function Layout() {
4+
export function RootLayout() {
55
return (
6-
<div className='text-text bg-background flex min-h-screen flex-col'>
6+
<div className='text-text bg-background flex min-h-screen flex-col overscroll-contain'>
77
<NavBar />
88
<main className='flex flex-1'>
99
<Outlet />

frontend/src/components/blocks/route-guard.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ export const loader =
1919
async ({ request }: LoaderFunctionArgs) => {
2020
const route = new URL(request.url);
2121
const path = route.pathname;
22-
const authedRoutes = [ROUTES.HOME];
22+
const unAuthedRoutes = [ROUTES.LOGIN, ROUTES.SIGNUP, ROUTES.FORGOT_PASSWORD];
23+
const unAuthedRoute = unAuthedRoutes.includes(path);
2324

2425
return defer({
2526
isAuthed: await queryClient.ensureQueryData({
@@ -33,7 +34,7 @@ export const loader =
3334
return Math.max(expiresAt.getTime() - now.getTime(), 0);
3435
},
3536
}),
36-
authedRoute: authedRoutes.includes(path),
37+
authedRoute: !unAuthedRoute,
3738
path,
3839
});
3940
};
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import * as React from 'react';
2+
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
3+
4+
import { cn } from '@/lib/utils';
5+
6+
const ScrollArea = React.forwardRef<
7+
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
8+
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
9+
>(({ className, children, ...props }, ref) => (
10+
<ScrollAreaPrimitive.Root
11+
ref={ref}
12+
className={cn('relative overflow-hidden', className)}
13+
{...props}
14+
>
15+
<ScrollAreaPrimitive.Viewport className='size-full rounded-[inherit]'>
16+
{children}
17+
</ScrollAreaPrimitive.Viewport>
18+
<ScrollBar />
19+
<ScrollAreaPrimitive.Corner />
20+
</ScrollAreaPrimitive.Root>
21+
));
22+
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
23+
24+
const ScrollBar = React.forwardRef<
25+
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
26+
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
27+
>(({ className, orientation = 'vertical', ...props }, ref) => (
28+
<ScrollAreaPrimitive.ScrollAreaScrollbar
29+
ref={ref}
30+
orientation={orientation}
31+
className={cn(
32+
'flex touch-none select-none transition-colors',
33+
orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent p-[1px]',
34+
orientation === 'horizontal' && 'h-2.5 flex-col border-t border-t-transparent p-[1px]',
35+
className
36+
)}
37+
{...props}
38+
>
39+
<ScrollAreaPrimitive.ScrollAreaThumb className='bg-border relative flex-1 rounded-full' />
40+
</ScrollAreaPrimitive.ScrollAreaScrollbar>
41+
));
42+
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
43+
44+
export { ScrollArea, ScrollBar };
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import * as React from 'react';
2+
import * as SeparatorPrimitive from '@radix-ui/react-separator';
3+
4+
import { cn } from '@/lib/utils';
5+
6+
const Separator = React.forwardRef<
7+
React.ElementRef<typeof SeparatorPrimitive.Root>,
8+
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
9+
>(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (
10+
<SeparatorPrimitive.Root
11+
ref={ref}
12+
decorative={decorative}
13+
orientation={orientation}
14+
className={cn(
15+
'shrink-0 bg-border',
16+
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
17+
className
18+
)}
19+
{...props}
20+
/>
21+
));
22+
Separator.displayName = SeparatorPrimitive.Root.displayName;
23+
24+
export { Separator };

frontend/src/lib/router.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,35 @@
11
import { createBrowserRouter } from 'react-router-dom';
22

3-
import { Layout } from '@/components/blocks/layout';
4-
import { loader as routeGuardLoader, RouteGuard } from '@/components/blocks/route-guard';
3+
import { AuthedLayout } from '@/components/blocks/authed-layout';
4+
import { RootLayout } from '@/components/blocks/root-layout';
5+
import { RouteGuard, loader as routeGuardLoader } from '@/components/blocks/route-guard';
56

67
import { ForgotPassword } from '@/routes/forgot-password';
78
import { Login } from '@/routes/login';
8-
import { Landing } from '@/routes/landing';
9+
import { loader as qnDetailsLoader, QuestionDetails } from '@/routes/questions/details';
910
import { SignUp } from '@/routes/signup';
1011

11-
import { ROUTES } from './routes';
1212
import { queryClient } from './query-client';
13+
import { ROUTES } from './routes';
1314

1415
export const router = createBrowserRouter([
1516
{
16-
element: <Layout />,
17+
element: <RootLayout />,
1718
children: [
1819
{
1920
element: <RouteGuard />,
2021
loader: routeGuardLoader(queryClient),
2122
children: [
2223
{
2324
path: ROUTES.HOME,
24-
element: <Landing />,
25+
element: <AuthedLayout />,
26+
children: [
27+
{
28+
path: ROUTES.QUESTION_DETAILS,
29+
loader: qnDetailsLoader(queryClient),
30+
element: <QuestionDetails />,
31+
},
32+
],
2533
},
2634
{
2735
path: ROUTES.LOGIN,

frontend/src/lib/routes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export const ROUTES = {
33
LOGIN: '/login',
44
SIGNUP: '/signup',
55
FORGOT_PASSWORD: '/forgot-password',
6+
QUESTION_DETAILS: 'questions/:questionId',
67
};
78

89
const TITLES: Record<string, string> = {

frontend/src/routes/landing.tsx

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)