Skip to content

Commit aed5b5a

Browse files
jonyeokjfirwerjavinchua
authored
[Questions] Initialize Questions Repository Frontend (#13)
**Description** This PR initializes the frontend for the Question Repository page, enabling CRUD operations for questions. The page includes a table displaying all questions, with the ability to view, edit, delete, and create new questions via modals. Each question's title, difficulty, and categories are displayed, with navigation to a detailed view for each question. **Changes** - Implemented Question Repository page displaying a table of questions. - Each row displays the question title, difficulty (via `DifficultyBadge`), and categories. - Clicking the question title navigates to the question detail page. - Added modals for: - Creating new questions (`CreateModal`). - Editing existing questions with pre-filled data from (`EditModal`). - Deleting questions with a confirmation modal (`DeleteModal`). - Implemented API integration: - Fetched question data using `fetchQuestions` and individual question details using `fetchQuestionById`. - Integrated `createQuestion`, `updateQuestion`, and `deleteQuestion` mutations. - Added optimistic updates by invalidating queries after mutations using React Query's `useMutation`. **Future Enhancements** - Implement pagination for the Question Repository table. - Add search and filter functionality to find questions based on difficulty, category, or title. - Add Toast for success/error of CRUD operations. --------- Co-authored-by: firwer <[email protected]> Co-authored-by: javinchua <[email protected]>
1 parent 5ecb636 commit aed5b5a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2769
-334
lines changed

project/.prettierrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"singleQuote": false
3+
}

