Skip to content

Commit

Permalink
feat(fe): show modal when leaving create and edit page (#2031)
Browse files Browse the repository at this point in the history
* feat(fe): show modal in admin contest

* feat(fe): add contest create modal

* feat(fe): add alert to contest create and edit

* feat(be): add get-contest-submission-informations api (#1894)

* feat(be): add contest-submission-result model

* chore(be): rename contest-submission-result to contest-submission-information

* chore(be): set nullability of fields in contest-submission-information

* feat(be): add get-contest-submission-informations api

* chore(be): rename

* test(be): add test

* docs(be): add docs

* chore(be): rename files and add fields on contest-submission-summary-for-one

* feat(be): implement combine score summary and submissions

* chore(be): comment test

* feat(be): add problem-id option

* fix(be): fix wrong type

* docs(be): rename files

* docs(be): rename file

* docs(be): rename files

* fix(be): fix ContestSubmissionSummaryForOne model

* docs(be): remove resolved todo

* chore(be): lint contest.service.spec

* fix(be): don't throw error when no submission

---------

Co-authored-by: 강민석 <min.seok@g.skku.edu>
Co-authored-by: jimin9038 <jimin9038@g.skku.edu>

* fix(fe): redirect to home when user logged out (#2027)

* feat(infra): add capacity provider strategy to ecs cluster (#2026)

* feat(infra): add capacity provider strategy to ecs cluster

* feat(infra): modify weight of capacity provider strategy to 1

* feat(fe): edit create problem page (#1923)

* feat(fe): edit popover text and padding

* chore(fe): add space between title and visible, add left margin to angleleft

* chore(fe): add color to radio button, adjust gaps

* feat(fe): change checkbox design, delete tag select, change box order

* chore(fe): change toggle color, move template field

* chore(fe): move title, add space between title and visible, move template up hint and source

* fix(fe): comment out tags and get tag

* chore(fe): delete comment about tag feature

* chore(fe): delete comments

* chore(fe): delete comments

---------

Co-authored-by: Jiho Park <59248080+jihorobert@users.noreply.github.com>

* chore(fe): hide test button (#2028)

Co-authored-by: Jiho Park <59248080+jihorobert@users.noreply.github.com>

* chore(fe): edit admin contest toggle title text (#2030)

* feat(be): implement getContestScoreSummaries api (#2029)

* feat(be): implement getContestScoreSummaries

* fix(be): make userContestScoreSummaryWithUserInfo model to return user

* docs(be): getcontestscoresummaries

* fix(be): rename get contest score api

* feat(be): sperate testcases into sample or hidden (#2000)

* feat(be): delete example-io and add is-hidden-testcase field

* feat(be): modify get-problem api to return testcases

* docs(be): add is-hidden-testcase

* fix(be): rename variable name of problem-response

* docs(be): add problem-testcase field

* docs(be): add todo on create-testcases
- TODO: 테스트케이스 저장 방식 S3 => DB 직접 저장으로 변경 시 함수 삭제

* chore(be): change is-hidden-testcase to is-hidden

* fix(fe): erase sample in admin, change samples to problem testcase in client

---------

Co-authored-by: b0xercat <seojin3154@naver.com>
Co-authored-by: jimin9038 <jimin9038@g.skku.edu>

* feat(fe): add contest create modal

* feat(fe): add modal to problem create and edit

* feat(fe): make confirmnavigation reusable

* fix(fe): disable confirm when creating

---------

Co-authored-by: Jaehyeon Kim <kjhkk1020@naver.com>
Co-authored-by: 강민석 <min.seok@g.skku.edu>
Co-authored-by: jimin9038 <jimin9038@g.skku.edu>
Co-authored-by: YooJin Lee <113789141+youznn@users.noreply.github.com>
Co-authored-by: Eunsu Kang <56429615+ssupecial@users.noreply.github.com>
Co-authored-by: 박주형 <80684730+juhyeong0505@users.noreply.github.com>
Co-authored-by: Jiho Park <59248080+jihorobert@users.noreply.github.com>
Co-authored-by: Kwon Seo Jin <97675977+B0XERCAT@users.noreply.github.com>
Co-authored-by: b0xercat <seojin3154@naver.com>
  • Loading branch information
10 people authored Aug 30, 2024
1 parent 07f38e1 commit b806a31
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 11 deletions.
37 changes: 37 additions & 0 deletions apps/frontend/app/admin/_components/ConfirmNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useRouter } from 'next/navigation'
import type { MutableRefObject } from 'react'
import { useEffect } from 'react'

export const useConfirmNavigation = (
shouldSkipWarningRef: MutableRefObject<boolean>
) => {
const router = useRouter()
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
event.preventDefault()
event.returnValue = ''
}

useEffect(() => {
window.addEventListener('beforeunload', handleBeforeUnload)

const originalPush = router.push

router.push = (href, ...args) => {
if (shouldSkipWarningRef.current) {
originalPush(href, ...args)
return
}
const shouldWarn = window.confirm(
'Are you sure you want to leave this page? Changes you made may not be saved.'
)
if (shouldWarn) {
originalPush(href, ...args)
}
}

return () => {
window.removeEventListener('beforeunload', handleBeforeUnload)
router.push = originalPush
}
}, [router, shouldSkipWarningRef.current])
}
8 changes: 7 additions & 1 deletion apps/frontend/app/admin/contest/[id]/edit/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

import { useConfirmNavigation } from '@/app/admin/_components/ConfirmNavigation'
import DescriptionForm from '@/app/admin/_components/DescriptionForm'
import FormSection from '@/app/admin/_components/FormSection'
import SwitchField from '@/app/admin/_components/SwitchField'
Expand Down Expand Up @@ -38,7 +39,7 @@ import { zodResolver } from '@hookform/resolvers/zod'
import { PlusCircleIcon } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useState } from 'react'
import { useRef, useState } from 'react'
import { FormProvider, useForm } from 'react-hook-form'
import { FaAngleLeft } from 'react-icons/fa6'
import { IoIosCheckmarkCircle } from 'react-icons/io'
Expand All @@ -60,8 +61,11 @@ export default function Page({ params }: { params: { id: string } }) {
const [showImportDialog, setShowImportDialog] = useState<boolean>(false)
const { id } = params

const shouldSkipWarning = useRef(false)
const router = useRouter()

useConfirmNavigation(shouldSkipWarning)

const methods = useForm<UpdateContestInput>({
resolver: zodResolver(editSchema),
defaultValues: {
Expand Down Expand Up @@ -178,6 +182,8 @@ export default function Page({ params }: { params: { id: string } }) {
orders: orderArray
}
})

shouldSkipWarning.current = true
toast.success('Contest updated successfully')
router.push('/admin/contest')
router.refresh()
Expand Down
49 changes: 44 additions & 5 deletions apps/frontend/app/admin/contest/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ import { zodResolver } from '@hookform/resolvers/zod'
import { PlusCircleIcon } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useState } from 'react'
import { useState, useRef } from 'react'
import { useForm, FormProvider } from 'react-hook-form'
import { FaAngleLeft } from 'react-icons/fa6'
import { IoMdCheckmarkCircleOutline } from 'react-icons/io'
import { toast } from 'sonner'
import { useConfirmNavigation } from '../../_components/ConfirmNavigation'
import DescriptionForm from '../../_components/DescriptionForm'
import FormSection from '../../_components/FormSection'
import SwitchField from '../../_components/SwitchField'
Expand All @@ -50,9 +51,13 @@ export default function Page() {
const [problems, setProblems] = useState<ContestProblem[]>([])
const [isCreating, setIsCreating] = useState<boolean>(false)
const [showImportDialog, setShowImportDialog] = useState<boolean>(false)
const [showCreateModal, setShowCreateModal] = useState(false)

const shouldSkipWarning = useRef(false)
const router = useRouter()

useConfirmNavigation(shouldSkipWarning)

const methods = useForm<CreateContestInput>({
resolver: zodResolver(createSchema),
defaultValues: {
Expand All @@ -71,7 +76,7 @@ export default function Page() {
UPDATE_CONTEST_PROBLEMS_ORDER
)

const onSubmit = async (input: CreateContestInput) => {
const isSubmittable = (input: CreateContestInput) => {
if (input.startTime >= input.endTime) {
toast.error('Start time must be less than end time')
return
Expand All @@ -83,8 +88,13 @@ export default function Page() {
toast.error('Duplicate problem order found')
return
}
setShowCreateModal(true)
}

const onSubmit = async () => {
const input = methods.getValues()
setIsCreating(true)

const { data } = await createContest({
variables: {
groupId: 1,
Expand All @@ -110,7 +120,6 @@ export default function Page() {
})
}
})

const orderArray = problems
.sort((a, b) => a.order - b.order)
.map((problem) => problem.id)
Expand All @@ -121,6 +130,8 @@ export default function Page() {
orders: orderArray
}
})

shouldSkipWarning.current = true
toast.success('Contest created successfully')
router.push('/admin/contest')
router.refresh()
Expand All @@ -136,7 +147,7 @@ export default function Page() {
<span className="text-4xl font-bold">Create Contest</span>
</div>
<form
onSubmit={handleSubmit(onSubmit)}
onSubmit={handleSubmit(isSubmittable)}
className="flex w-[760px] flex-col gap-6"
>
<FormProvider {...methods}>
Expand Down Expand Up @@ -238,11 +249,39 @@ export default function Page() {
<Button
type="submit"
className="flex h-[36px] w-[100px] items-center gap-2 px-0"
disabled={isCreating}
>
<IoMdCheckmarkCircleOutline fontSize={20} />
<div className="mb-[2px] text-base">Create</div>
</Button>
<AlertDialog open={showCreateModal}>
<AlertDialogContent className="p-8">
<AlertDialogHeader className="gap-2">
<AlertDialogTitle>Create Contest?</AlertDialogTitle>
<AlertDialogDescription>
Once user submit any coding, the contest problem list and
score <span className="underline">cannot</span> be modified.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel
type="button"
className="rounded-md px-4 py-2"
onClick={() => setShowCreateModal(false)}
>
Cancel
</AlertDialogCancel>
<AlertDialogAction asChild>
<Button
type="button"
disabled={isCreating}
onClick={() => onSubmit()}
>
Create
</Button>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</FormProvider>
</form>
</main>
Expand Down
7 changes: 6 additions & 1 deletion apps/frontend/app/admin/problem/[id]/edit/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use client'

import { useConfirmNavigation } from '@/app/admin/_components/ConfirmNavigation'
import { Button } from '@/components/ui/button'
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'
import { UPDATE_PROBLEM } from '@/graphql/problem/mutations'
Expand All @@ -9,7 +10,7 @@ import type { Template, Testcase, UpdateProblemInput } from '@generated/graphql'
import { zodResolver } from '@hookform/resolvers/zod'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useState } from 'react'
import { useRef, useState } from 'react'
import { useForm, FormProvider } from 'react-hook-form'
import { FaAngleLeft } from 'react-icons/fa6'
import { IoIosCheckmarkCircle } from 'react-icons/io'
Expand All @@ -30,8 +31,11 @@ import { editSchema } from '../../utils'

export default function Page({ params }: { params: { id: string } }) {
const { id } = params
const shouldSkipWarning = useRef(false)
const router = useRouter()

useConfirmNavigation(shouldSkipWarning)

const methods = useForm<UpdateProblemInput>({
resolver: zodResolver(editSchema),
defaultValues: {
Expand Down Expand Up @@ -124,6 +128,7 @@ export default function Page({ params }: { params: { id: string } }) {
toast.error('Failed to update problem')
return
}
shouldSkipWarning.current = true
toast.success('Succesfully updated problem')
router.push('/admin/problem')
router.refresh()
Expand Down
56 changes: 52 additions & 4 deletions apps/frontend/app/admin/problem/create/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
'use client'

import {
AlertDialog,
AlertDialogContent,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogCancel,
AlertDialogAction
} from '@/components/ui/alert-dialog'
import { Button } from '@/components/ui/button'
import { ScrollArea, ScrollBar } from '@/components/ui/scroll-area'
import { CREATE_PROBLEM } from '@/graphql/problem/mutations'
Expand All @@ -8,11 +18,12 @@ import { Level, type CreateProblemInput } from '@generated/graphql'
import { zodResolver } from '@hookform/resolvers/zod'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useState } from 'react'
import { useRef, useState } from 'react'
import { useForm, FormProvider } from 'react-hook-form'
import { FaAngleLeft } from 'react-icons/fa6'
import { IoMdCheckmarkCircleOutline } from 'react-icons/io'
import { toast } from 'sonner'
import { useConfirmNavigation } from '../../_components/ConfirmNavigation'
import DescriptionForm from '../../_components/DescriptionForm'
import FormSection from '../../_components/FormSection'
import SwitchField from '../../_components/SwitchField'
Expand All @@ -31,9 +42,13 @@ export default function Page() {
const [isCreating, setIsCreating] = useState(false)
const [isDialogOpen, setDialogOpen] = useState<boolean>(false)
const [dialogDescription, setDialogDescription] = useState<string>('')
const [showCreateModal, setShowCreateModal] = useState(false)

const shouldSkipWarning = useRef(false)
const router = useRouter()

useConfirmNavigation(shouldSkipWarning)

const methods = useForm<CreateProblemInput>({
resolver: zodResolver(createSchema),
defaultValues: {
Expand All @@ -53,8 +68,8 @@ export default function Page() {
const { handleSubmit, getValues } = methods

const [createProblem, { error }] = useMutation(CREATE_PROBLEM)

const onSubmit = async (input: CreateProblemInput) => {
const onSubmit = async () => {
const input = methods.getValues()
setIsCreating(true)
const testcases = getValues('testcases')
if (validateScoreWeight(testcases) === false) {
Expand All @@ -76,6 +91,8 @@ export default function Page() {
setIsCreating(false)
return
}

shouldSkipWarning.current = true
toast.success('Problem created successfully')
router.push('/admin/problem')
router.refresh()
Expand All @@ -92,7 +109,9 @@ export default function Page() {
</div>

<form
onSubmit={handleSubmit(onSubmit)}
onSubmit={handleSubmit(() => {
setShowCreateModal(true)
})}
className="flex w-[760px] flex-col gap-6"
>
<FormProvider {...methods}>
Expand Down Expand Up @@ -153,6 +172,35 @@ export default function Page() {
<IoMdCheckmarkCircleOutline fontSize={20} />
<div className="mb-[2px] text-base">Create</div>
</Button>
<AlertDialog open={showCreateModal}>
<AlertDialogContent className="p-8">
<AlertDialogHeader className="gap-2">
<AlertDialogTitle>Create Contest?</AlertDialogTitle>
<AlertDialogDescription>
Once user submit any coding, the testcases{' '}
<span className="underline">cannot</span> be modified.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel
type="button"
className="rounded-md px-4 py-2"
onClick={() => setShowCreateModal(false)}
>
Cancel
</AlertDialogCancel>
<AlertDialogAction asChild>
<Button
type="button"
disabled={isCreating}
onClick={() => onSubmit()}
>
Create
</Button>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</FormProvider>
</form>
</main>
Expand Down

0 comments on commit b806a31

Please sign in to comment.