From a651dd42e6575529ce2c78518677457a2ae7ac78 Mon Sep 17 00:00:00 2001 From: yunruu Date: Sat, 9 Nov 2024 21:31:08 +0800 Subject: [PATCH 01/13] feat: change button focus color to lighter gray for visual integration --- frontend/components/ui/button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/ui/button.tsx b/frontend/components/ui/button.tsx index 8bf8d0f16a..0c0187cd7b 100644 --- a/frontend/components/ui/button.tsx +++ b/frontend/components/ui/button.tsx @@ -5,7 +5,7 @@ import { cva, type VariantProps } from 'class-variance-authority' import { cn } from '@/lib/utils' const buttonVariants = cva( - 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50', + 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-gray-300 disabled:pointer-events-none disabled:opacity-50', { variants: { variant: { From 5f876d230625aac74415e9adef901652b0c9aab9 Mon Sep 17 00:00:00 2001 From: yunruu Date: Sat, 9 Nov 2024 22:07:58 +0800 Subject: [PATCH 02/13] feat: even out spacing between columns in questions datatable --- frontend/components/customs/datatable.tsx | 2 +- frontend/pages/questions/props.tsx | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/frontend/components/customs/datatable.tsx b/frontend/components/customs/datatable.tsx index c7dec5ab39..3b4d6a8407 100644 --- a/frontend/components/customs/datatable.tsx +++ b/frontend/components/customs/datatable.tsx @@ -65,7 +65,7 @@ export default function Datatable({ - {hideIdx ? null : 'No.'} + {hideIdx ? null : 'No.'} {columns.map((elem) => { if (elem.isHidden) { return null diff --git a/frontend/pages/questions/props.tsx b/frontend/pages/questions/props.tsx index 2e034d04e6..517e3c328a 100644 --- a/frontend/pages/questions/props.tsx +++ b/frontend/pages/questions/props.tsx @@ -15,12 +15,10 @@ const getColumns = (isAdmin: boolean): IDatatableColumn[] => { }, { key: 'title', - width: '30%', offAutoCapitalize: true, }, { key: 'categories', - width: '40%', formatter: (values) => { const c = values.map((v: string) => ( { }, { key: 'complexity', - width: '10%', isSortable: true, formatter: (value) => { return From 397e27b3fafe52e5177e25e7a92dffb2b71ce95a Mon Sep 17 00:00:00 2001 From: yunruu Date: Sat, 9 Nov 2024 22:18:44 +0800 Subject: [PATCH 03/13] feat: show session end toast --- frontend/pages/code/[id]/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/pages/code/[id]/index.tsx b/frontend/pages/code/[id]/index.tsx index 8143c6c0ba..eaf884fa05 100644 --- a/frontend/pages/code/[id]/index.tsx +++ b/frontend/pages/code/[id]/index.tsx @@ -203,6 +203,7 @@ export default function Code() { if (socketRef.current) { socketRef.current?.emit('end-session') router.push('/') + toast.info('The session has ended') } setIsDialogOpen(false) } From f3915a08c03cb3ed7b36b95f353f021c1124ad30 Mon Sep 17 00:00:00 2001 From: yunruu Date: Sun, 10 Nov 2024 15:33:52 +0800 Subject: [PATCH 04/13] feat: show custom action icon only if exists --- frontend/components/customs/datatable.tsx | 31 ++++++++++++----------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/frontend/components/customs/datatable.tsx b/frontend/components/customs/datatable.tsx index 3b4d6a8407..8c4d3a7c5e 100644 --- a/frontend/components/customs/datatable.tsx +++ b/frontend/components/customs/datatable.tsx @@ -129,21 +129,22 @@ export default function Datatable({ )} - {col.customAction && col.customAction.formatter ? ( - col.customAction.formatter(elem, router) - ) : ( - - )} + {col.customAction && + (col.customAction.formatter ? ( + col.customAction.formatter(elem, router) + ) : ( + + ))} ) } From 859a7927d21f3d4c5d93db29a99a65fd71f3f8eb Mon Sep 17 00:00:00 2001 From: yunruu Date: Sun, 10 Nov 2024 18:31:59 +0800 Subject: [PATCH 05/13] feat: show success toast on delete --- frontend/pages/questions/index.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/pages/questions/index.tsx b/frontend/pages/questions/index.tsx index 2b556e3f33..06f79b1ada 100644 --- a/frontend/pages/questions/index.tsx +++ b/frontend/pages/questions/index.tsx @@ -156,10 +156,8 @@ export default function Questions() { } else if (modificationType === Modification.DELETE) { if (!questionData.id) return try { - const res = await deleteQuestionById(questionData.id) - if (res) { - toast.success('Question deleted successfully') - } + await deleteQuestionById(questionData.id) + toast.success('Question deleted successfully') } catch (error) { toast.error('Failed to delete question' + error) return From d77ed96a68808103287d3bf935d9d6653ec11a21 Mon Sep 17 00:00:00 2001 From: yunruu Date: Sun, 10 Nov 2024 20:02:32 +0800 Subject: [PATCH 06/13] feat: show specific dialog header --- frontend/components/customs/custom-dialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/customs/custom-dialog.tsx b/frontend/components/customs/custom-dialog.tsx index 047558b707..be81151dfd 100644 --- a/frontend/components/customs/custom-dialog.tsx +++ b/frontend/components/customs/custom-dialog.tsx @@ -56,7 +56,7 @@ function CustomDialogWithButton(props: CustomDialogProps) {
- Warning + {props.text || 'Warning'}
From d020f6c7e2b73c946cb38d8e8f8154c1c140e080 Mon Sep 17 00:00:00 2001 From: yunruu Date: Sun, 10 Nov 2024 20:02:46 +0800 Subject: [PATCH 07/13] feat: put dialog above navbar --- frontend/components/ui/dialog.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/components/ui/dialog.tsx b/frontend/components/ui/dialog.tsx index bd27c8a468..c20850997a 100644 --- a/frontend/components/ui/dialog.tsx +++ b/frontend/components/ui/dialog.tsx @@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef< Date: Sun, 10 Nov 2024 21:07:22 +0800 Subject: [PATCH 08/13] feat: encode the match id in url for security --- frontend/components/dashboard/new-session.tsx | 6 ++++-- frontend/components/dashboard/resume-session.tsx | 9 +++++++-- frontend/pages/code/[id]/index.tsx | 9 ++++++--- frontend/pages/sessions/columns.tsx | 4 +++- frontend/pages/sessions/index.tsx | 4 +++- frontend/util/encryption.ts | 7 +++++++ 6 files changed, 30 insertions(+), 9 deletions(-) create mode 100644 frontend/util/encryption.ts diff --git a/frontend/components/dashboard/new-session.tsx b/frontend/components/dashboard/new-session.tsx index 515c07d052..c0fe41e72f 100644 --- a/frontend/components/dashboard/new-session.tsx +++ b/frontend/components/dashboard/new-session.tsx @@ -14,6 +14,7 @@ import { addUserToMatchmaking } from '../../services/matching-service-api' import CustomModal from '../customs/custom-modal' import Loading from '../customs/loading' import { capitalizeFirstLowerRest } from '@/util/string-modification' +import { encodeStr } from '@/util/encryption' export const NewSession = () => { const router = useRouter() @@ -109,7 +110,8 @@ export const NewSession = () => { switch (newMessage.type) { case WebSocketMessageType.SUCCESS: updateMatchmakingStatus(MatchingStatus.MATCH_FOUND, newMessage.matchId) - router.push(`/code/${newMessage.matchId}`) + const encodedId = encodeStr(newMessage.matchId) + router.push(`/code/${encodedId}`) break case WebSocketMessageType.FAILURE: socketRef.current?.close() @@ -267,7 +269,7 @@ export const NewSession = () => { diff --git a/frontend/components/dashboard/resume-session.tsx b/frontend/components/dashboard/resume-session.tsx index 4f6ec06293..abc6ceb7ed 100644 --- a/frontend/components/dashboard/resume-session.tsx +++ b/frontend/components/dashboard/resume-session.tsx @@ -3,6 +3,8 @@ import { Button } from '../ui/button' import { IMatch } from '@repo/user-types' import { convertSortedComplexityToComplexity } from '@repo/question-types' import { capitalizeFirstLowerRest } from '@/util/string-modification' +import { encodeStr } from '@/util/encryption' +import { toast } from 'sonner' interface IResumeSessionProps { match: IMatch @@ -18,8 +20,11 @@ export default function ResumeSession({ match, isOngoing }: IResumeSessionProps) try { const ongoing = await isOngoing() if (!ongoing) return - router.push(`/code/${match.id}`) - } catch (error) {} + const encodedId = encodeStr(match.id) + router.push(`/code/${encodedId}`) + } catch (error) { + toast.error('Unable to resume session due to a server error') + } } return ( diff --git a/frontend/pages/code/[id]/index.tsx b/frontend/pages/code/[id]/index.tsx index eaf884fa05..4df5d1be8a 100644 --- a/frontend/pages/code/[id]/index.tsx +++ b/frontend/pages/code/[id]/index.tsx @@ -32,6 +32,7 @@ import { getChatHistory, getCollabHistory } from '@/services/collaboration-servi import ReadOnlyCodeMirrorEditor from '../read-only-editor' import { ResultModel } from '@repo/collaboration-types' import { capitalizeFirstLowerRest } from '@/util/string-modification' +import { decodeStr } from '@/util/encryption' const formatQuestionCategories = (cat: Category[]) => { return cat.map((c) => capitalizeFirstLowerRest(c)).join(', ') @@ -40,7 +41,7 @@ const formatQuestionCategories = (cat: Category[]) => { export default function Code() { const router = useRouter() const [isChatOpen, setIsChatOpen] = useState(true) - const { id } = router.query + const [id, setId] = useState(router.query.id as string) const editorRef = useRef<{ getText: () => string } | null>(null) const [editorLanguage, setEditorLanguage] = useState(LanguageMode.Javascript) const testTabs = ['Testcases', 'Test Results'] @@ -81,8 +82,10 @@ export default function Code() { const { data: sessionData } = useSession() useEffect(() => { - const matchId = router.query.id as string + const encodedId = router.query.id as string + const matchId = decodeStr(encodedId) retrieveMatchDetails(matchId) + setId(matchId) }, [router.query.id, retry]) useEffect(() => { @@ -136,6 +139,7 @@ export default function Code() { socketRef.current.on('disconnect', () => { if (!isViewOnly) { router.push('/') + toast.info('The session has ended') } }) @@ -203,7 +207,6 @@ export default function Code() { if (socketRef.current) { socketRef.current?.emit('end-session') router.push('/') - toast.info('The session has ended') } setIsDialogOpen(false) } diff --git a/frontend/pages/sessions/columns.tsx b/frontend/pages/sessions/columns.tsx index 1ac1c8172a..e4fe12f99b 100644 --- a/frontend/pages/sessions/columns.tsx +++ b/frontend/pages/sessions/columns.tsx @@ -3,6 +3,7 @@ import { DifficultyLabel } from '@/components/customs/difficulty-label' import { Button } from '@/components/ui/button' import CustomLabel from '@/components/ui/label' import { IDatatableColumn, IRowData } from '@/types' +import { encodeStr } from '@/util/encryption' import { capitalizeFirstLowerRest } from '@/util/string-modification' import { EyeIcon } from 'lucide-react' import { NextRouter } from 'next/router' @@ -77,7 +78,8 @@ export const columns: IDatatableColumn[] = [ variant="iconNoBorder" size="icon" onClick={() => { - router.push(`/code/${elem._id}`) + const encoded = encodeStr(elem._id) + router.push(`/code/${encoded}`) }} > {elem.isCompleted ? : } diff --git a/frontend/pages/sessions/index.tsx b/frontend/pages/sessions/index.tsx index 913bb4f212..7778b879eb 100644 --- a/frontend/pages/sessions/index.tsx +++ b/frontend/pages/sessions/index.tsx @@ -11,6 +11,7 @@ import { Button } from '@/components/ui/button' import ConfirmDialog, { ConfirmDialogProps } from '@/components/customs/confirm-dialog' import { getOngoingMatch } from '@/services/matching-service-api' import { useRouter } from 'next/router' +import { encodeStr } from '@/util/encryption' export default function Sessions() { const router = useRouter() @@ -111,7 +112,8 @@ export default function Sessions() { if (!session?.user?.id) return const matchData = await getOngoingMatch(session.user.id) if (matchData) { - router.push(`/code/${matchData.id}`) + const encodedId = encodeStr(matchData.id) + router.push(`/code/${encodedId}`) } else { setDialog((prev) => ({ ...prev, dialogData: { ...prev.dialogData, isOpen: true } })) } diff --git a/frontend/util/encryption.ts b/frontend/util/encryption.ts new file mode 100644 index 0000000000..7e1068a22b --- /dev/null +++ b/frontend/util/encryption.ts @@ -0,0 +1,7 @@ +export const encodeStr = (str: string) => { + return btoa(str) +} + +export const decodeStr = (encoded: string) => { + return atob(encoded) +} From df0610633a838f5290eb475a8cc462dfb620b78d Mon Sep 17 00:00:00 2001 From: yunruu Date: Wed, 13 Nov 2024 16:52:07 +0800 Subject: [PATCH 09/13] fix: undefined handling in string decoder --- frontend/util/encryption.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/util/encryption.ts b/frontend/util/encryption.ts index 7e1068a22b..58a66b278b 100644 --- a/frontend/util/encryption.ts +++ b/frontend/util/encryption.ts @@ -3,5 +3,6 @@ export const encodeStr = (str: string) => { } export const decodeStr = (encoded: string) => { + if (!encoded) return '' return atob(encoded) } From 4d0d75bee16c948f282a8dd26c932b3f0f4bc0b6 Mon Sep 17 00:00:00 2001 From: yunruu Date: Wed, 13 Nov 2024 17:43:39 +0800 Subject: [PATCH 10/13] feat: standardize primary buttons --- frontend/components/account/Profile.tsx | 2 +- frontend/components/account/Settings.tsx | 2 +- frontend/components/dashboard/new-session.tsx | 2 +- frontend/components/ui/button.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/components/account/Profile.tsx b/frontend/components/account/Profile.tsx index b17c4c1f17..ce3574deac 100644 --- a/frontend/components/account/Profile.tsx +++ b/frontend/components/account/Profile.tsx @@ -107,7 +107,7 @@ function Profile() { dialogOpen={isDialogOpen} onDialogOpenChange={manageDialog} // Allow toggling the dialog text="Update Profile" - className="w-fit bg-btn text-white text-sm py-2 px-4 rounded-md hover:bg-purple-700" + className="w-fit bg-btn text-white text-sm py-2 px-4 rounded-md" type="button" variant="primary" description="Are you sure you want to update your profile?" diff --git a/frontend/components/account/Settings.tsx b/frontend/components/account/Settings.tsx index c327bd4605..5113800068 100644 --- a/frontend/components/account/Settings.tsx +++ b/frontend/components/account/Settings.tsx @@ -120,7 +120,7 @@ function Setting() { dialogOpen={isUpdateDialogOpen} onDialogOpenChange={manageUpdateDialog} text="Update Settings" - className="w-fit bg-btn text-white text-sm py-2 px-4 rounded-md hover:bg-theme-700" + className="w-fit bg-btn text-white text-sm py-2 px-4 rounded-md" type="button" variant="primary" description="Are you sure you want to update your settings?" diff --git a/frontend/components/dashboard/new-session.tsx b/frontend/components/dashboard/new-session.tsx index c0fe41e72f..0f151a7931 100644 --- a/frontend/components/dashboard/new-session.tsx +++ b/frontend/components/dashboard/new-session.tsx @@ -206,7 +206,7 @@ export const NewSession = () => { - diff --git a/frontend/components/ui/button.tsx b/frontend/components/ui/button.tsx index 0c0187cd7b..12073ffa15 100644 --- a/frontend/components/ui/button.tsx +++ b/frontend/components/ui/button.tsx @@ -16,7 +16,7 @@ const buttonVariants = cva( link: 'text-primary underline-offset-4 hover:underline', icon: 'bg-transparent border-[1px] rounded-xl hover:bg-btn-secondaryHover', iconNoBorder: 'hover:bg-btn-hover', - primary: 'bg-theme-600 hover:bg-theme-700 text-primary-foreground', + primary: 'bg-theme-600 hover:bg-theme-700 text-primary-foreground focus-visible:ring-gray-500', activeTab: 'text-foreground bg-transparent hover:bg-btn-hover rounded-b-none border-b-2 border-theme-600', ghostTab: 'text-foreground bg-transparent hover:bg-btn-hover rounded-b-none', From fb8c4c482f7ca77f105d642f4290f8e7e892fe1e Mon Sep 17 00:00:00 2001 From: Glemen Neo Date: Sat, 9 Nov 2024 18:46:48 +0800 Subject: [PATCH 11/13] [#206] feat: add api suffix for all public api calls --- frontend/components/dashboard/new-session.tsx | 2 +- frontend/pages/api/auth/[...nextauth].ts | 2 +- frontend/pages/code/editor.tsx | 2 +- frontend/services/axios-middleware.ts | 10 ++++++---- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/frontend/components/dashboard/new-session.tsx b/frontend/components/dashboard/new-session.tsx index 0f151a7931..890ce3b82c 100644 --- a/frontend/components/dashboard/new-session.tsx +++ b/frontend/components/dashboard/new-session.tsx @@ -83,7 +83,7 @@ export const NewSession = () => { // Refactor this const wsUrl = (process.env.NEXT_PUBLIC_API_URL || 'ws://localhost:3006')?.concat( - `/matching/ws/?id=${websocketId}` + `/api/matching/ws/?id=${websocketId}` ) const socket = new WebSocket(wsUrl) setTimeout(() => { diff --git a/frontend/pages/api/auth/[...nextauth].ts b/frontend/pages/api/auth/[...nextauth].ts index c46d6be6b8..ccae3f1646 100644 --- a/frontend/pages/api/auth/[...nextauth].ts +++ b/frontend/pages/api/auth/[...nextauth].ts @@ -24,7 +24,7 @@ export default NextAuth({ try { const api = axios.create({ - baseURL: process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3002', + baseURL: process.env.NEXT_PUBLIC_API_URL?.concat('/api') ?? 'http://localhost:3002', }) const response = await api.post('/auth/login', { usernameOrEmail: username, password }) diff --git a/frontend/pages/code/editor.tsx b/frontend/pages/code/editor.tsx index f57cd5f51d..7e73465ffa 100644 --- a/frontend/pages/code/editor.tsx +++ b/frontend/pages/code/editor.tsx @@ -52,7 +52,7 @@ const CodeMirrorEditor = forwardRef(({ roomId, language }: IProps, ref) => { const token = session.user.accessToken if (!token) return undefined const wsProvider = new WebsocketProvider( - process.env.NEXT_PUBLIC_API_URL?.concat('/collab/y/ws') ?? 'ws://localhost:3008', + process.env.NEXT_PUBLIC_API_URL?.concat('/api/collab/y/ws') ?? 'ws://localhost:3008', roomId, ydoc, { diff --git a/frontend/services/axios-middleware.ts b/frontend/services/axios-middleware.ts index 7894b7d640..02d93b693d 100644 --- a/frontend/services/axios-middleware.ts +++ b/frontend/services/axios-middleware.ts @@ -38,9 +38,11 @@ const createServiceAPI = (baseURL: string) => { return api } -const userServiceAPI = createServiceAPI(process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3002') -const questionServiceAPI = createServiceAPI(process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3004') -const matchingServiceAPI = createServiceAPI(process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3006') -const collaborationServiceAPI = createServiceAPI(process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3008') +const userServiceAPI = createServiceAPI(process.env.NEXT_PUBLIC_API_URL?.concat('/api') ?? 'http://localhost:3002') +const questionServiceAPI = createServiceAPI(process.env.NEXT_PUBLIC_API_URL?.concat('/api') ?? 'http://localhost:3004') +const matchingServiceAPI = createServiceAPI(process.env.NEXT_PUBLIC_API_URL?.concat('/api') ?? 'http://localhost:3006') +const collaborationServiceAPI = createServiceAPI( + process.env.NEXT_PUBLIC_API_URL?.concat('/api') ?? 'http://localhost:3008' +) export default { userServiceAPI, questionServiceAPI, matchingServiceAPI, collaborationServiceAPI } From 2612094922e96cfaacce183b0c9e825de07e4c6c Mon Sep 17 00:00:00 2001 From: Glemen Neo Date: Sun, 10 Nov 2024 00:28:42 +0800 Subject: [PATCH 12/13] [#206] feat: add rewrite rule to strip /api prefixes --- nginx/templates/local-nginx.conf.template | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nginx/templates/local-nginx.conf.template b/nginx/templates/local-nginx.conf.template index ba0a8511ad..a6122d34be 100644 --- a/nginx/templates/local-nginx.conf.template +++ b/nginx/templates/local-nginx.conf.template @@ -18,6 +18,10 @@ http { proxy_set_header Upgrade ${D}http_upgrade; proxy_set_header Connection "upgrade"; + location /api/ { + rewrite ^/api(/.*)$ $1 last; + } + location /auth { proxy_pass ${USER_SERVICE_URL}; } From 6d5eca7fd0dd5688366d9e7fe8fa7024c6c2f32e Mon Sep 17 00:00:00 2001 From: yunruu Date: Wed, 13 Nov 2024 18:00:45 +0800 Subject: [PATCH 13/13] chore: remove unnecessary optional op --- frontend/pages/code/[id]/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/pages/code/[id]/index.tsx b/frontend/pages/code/[id]/index.tsx index 4df5d1be8a..87b5ba9bbd 100644 --- a/frontend/pages/code/[id]/index.tsx +++ b/frontend/pages/code/[id]/index.tsx @@ -205,7 +205,7 @@ export default function Code() { function handleEndSessionConfirmation() { if (socketRef.current) { - socketRef.current?.emit('end-session') + socketRef.current.emit('end-session') router.push('/') } setIsDialogOpen(false)