project/apps/api-gateway/src/questions/questions.controller.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ import {
77
Inject,
88
Body,
99
Post,
10-
UseGuards,
10+
// UseGuards,
1111
Put,
1212
Delete,
1313
Query,
1414
UsePipes,
1515
BadRequestException,
1616
} from '@nestjs/common';
1717
import { ClientProxy } from '@nestjs/microservices';
18-
import { AuthGuard } from 'src/auth/auth.guard';
18+
// import { AuthGuard } from 'src/auth/auth.guard';
1919
import {
2020
CreateQuestionDto,
2121
createQuestionSchema,
@@ -27,7 +27,7 @@ import {
2727
import { ZodValidationPipe } from '@repo/pipes/zod-validation-pipe.pipe';
2828

2929
@Controller('questions')
30-
@UseGuards(AuthGuard) // Can comment out if we dw auth for now
30+
// @UseGuards(AuthGuard) // comment out if we dw auth for now
3131
export class QuestionsController {
3232
constructor(
3333
@Inject('QUESTIONS_SERVICE')

project/apps/web/app/auth/SignInForm.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { signIn } from "@/lib/api/auth";
1616
import { useZodForm } from "@/lib/form";
1717
import { useLoginState } from "@/contexts/LoginStateContext";
1818
import { useToast } from "@/hooks/use-toast";
19+
import { QUERY_KEYS } from "@/constants/queryKeys";
1920

2021
export function SignInForm() {
2122
const form = useZodForm({ schema: signInSchema });
@@ -27,7 +28,7 @@ export function SignInForm() {
2728
mutationFn: signIn,
2829
onSuccess: async () => {
2930
setHasLoginStateFlag();
30-
await queryClient.invalidateQueries({ queryKey: ["me"] });
31+
await queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.Me] });
3132
},
3233
onError(error) {
3334
toast({

project/apps/web/app/auth/SignUpForm.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
1515
import { signUp } from "@/lib/api/auth";
1616
import { useZodForm } from "@/lib/form";
1717
import { useLoginState } from "@/contexts/LoginStateContext";
18+
import { QUERY_KEYS } from "@/constants/queryKeys";
1819
import { useToast } from "@/hooks/use-toast";
1920

2021
export function SignUpForm() {
@@ -26,7 +27,7 @@ export function SignUpForm() {
2627
mutationFn: signUp,
2728
onSuccess: async () => {
2829
setHasLoginStateFlag();
29-
await queryClient.invalidateQueries({ queryKey: ["me"] });
30+
await queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.Me] });
3031
},
3132
onError: (error) => {
3233
toast({

project/apps/web/app/auth/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
} from "@/components/ui/card";
1313
import { SignInForm } from "./SignInForm";
1414
import { SignUpForm } from "./SignUpForm";
15-
import { PublicPageWrapper } from "@/components/AuthWrappers/PublicPageWrapper";
15+
import { PublicPageWrapper } from "@/components/auth-wrappers/PublicPageWrapper";
1616
import { LANDING } from "@/lib/routes";
1717

1818
export default function AuthPage() {

project/apps/web/app/globals.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22
@tailwind components;
33
@tailwind utilities;
44

5+
body {
6+
margin: 0;
7+
padding: 0;
8+
font-family: var(--font-roboto), sans-serif;
9+
}
10+
11+
h1,
12+
h2,
13+
h3,
14+
h4,
15+
h5,
16+
h6 {
17+
font-family: var(--font-inter), sans-serif;
18+
}
19+
520
@layer base {
621
:root {
722
--background: 0 0% 100%;
@@ -31,6 +46,9 @@
3146
--destructive: 0 100% 50%;
3247
--destructive-foreground: 210 40% 98%;
3348

49+
--green: 120 100% 25%;
50+
--green-foreground: 0 0% 100%;
51+
3452
--ring: 215 20.2% 65.1%;
3553

3654
--radius: 0.5rem;

project/apps/web/app/layout.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,31 @@
1+
import { Inter, Roboto } from "next/font/google";
12
import ReactQueryProvider from "@/components/ReactQueryProvider";
23
import Suspense from "@/components/Suspense";
34
import { Skeleton } from "@/components/ui/skeleton";
45
import { LoginStateProvider } from "@/contexts/LoginStateContext";
5-
import "./globals.css";
66
import { Toaster } from "@/components/ui/toaster";
77

8+
import "./globals.css";
9+
10+
const inter = Inter({
11+
subsets: ["latin"],
12+
weight: ["400", "500"],
13+
display: "swap",
14+
});
15+
16+
const roboto = Roboto({
17+
subsets: ["latin"],
18+
weight: ["400", "500", "700"],
19+
});
20+
821
export default function RootLayout({
922
children,
1023
}: Readonly<{
1124
children: React.ReactNode;
1225
}>) {
1326
return (
14-
<html lang="en">
15-
<body>
27+
<html lang="en" className={inter.className}>
28+
<body className={roboto.className}>
1629
<LoginStateProvider>
1730
<Suspense fallback={<Skeleton className="w-screen h-screen" />}>
1831
<ReactQueryProvider>{children}</ReactQueryProvider>

project/apps/web/app/page.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
"use client";
22

3-
export default function Home() {
4-
return <div>Landing Page</div>;
5-
}
3+
import Link from "next/link";
4+
import { Button } from "@/components/ui/button";
5+
6+
const Dashboard = () => {
7+
return (
8+
<div className="flex items-center justify-center min-h-screen">
9+
<div className="flex flex-col items-center">
10+
<div className="font-bold">Dashboard Page</div>
11+
<Link href="/questions" passHref>
12+
<Button className="mt-4">Go to Questions Repository</Button>
13+
</Link>
14+
</div>
15+
</div>
16+
);
17+
};
18+
19+
export default Dashboard;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"use client";
2+
3+
import { Button } from "@/components/ui/button";
4+
import {
5+
Dialog,
6+
DialogContent,
7+
DialogDescription,
8+
DialogFooter,
9+
DialogHeader,
10+
DialogTitle,
11+
} from "@/components/ui/dialog";
12+
13+
interface DeleteModalProps {
14+
open: boolean;
15+
setOpen: (open: boolean) => void;
16+
onDelete: () => void;
17+
questionTitle: string;
18+
}
19+
20+
export default function DeleteModal({
21+
open,
22+
setOpen,
23+
onDelete,
24+
questionTitle,
25+
}: DeleteModalProps) {
26+
return (
27+
<Dialog open={open} onOpenChange={setOpen}>
28+
<DialogContent>
29+
<DialogHeader>
30+
<DialogTitle>Delete Question</DialogTitle>
31+
</DialogHeader>
32+
<DialogDescription>
33+
<div>
34+
Are you sure you want to delete the question "{questionTitle}"?
35+
</div>
36+
<div>This action cannot be undone.</div>
37+
</DialogDescription>
38+
<DialogFooter>
39+
<Button variant="outline" onClick={() => setOpen(false)}>
40+
Cancel
41+
</Button>
42+
<Button variant="destructive" onClick={onDelete}>
43+
Delete
44+
</Button>
45+
</DialogFooter>
46+
</DialogContent>
47+
</Dialog>
48+
);
49+
}

0 commit comments

Comments
 (0)