From d52e06756662892e2811478d17e58c70eba44f2c Mon Sep 17 00:00:00 2001 From: SeeuSim Date: Thu, 7 Nov 2024 15:31:03 +0800 Subject: [PATCH 01/21] (chore): Fix AI history dark mode bug Signed-off-by: SeeuSim --- frontend/.env.local | 7 ----- .../blocks/interview/chat/chat-layout.tsx | 6 ++-- .../attempt-details/code-viewer.tsx | 29 ++++++++++--------- .../attempt-details/main.tsx | 2 +- 4 files changed, 20 insertions(+), 24 deletions(-) delete mode 100644 frontend/.env.local diff --git a/frontend/.env.local b/frontend/.env.local deleted file mode 100644 index d2ffc87951..0000000000 --- a/frontend/.env.local +++ /dev/null @@ -1,7 +0,0 @@ -FRONTEND_ENV=local - -VITE_USER_SERVICE=http://localhost:9001 -VITE_QUESTION_SERVICE=http://localhost:9002 -VITE_COLLAB_SERVICE=http://localhost:9003 -VITE_MATCHING_SERVICE=http://localhost:9004 -VITE_CHAT_SERVICE=http://localhost:9005 diff --git a/frontend/src/components/blocks/interview/chat/chat-layout.tsx b/frontend/src/components/blocks/interview/chat/chat-layout.tsx index 8631b12ad4..13f29de57e 100644 --- a/frontend/src/components/blocks/interview/chat/chat-layout.tsx +++ b/frontend/src/components/blocks/interview/chat/chat-layout.tsx @@ -110,15 +110,15 @@ export const ChatLayout = ({ - + - Clear Chat History + Clear Chat History Are you sure you want to clear the chat history? This action cannot be undone. - Cancel + Cancel Clear History diff --git a/frontend/src/components/blocks/interview/question-attempts/attempt-details/code-viewer.tsx b/frontend/src/components/blocks/interview/question-attempts/attempt-details/code-viewer.tsx index ffbe70a7ce..e03f40650b 100644 --- a/frontend/src/components/blocks/interview/question-attempts/attempt-details/code-viewer.tsx +++ b/frontend/src/components/blocks/interview/question-attempts/attempt-details/code-viewer.tsx @@ -4,6 +4,7 @@ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; import { Button } from '@/components/ui/button'; +import { ScrollArea } from '@/components/ui/scroll-area'; type ICodeProps = { code: string; @@ -24,7 +25,7 @@ export const CodeViewer: FC = ({ code, language }) => { }; return ( -
+
{language}
- - {code} - + + + {code} + +
); }; diff --git a/frontend/src/components/blocks/interview/question-attempts/attempt-details/main.tsx b/frontend/src/components/blocks/interview/question-attempts/attempt-details/main.tsx index 09d0ce9536..796f853cdb 100644 --- a/frontend/src/components/blocks/interview/question-attempts/attempt-details/main.tsx +++ b/frontend/src/components/blocks/interview/question-attempts/attempt-details/main.tsx @@ -31,7 +31,7 @@ export const AttemptDetailsDialog: FC ) : ( {triggerText} )} - + Attempt {attemptId} From 0f7d97c8b50ac53e7900eef139b8ac6ea0d71b5c Mon Sep 17 00:00:00 2001 From: SeeuSim Date: Thu, 7 Nov 2024 16:28:06 +0800 Subject: [PATCH 02/21] chore/ui: Add fix for copy paste from chat Signed-off-by: SeeuSim --- .../blocks/authed/with-nav-blocker.tsx | 48 +++++++++++++------ .../blocks/interview/chat/chat-markdown.tsx | 9 +++- .../components/blocks/interview/editor.tsx | 8 ++++ .../attempt-details/code-viewer.tsx | 3 +- .../interview/question-attempts/table.tsx | 6 --- .../blocks/interview/room/complete-dialog.tsx | 14 +++++- 6 files changed, 63 insertions(+), 25 deletions(-) diff --git a/frontend/src/components/blocks/authed/with-nav-blocker.tsx b/frontend/src/components/blocks/authed/with-nav-blocker.tsx index b79aa169ee..b8e9ba7e28 100644 --- a/frontend/src/components/blocks/authed/with-nav-blocker.tsx +++ b/frontend/src/components/blocks/authed/with-nav-blocker.tsx @@ -1,8 +1,15 @@ +import { VisuallyHidden } from '@radix-ui/react-visually-hidden'; import { FC, PropsWithChildren } from 'react'; import { useBlocker } from 'react-router-dom'; import { Button } from '@/components/ui/button'; -import { Dialog, DialogContent } from '@/components/ui/dialog'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogTitle, +} from '@/components/ui/dialog'; export const WithNavBlocker: FC = ({ children }) => { const blocker = useBlocker( @@ -13,21 +20,32 @@ export const WithNavBlocker: FC = ({ children }) => { {blocker.state === 'blocked' && ( -

+ Are you sure you want to navigate away from this page? -

-
- - -
-
+ + + + + +
+ + +
+
+
)} diff --git a/frontend/src/components/blocks/interview/chat/chat-markdown.tsx b/frontend/src/components/blocks/interview/chat/chat-markdown.tsx index 7d08af71c7..992fa0619a 100644 --- a/frontend/src/components/blocks/interview/chat/chat-markdown.tsx +++ b/frontend/src/components/blocks/interview/chat/chat-markdown.tsx @@ -31,7 +31,15 @@ export const MarkdownComponent = ({ code({ children, className, ...rest }) { const [copyCodeText, setCopyCodeText] = useState('Copy Code'); + const match = /language-(\w+)/.exec(className || ''); + const onCopy = (code: string) => { + const language = match?.[1]; + + if (language) { + localStorage.setItem('ai-asst-lang', language); + } + navigator.clipboard.writeText(code); setCopyCodeText('Copied!'); setTimeout(() => { @@ -39,7 +47,6 @@ export const MarkdownComponent = ({ }, 3000); }; - const match = /language-(\w+)/.exec(className || ''); return match ? (
diff --git a/frontend/src/components/blocks/interview/editor.tsx b/frontend/src/components/blocks/interview/editor.tsx index 664ab34613..e017bfa72f 100644 --- a/frontend/src/components/blocks/interview/editor.tsx +++ b/frontend/src/components/blocks/interview/editor.tsx @@ -193,6 +193,14 @@ export const Editor = ({ height={`${Math.max((height as number) - EXTENSION_HEIGHT, MIN_EDITOR_HEIGHT)}px`} value={code} onChange={handleCodeChange} + onPaste={(_event) => { + const lang = localStorage.getItem('ai-asst-lang'); + + if (lang) { + setLanguage(lang as LanguageName); + localStorage.removeItem('ai-assist-lang'); + } + }} theme={themePreset} lang={language} basicSetup={{ diff --git a/frontend/src/components/blocks/interview/question-attempts/attempt-details/code-viewer.tsx b/frontend/src/components/blocks/interview/question-attempts/attempt-details/code-viewer.tsx index e03f40650b..4dec8573d2 100644 --- a/frontend/src/components/blocks/interview/question-attempts/attempt-details/code-viewer.tsx +++ b/frontend/src/components/blocks/interview/question-attempts/attempt-details/code-viewer.tsx @@ -25,7 +25,7 @@ export const CodeViewer: FC = ({ code, language }) => { }; return ( -
+
{language}
- {/* table.getColumn('title')?.setFilterValue(event.target.value)} - className='max-w-sm' - /> */}
diff --git a/frontend/src/components/blocks/interview/room/complete-dialog.tsx b/frontend/src/components/blocks/interview/room/complete-dialog.tsx index 331f84bcb3..6ddce09452 100644 --- a/frontend/src/components/blocks/interview/room/complete-dialog.tsx +++ b/frontend/src/components/blocks/interview/room/complete-dialog.tsx @@ -1,3 +1,4 @@ +import { VisuallyHidden } from '@radix-ui/react-visually-hidden'; import { useMutation } from '@tanstack/react-query'; import { Loader2 } from 'lucide-react'; import { Dispatch, FC, PropsWithChildren, SetStateAction, useCallback, useState } from 'react'; @@ -7,8 +8,10 @@ import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, + DialogDescription, DialogFooter, DialogHeader, + DialogTitle, DialogTrigger, } from '@/components/ui/dialog'; import { addQuestionAttempt } from '@/services/question-service'; @@ -69,6 +72,10 @@ export const CompleteDialog: FC> = ({ // Navigate to home page setTimeout(() => { setCompleting(COMPLETION_STATES.EMPTY, true); + setIsOpen(false); + // Clear AI chat if moving away + localStorage.removeItem('ai-assist-lang'); + localStorage.removeItem('ai_chat_history'); navigate('/'); }, 200); }, @@ -81,9 +88,12 @@ export const CompleteDialog: FC> = ({ {children} - - Are you sure you wish to mark this question as complete? + + Are you sure you wish to mark this question as complete? + + +
) ) : ( - + <> + {data?.isAdmin && ( + + Admin + + )} + + )}
diff --git a/frontend/src/components/blocks/root-layout.tsx b/frontend/src/components/blocks/root-layout.tsx index 4e24d97eab..e24c858961 100644 --- a/frontend/src/components/blocks/root-layout.tsx +++ b/frontend/src/components/blocks/root-layout.tsx @@ -1,14 +1,9 @@ import { Outlet } from 'react-router-dom'; -import NavBar from './nav-bar'; - export function RootLayout() { return (
- -
- -
+
); } diff --git a/frontend/src/components/blocks/route-guard.tsx b/frontend/src/components/blocks/route-guard.tsx index e860f017a3..546b64c269 100644 --- a/frontend/src/components/blocks/route-guard.tsx +++ b/frontend/src/components/blocks/route-guard.tsx @@ -14,6 +14,7 @@ import { checkIsAuthed } from '@/services/user-service'; import { AuthStoreProvider } from '@/stores/auth-store'; import { Loading } from './loading'; +import NavBar from './nav-bar'; export const loader = (queryClient: QueryClient) => @@ -61,9 +62,12 @@ export const RouteGuard = () => { value={{ userId: authedPayload.userId ?? '', username: authedPayload.username ?? '', + email: authedPayload.email ?? '', + isAdmin: authedPayload.isAdmin ?? undefined, }} > - {isLoading ? : } + +
{isLoading ? : }
); }} diff --git a/frontend/src/routes/login/login-form.tsx b/frontend/src/routes/login/login-form.tsx index 0bf1dc26d3..bea80c3f26 100644 --- a/frontend/src/routes/login/login-form.tsx +++ b/frontend/src/routes/login/login-form.tsx @@ -46,7 +46,13 @@ export const LoginForm = () => { Password - +
diff --git a/frontend/src/routes/signup/signup-form.tsx b/frontend/src/routes/signup/signup-form.tsx index c7dc25f3a1..29a92a49d8 100644 --- a/frontend/src/routes/signup/signup-form.tsx +++ b/frontend/src/routes/signup/signup-form.tsx @@ -58,7 +58,12 @@ export const SignUpForm = () => { Username - + @@ -92,6 +97,7 @@ export const SignUpForm = () => { @@ -110,6 +116,7 @@ export const SignUpForm = () => { diff --git a/frontend/src/services/user-service.ts b/frontend/src/services/user-service.ts index d16a088458..ab3a8d2352 100644 --- a/frontend/src/services/user-service.ts +++ b/frontend/src/services/user-service.ts @@ -44,6 +44,8 @@ export const checkIsAuthed = (param?: { signal: AbortSignal }) => { expiresAt: response.data ? new Date(response.data.expiresAt) : new Date(), userId: response.data ? response.data.userId : undefined, username: response.data ? response.data.userName : undefined, + email: response.data ? response.data.email : undefined, + isAdmin: response.data?.isAdmin ?? undefined, }; } diff --git a/frontend/src/stores/auth-store.ts b/frontend/src/stores/auth-store.ts index 2d59f44ca4..ae0264f98c 100644 --- a/frontend/src/stores/auth-store.ts +++ b/frontend/src/stores/auth-store.ts @@ -1,16 +1,21 @@ import { createContext, useContext } from 'react'; -const AuthStore = createContext<{ userId: string; username: string }>({ +type AuthStoreType = { + userId: string; + username: string; + email: string; + isAdmin?: boolean; +}; + +const AuthStore = createContext({ userId: '', username: '', + email: '', }); export const AuthStoreProvider = AuthStore.Provider; export const useAuthedRoute = () => { - const { userId, username } = useContext(AuthStore); - return { - userId, - username, - }; + const data = useContext(AuthStore); + return data; }; From 80f9959c945a84bf67fe32c4603834a96b59bf49 Mon Sep 17 00:00:00 2001 From: SeeuSim Date: Thu, 7 Nov 2024 17:10:15 +0800 Subject: [PATCH 04/21] chore/ui: minimise layout shift for auth check Signed-off-by: SeeuSim --- .../src/components/blocks/root-layout.tsx | 29 ++++++++++++++- .../src/components/blocks/route-guard.tsx | 36 +++++++++++-------- frontend/src/stores/auth-store.ts | 6 +++- 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/frontend/src/components/blocks/root-layout.tsx b/frontend/src/components/blocks/root-layout.tsx index e24c858961..e044a51386 100644 --- a/frontend/src/components/blocks/root-layout.tsx +++ b/frontend/src/components/blocks/root-layout.tsx @@ -1,9 +1,36 @@ +import { useState } from 'react'; import { Outlet } from 'react-router-dom'; +import { AuthStoreProvider } from '@/stores/auth-store'; + +import NavBar from './nav-bar'; + export function RootLayout() { + const [userId, setUserId] = useState(''); + const [username, setUsername] = useState(''); + const [email, setEmail] = useState(''); + const [isAdmin, setIsAdmin] = useState(false); return (
- + + +
+ +
+
); } diff --git a/frontend/src/components/blocks/route-guard.tsx b/frontend/src/components/blocks/route-guard.tsx index 546b64c269..cba39021f2 100644 --- a/frontend/src/components/blocks/route-guard.tsx +++ b/frontend/src/components/blocks/route-guard.tsx @@ -11,10 +11,9 @@ import { import { ROUTES, UNAUTHED_ROUTES } from '@/lib/routes'; import { checkIsAuthed } from '@/services/user-service'; -import { AuthStoreProvider } from '@/stores/auth-store'; +import { useAuthedRoute } from '@/stores/auth-store'; import { Loading } from './loading'; -import NavBar from './nav-bar'; export const loader = (queryClient: QueryClient) => @@ -50,26 +49,33 @@ export const RouteGuard = () => { {({ authedPayload, isAuthedRoute, path: _p }) => { const [isLoading, setIsLoading] = useState(true); + const hooks = useAuthedRoute(); useEffect(() => { if (authedPayload.isAuthed !== isAuthedRoute) { navigate(isAuthedRoute ? ROUTES.LOGIN : ROUTES.HOME); } + const { isAdmin, email, username, userId } = authedPayload; + + if (isAdmin && hooks.setIsAdmin) { + hooks.setIsAdmin(true); + } + + if (email && hooks.setEmail) { + hooks.setEmail(email); + } + + if (username && hooks.setUsername) { + hooks.setUsername(username); + } + + if (userId && hooks.setUserId) { + hooks.setUserId(userId); + } + setIsLoading(false); }, [authedPayload]); - return ( - - -
{isLoading ? : }
-
- ); + return isLoading ? : ; }}
diff --git a/frontend/src/stores/auth-store.ts b/frontend/src/stores/auth-store.ts index ae0264f98c..83cb5b19f7 100644 --- a/frontend/src/stores/auth-store.ts +++ b/frontend/src/stores/auth-store.ts @@ -1,10 +1,14 @@ -import { createContext, useContext } from 'react'; +import { createContext, type Dispatch, type SetStateAction, useContext } from 'react'; type AuthStoreType = { userId: string; + setUserId?: Dispatch>; username: string; + setUsername?: Dispatch>; email: string; + setEmail?: Dispatch>; isAdmin?: boolean; + setIsAdmin?: Dispatch>; }; const AuthStore = createContext({ From 4ad7968a9189a52b2d7ab0966ecfd2c1af2531c7 Mon Sep 17 00:00:00 2001 From: SeeuSim Date: Thu, 7 Nov 2024 17:21:00 +0800 Subject: [PATCH 05/21] chore/ui: Add user dropdown Signed-off-by: SeeuSim --- .../src/components/common/user-dropdown.tsx | 25 +++++++++++++++++-- frontend/src/services/user-service.ts | 2 +- frontend/vite.config.ts | 10 ++++---- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/common/user-dropdown.tsx b/frontend/src/components/common/user-dropdown.tsx index 051335165e..80204bed34 100644 --- a/frontend/src/components/common/user-dropdown.tsx +++ b/frontend/src/components/common/user-dropdown.tsx @@ -7,11 +7,17 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { logout } from '@/services/user-service'; +import { useAuthedRoute } from '@/stores/auth-store'; + +import { Badge } from '../ui/badge'; export const UserDropdown = () => { + const { email, username, isAdmin } = useAuthedRoute(); const navigate = useNavigate(); const { mutate: sendLogoutRequest } = useMutation({ @@ -32,9 +38,24 @@ export const UserDropdown = () => { - + + +
+

{username}

+

{email}

+ {isAdmin && ( + <> +
+ + Admin + + + )} +
+ + - Logout + Log out diff --git a/frontend/src/services/user-service.ts b/frontend/src/services/user-service.ts index ab3a8d2352..908200025e 100644 --- a/frontend/src/services/user-service.ts +++ b/frontend/src/services/user-service.ts @@ -43,7 +43,7 @@ export const checkIsAuthed = (param?: { signal: AbortSignal }) => { isAuthed: true, expiresAt: response.data ? new Date(response.data.expiresAt) : new Date(), userId: response.data ? response.data.userId : undefined, - username: response.data ? response.data.userName : undefined, + username: response.data ? response.data.username : undefined, email: response.data ? response.data.email : undefined, isAdmin: response.data?.isAdmin ?? undefined, }; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index c8ad306f11..b9ffd2ffe4 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -26,7 +26,7 @@ export default defineConfig(({ mode }) => { proxy: { '/user-service': { target: env.VITE_USER_SERVICE, - rewrite: (path) => path.replace(/^\/user-service/, ''), + rewrite: (path: string) => path.replace(/^\/user-service/, ''), changeOrigin: true, cookiePathRewrite: { '*': '/', @@ -34,7 +34,7 @@ export default defineConfig(({ mode }) => { }, '/question-service': { target: env.VITE_QUESTION_SERVICE, - rewrite: (path) => path.replace(/^\/question-service/, ''), + rewrite: (path: string) => path.replace(/^\/question-service/, ''), changeOrigin: true, cookiePathRewrite: { '*': '/', @@ -42,7 +42,7 @@ export default defineConfig(({ mode }) => { }, '/collaboration-service': { target: env.VITE_COLLAB_SERVICE, - rewrite: (path) => path.replace(/^\/collaboration-service/, ''), + rewrite: (path: string) => path.replace(/^\/collaboration-service/, ''), changeOrigin: true, cookiePathRewrite: { '*': '/', @@ -50,7 +50,7 @@ export default defineConfig(({ mode }) => { }, '/matching-service': { target: env.VITE_MATCHING_SERVICE, - rewrite: (path) => path.replace(/^\/matching-service/, ''), + rewrite: (path: string) => path.replace(/^\/matching-service/, ''), changeOrigin: true, cookiePathRewrite: { '*': '/', @@ -58,7 +58,7 @@ export default defineConfig(({ mode }) => { }, '/collab-ws': { target: `${env.VITE_COLLAB_SERVICE.replace('http', 'ws')}`, - rewrite: (path) => path.replace(/\/collab-ws/, ''), + rewrite: (path: string) => path.replace(/\/collab-ws/, ''), ws: true, }, '/matching-socket/': { From 20e20306015b46dba02d97ced1eb32009a90852a Mon Sep 17 00:00:00 2001 From: SeeuSim Date: Thu, 7 Nov 2024 17:25:14 +0800 Subject: [PATCH 06/21] chore/ui: try fix build bug Signed-off-by: SeeuSim --- frontend/src/components/blocks/questions/details.tsx | 12 +++++++++++- frontend/vite.config.ts | 8 ++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/blocks/questions/details.tsx b/frontend/src/components/blocks/questions/details.tsx index c831d008d3..21c35af1ec 100644 --- a/frontend/src/components/blocks/questions/details.tsx +++ b/frontend/src/components/blocks/questions/details.tsx @@ -1,12 +1,15 @@ +import { Pencil1Icon } from '@radix-ui/react-icons'; import Markdown from 'react-markdown'; import rehypeKatex from 'rehype-katex'; import remarkGfm from 'remark-gfm'; import remarkMath from 'remark-math'; import { Badge } from '@/components/ui/badge'; -import { CardContent,CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Separator } from '@/components/ui/separator'; +import { useAuthedRoute } from '@/stores/auth-store'; import type { IGetQuestionDetailsResponse } from '@/types/question-types'; export const QuestionDetails = ({ @@ -14,6 +17,7 @@ export const QuestionDetails = ({ }: { questionDetails: IGetQuestionDetailsResponse['question']; }) => { + const { isAdmin } = useAuthedRoute(); return ( @@ -22,6 +26,12 @@ export const QuestionDetails = ({ {questionDetails.id}. {questionDetails.title} + {isAdmin && ( + + )}
{ }, '/question-service': { target: env.VITE_QUESTION_SERVICE, - rewrite: (path: string) => path.replace(/^\/question-service/, ''), + rewrite: (path: string) => path?.replace(/^\/question-service/, ''), changeOrigin: true, cookiePathRewrite: { '*': '/', @@ -42,7 +42,7 @@ export default defineConfig(({ mode }) => { }, '/collaboration-service': { target: env.VITE_COLLAB_SERVICE, - rewrite: (path: string) => path.replace(/^\/collaboration-service/, ''), + rewrite: (path: string) => path?.replace(/^\/collaboration-service/, ''), changeOrigin: true, cookiePathRewrite: { '*': '/', @@ -50,7 +50,7 @@ export default defineConfig(({ mode }) => { }, '/matching-service': { target: env.VITE_MATCHING_SERVICE, - rewrite: (path: string) => path.replace(/^\/matching-service/, ''), + rewrite: (path: string) => path?.replace(/^\/matching-service/, ''), changeOrigin: true, cookiePathRewrite: { '*': '/', @@ -58,7 +58,7 @@ export default defineConfig(({ mode }) => { }, '/collab-ws': { target: `${env.VITE_COLLAB_SERVICE.replace('http', 'ws')}`, - rewrite: (path: string) => path.replace(/\/collab-ws/, ''), + rewrite: (path: string) => path?.replace(/\/collab-ws/, ''), ws: true, }, '/matching-socket/': { From 11abbd824a0f8c38d855106feb5dc23fa050edbe Mon Sep 17 00:00:00 2001 From: SeeuSim Date: Thu, 7 Nov 2024 17:30:56 +0800 Subject: [PATCH 07/21] chore/ui: try fix build bug Signed-off-by: SeeuSim --- .../blocks/questions/admin-edit-form.tsx | 1 + .../components/blocks/questions/details.tsx | 2 +- frontend/vite.config.ts | 18 ++++++++++-------- 3 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 frontend/src/components/blocks/questions/admin-edit-form.tsx diff --git a/frontend/src/components/blocks/questions/admin-edit-form.tsx b/frontend/src/components/blocks/questions/admin-edit-form.tsx new file mode 100644 index 0000000000..2f846b6782 --- /dev/null +++ b/frontend/src/components/blocks/questions/admin-edit-form.tsx @@ -0,0 +1 @@ +export const AdminEditForm = () => {}; diff --git a/frontend/src/components/blocks/questions/details.tsx b/frontend/src/components/blocks/questions/details.tsx index 21c35af1ec..ccca3203c2 100644 --- a/frontend/src/components/blocks/questions/details.tsx +++ b/frontend/src/components/blocks/questions/details.tsx @@ -27,7 +27,7 @@ export const QuestionDetails = ({ {questionDetails.id}. {questionDetails.title} {isAdmin && ( - diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 846be11258..f31c39201e 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -7,9 +7,6 @@ import { defineConfig, loadEnv } from 'vite'; export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd()); return { - define: { - 'process.env.OPENAI_API_KEY': JSON.stringify(env.OPENAI_API_KEY), - }, plugins: [react()], build: { outDir: 'build', @@ -26,7 +23,8 @@ export default defineConfig(({ mode }) => { proxy: { '/user-service': { target: env.VITE_USER_SERVICE, - rewrite: (path: string) => path.replace(/^\/user-service/, ''), + rewrite: (path: string) => + String(path).replace && String(path).replace(/^\/user-service/, ''), changeOrigin: true, cookiePathRewrite: { '*': '/', @@ -34,7 +32,8 @@ export default defineConfig(({ mode }) => { }, '/question-service': { target: env.VITE_QUESTION_SERVICE, - rewrite: (path: string) => path?.replace(/^\/question-service/, ''), + rewrite: (path: string) => + String(path).replace && String(path).replace(/^\/question-service/, ''), changeOrigin: true, cookiePathRewrite: { '*': '/', @@ -42,7 +41,8 @@ export default defineConfig(({ mode }) => { }, '/collaboration-service': { target: env.VITE_COLLAB_SERVICE, - rewrite: (path: string) => path?.replace(/^\/collaboration-service/, ''), + rewrite: (path: string) => + String(path).replace && String(path).replace(/^\/collaboration-service/, ''), changeOrigin: true, cookiePathRewrite: { '*': '/', @@ -50,7 +50,8 @@ export default defineConfig(({ mode }) => { }, '/matching-service': { target: env.VITE_MATCHING_SERVICE, - rewrite: (path: string) => path?.replace(/^\/matching-service/, ''), + rewrite: (path: string) => + String(path).replace && String(path).replace(/^\/matching-service/, ''), changeOrigin: true, cookiePathRewrite: { '*': '/', @@ -58,7 +59,8 @@ export default defineConfig(({ mode }) => { }, '/collab-ws': { target: `${env.VITE_COLLAB_SERVICE.replace('http', 'ws')}`, - rewrite: (path: string) => path?.replace(/\/collab-ws/, ''), + rewrite: (path: string) => + String(path).replace && String(path).replace(/\/collab-ws/, ''), ws: true, }, '/matching-socket/': { From 32a77a0413dd20edcda437792550767b4762e417 Mon Sep 17 00:00:00 2001 From: SeeuSim Date: Thu, 7 Nov 2024 17:36:02 +0800 Subject: [PATCH 08/21] chore/ui: try fix build bug Signed-off-by: SeeuSim --- frontend/vite.config.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index f31c39201e..fa82f0d1b6 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -23,8 +23,7 @@ export default defineConfig(({ mode }) => { proxy: { '/user-service': { target: env.VITE_USER_SERVICE, - rewrite: (path: string) => - String(path).replace && String(path).replace(/^\/user-service/, ''), + rewrite: (path: string) => path?.replace && path.replace(/^\/user-service/, ''), changeOrigin: true, cookiePathRewrite: { '*': '/', @@ -32,8 +31,7 @@ export default defineConfig(({ mode }) => { }, '/question-service': { target: env.VITE_QUESTION_SERVICE, - rewrite: (path: string) => - String(path).replace && String(path).replace(/^\/question-service/, ''), + rewrite: (path: string) => path?.replace && path.replace(/^\/question-service/, ''), changeOrigin: true, cookiePathRewrite: { '*': '/', @@ -41,8 +39,7 @@ export default defineConfig(({ mode }) => { }, '/collaboration-service': { target: env.VITE_COLLAB_SERVICE, - rewrite: (path: string) => - String(path).replace && String(path).replace(/^\/collaboration-service/, ''), + rewrite: (path: string) => path?.replace && path.replace(/^\/collaboration-service/, ''), changeOrigin: true, cookiePathRewrite: { '*': '/', @@ -50,8 +47,7 @@ export default defineConfig(({ mode }) => { }, '/matching-service': { target: env.VITE_MATCHING_SERVICE, - rewrite: (path: string) => - String(path).replace && String(path).replace(/^\/matching-service/, ''), + rewrite: (path: string) => path?.replace && path.replace(/^\/matching-service/, ''), changeOrigin: true, cookiePathRewrite: { '*': '/', @@ -59,8 +55,7 @@ export default defineConfig(({ mode }) => { }, '/collab-ws': { target: `${env.VITE_COLLAB_SERVICE.replace('http', 'ws')}`, - rewrite: (path: string) => - String(path).replace && String(path).replace(/\/collab-ws/, ''), + rewrite: (path: string) => path?.replace && path.replace(/\/collab-ws/, ''), ws: true, }, '/matching-socket/': { From 39fb9dc98f90b96f7ad75f7fb13b281627e93c4f Mon Sep 17 00:00:00 2001 From: SeeuSim Date: Thu, 7 Nov 2024 17:42:54 +0800 Subject: [PATCH 09/21] try fix build bug Signed-off-by: SeeuSim --- frontend/package.json | 2 +- .../blocks/questions/admin-edit-form.tsx | 24 ++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 9e790a43d3..308ba0a20e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,7 +4,7 @@ "type": "module", "scripts": { "dev": "env-cmd -f .env.local vite", - "build": "tsc -b && vite build", + "build": "tsc -b && DEBUG=vite:* vite build", "lint": "eslint .", "preview": "vite preview --port 5173 --host 0.0.0.0" }, diff --git a/frontend/src/components/blocks/questions/admin-edit-form.tsx b/frontend/src/components/blocks/questions/admin-edit-form.tsx index 2f846b6782..0e3bab14f4 100644 --- a/frontend/src/components/blocks/questions/admin-edit-form.tsx +++ b/frontend/src/components/blocks/questions/admin-edit-form.tsx @@ -1 +1,23 @@ -export const AdminEditForm = () => {}; +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; + +export const AdminEditForm = () => { + return ( + + + + + + + + + + + ); +}; From 85afa58fb0447e26c005f23f2817900dcbf141f2 Mon Sep 17 00:00:00 2001 From: SeeuSim Date: Thu, 7 Nov 2024 17:58:16 +0800 Subject: [PATCH 10/21] try fix build bug Signed-off-by: SeeuSim --- frontend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 308ba0a20e..ea1f516fc1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,7 +4,7 @@ "type": "module", "scripts": { "dev": "env-cmd -f .env.local vite", - "build": "tsc -b && DEBUG=vite:* vite build", + "build": "tsc -b && rm -rf **/vite.config.ts.timestamp-* && vite build", "lint": "eslint .", "preview": "vite preview --port 5173 --host 0.0.0.0" }, From e447e764e51da4de2384faa313414114f57648cc Mon Sep 17 00:00:00 2001 From: SeeuSim Date: Thu, 7 Nov 2024 18:02:44 +0800 Subject: [PATCH 11/21] try fix build bug Signed-off-by: SeeuSim --- frontend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index ea1f516fc1..d6f822432e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,7 +4,7 @@ "type": "module", "scripts": { "dev": "env-cmd -f .env.local vite", - "build": "tsc -b && rm -rf **/vite.config.ts.timestamp-* && vite build", + "build": "tsc -b && vite build -d --emptyOutDir", "lint": "eslint .", "preview": "vite preview --port 5173 --host 0.0.0.0" }, From 8ce07d43db9208980ad7985a282262d05b849e8f Mon Sep 17 00:00:00 2001 From: SeeuSim Date: Thu, 7 Nov 2024 18:09:15 +0800 Subject: [PATCH 12/21] try fix build bug Signed-off-by: SeeuSim --- frontend/package.json | 2 +- frontend/tsconfig.node.json | 2 +- frontend/vite.config.ts | 10 +++++----- package-lock.json | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index d6f822432e..a3190a257d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -82,6 +82,6 @@ "tailwindcss": "^3.4.11", "typescript": "^5.5.3", "typescript-eslint": "^8.0.1", - "vite": "^5.4.1" + "vite": "^5.4.10" } } diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json index 0d3d71446a..d3c0143f9d 100644 --- a/frontend/tsconfig.node.json +++ b/frontend/tsconfig.node.json @@ -13,7 +13,7 @@ "noEmit": true, /* Linting */ - "strict": true, + "strict": false, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index fa82f0d1b6..e5105cbdf3 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -23,7 +23,7 @@ export default defineConfig(({ mode }) => { proxy: { '/user-service': { target: env.VITE_USER_SERVICE, - rewrite: (path: string) => path?.replace && path.replace(/^\/user-service/, ''), + rewrite: (path) => path?.replace && path.replace(/^\/user-service/, ''), changeOrigin: true, cookiePathRewrite: { '*': '/', @@ -31,7 +31,7 @@ export default defineConfig(({ mode }) => { }, '/question-service': { target: env.VITE_QUESTION_SERVICE, - rewrite: (path: string) => path?.replace && path.replace(/^\/question-service/, ''), + rewrite: (path) => path?.replace && path.replace(/^\/question-service/, ''), changeOrigin: true, cookiePathRewrite: { '*': '/', @@ -39,7 +39,7 @@ export default defineConfig(({ mode }) => { }, '/collaboration-service': { target: env.VITE_COLLAB_SERVICE, - rewrite: (path: string) => path?.replace && path.replace(/^\/collaboration-service/, ''), + rewrite: (path) => path?.replace && path.replace(/^\/collaboration-service/, ''), changeOrigin: true, cookiePathRewrite: { '*': '/', @@ -47,7 +47,7 @@ export default defineConfig(({ mode }) => { }, '/matching-service': { target: env.VITE_MATCHING_SERVICE, - rewrite: (path: string) => path?.replace && path.replace(/^\/matching-service/, ''), + rewrite: (path) => path?.replace && path.replace(/^\/matching-service/, ''), changeOrigin: true, cookiePathRewrite: { '*': '/', @@ -55,7 +55,7 @@ export default defineConfig(({ mode }) => { }, '/collab-ws': { target: `${env.VITE_COLLAB_SERVICE.replace('http', 'ws')}`, - rewrite: (path: string) => path?.replace && path.replace(/\/collab-ws/, ''), + rewrite: (path) => path?.replace && path.replace(/\/collab-ws/, ''), ws: true, }, '/matching-socket/': { diff --git a/package-lock.json b/package-lock.json index 6f225842b0..adbbc9ce10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -341,7 +341,7 @@ "tailwindcss": "^3.4.11", "typescript": "^5.5.3", "typescript-eslint": "^8.0.1", - "vite": "^5.4.1" + "vite": "^5.4.10" } }, "frontend/node_modules/eslint": { From 1f80d09e4c7d9e48fd48f5f42c5e5f2c4b0e3602 Mon Sep 17 00:00:00 2001 From: SeeuSim Date: Thu, 7 Nov 2024 20:32:51 +0800 Subject: [PATCH 13/21] chore/ui: Add update form Signed-off-by: SeeuSim --- .../blocks/questions/admin-edit-form.tsx | 311 +++++++++++++++++- .../components/blocks/questions/details.tsx | 11 +- 2 files changed, 302 insertions(+), 20 deletions(-) diff --git a/frontend/src/components/blocks/questions/admin-edit-form.tsx b/frontend/src/components/blocks/questions/admin-edit-form.tsx index 0e3bab14f4..11ec7a7e74 100644 --- a/frontend/src/components/blocks/questions/admin-edit-form.tsx +++ b/frontend/src/components/blocks/questions/admin-edit-form.tsx @@ -1,23 +1,310 @@ +import { zodResolver } from '@hookform/resolvers/zod'; +import { Cross2Icon, DotsVerticalIcon, Pencil1Icon, PlusIcon } from '@radix-ui/react-icons'; +import { useMutation } from '@tanstack/react-query'; +import { type FC,useState } from 'react'; +import { useForm } from 'react-hook-form'; +import Markdown from 'react-markdown'; +import rehypeKatex from 'rehype-katex'; +import remarkGfm from 'remark-gfm'; +import remarkMath from 'remark-math'; +import { z } from 'zod'; + +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, + DialogDescription, DialogFooter, DialogHeader, DialogTitle, - DialogTrigger, } from '@/components/ui/dialog'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Textarea } from '@/components/ui/textarea'; +import type { IGetQuestionDetailsResponse } from '@/types/question-types'; + +type AdminEditFormProps = { + questionDetails: IGetQuestionDetailsResponse['question']; +}; + +const formSchema = z.object({ + title: z.string().min(1), + difficulty: z.enum(['Easy', 'Medium', 'Hard']), + topic: z.string().min(1).array().min(1), + description: z.string().min(1), +}); + +export const AdminEditForm: FC = ({ questionDetails }) => { + const [isFormOpen, setIsFormOpen] = useState(false); + const [addedTopic, setAddedTopic] = useState(''); + const { mutate: _sendUpdate, isPending } = useMutation({}); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + ...questionDetails, + description: questionDetails.description, + difficulty: questionDetails.difficulty as z.infer['difficulty'], + }, + mode: 'onSubmit', + }); + + const onSubmit = (_formValues: z.infer) => {}; + + const addTopic = (topic: string) => { + const val = new Set(form.getValues('topic').map((v) => v.toLowerCase())); + val.add(topic.toLowerCase()); + form.setValue( + 'topic', + Array.from(val).map((v) => v.replace(/^[a-z]/, (c) => c.toUpperCase())) + ); + }; -export const AdminEditForm = () => { return ( - - - - - - - - - - + <> + + + + + + setIsFormOpen((isOpen) => !isOpen)} + className='flex w-full justify-between gap-2 hover:cursor-pointer' + > + Edit + + + + + + + + Edit Question Details + + +
+ + ( + + Title + + + + + + )} + /> +
+ ( + + Difficulty + + + + + + )} + /> + ( + +
+ Topics +
+ +
+
+ { + setAddedTopic(e.currentTarget.value); + }} + className='w-[150px]' + placeholder='New topic' + onKeyDown={(event) => { + if (event.key === 'Enter' && addedTopic) { + event.preventDefault(); + addTopic(addedTopic); + setAddedTopic(''); + } + }} + /> + + +
+
+ {field.value.map((value, index) => ( + + {value} + { + if (isPending) { + return; + } + + form.setValue( + 'topic', + field.value.filter((_value, idx) => idx !== index) + ); + }} + /> + + ))} +
+
+
+ +
+ )} + /> +
+ ( + + + + Description + Preview + + + +