Skip to content

Commit

Permalink
feat(fe): add toggles in contest create (#1991)
Browse files Browse the repository at this point in the history
* feat(fe): add enable copy and hide result toggle in create contest

* feat(fe): add switchfield to edit contest page

* feat(fe): add error message in zod schema

* feat(fe): change popover to tooltip

---------

Co-authored-by: YooJin Lee <[email protected]>
  • Loading branch information
B0XERCAT and youznn authored Aug 26, 2024
1 parent 9b2cbbe commit 26bbd37
Show file tree
Hide file tree
Showing 10 changed files with 126 additions and 48 deletions.
23 changes: 13 additions & 10 deletions apps/frontend/app/admin/_components/SwitchField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface SwitchFieldProps {
name: string
title?: string
placeholder?: string
isInput?: boolean
formElement?: 'input' | 'textarea'
type?: string
hasValue?: boolean
}
Expand All @@ -21,15 +21,16 @@ export default function SwitchField({
name,
title,
placeholder,
isInput = false,
formElement,
type = 'text',
hasValue = false
}: SwitchFieldProps) {
const [isEnabled, setIsEnabled] = useState(false)
const [isEnabled, setIsEnabled] = useState<boolean>(false)
const {
register,
setValue,
trigger,
getValues,
formState: { errors }
} = useFormContext()

Expand All @@ -43,15 +44,17 @@ export default function SwitchField({
<Label required={false}>{title}</Label>
<Switch
onCheckedChange={() => {
if (name == 'invitationCode') setValue(name, null)
else if (name == 'hint' || name == 'source') setValue(name, '')
else setValue(name, !getValues(name))
setIsEnabled(!isEnabled)
setValue(name, name === 'invitationCode' ? null : '')
}}
checked={isEnabled}
className="data-[state=checked]:bg-black data-[state=unchecked]:bg-gray-300"
className="data-[state=checked]:bg-primary data-[state=unchecked]:bg-gray-300"
/>
</div>
{isEnabled &&
(isInput ? (
(formElement == 'input' ? (
<Input
id={name}
type={type}
Expand All @@ -65,16 +68,16 @@ export default function SwitchField({
onChange: () => trigger(name)
})}
/>
) : (
) : formElement == 'textarea' ? (
<Textarea
id={name}
placeholder={placeholder}
className="min-h-[120px] w-[760px] bg-white"
{...register(name)}
/>
))}
{isEnabled && errors['invitationCode'] && (
<ErrorMessage message="The invitation code must be a 6-digit number" />
) : null)}
{isEnabled && name == 'invitationCode' && errors[name] && (
<ErrorMessage message={errors[name]?.message?.toString()} />
)}
</div>
)
Expand Down
31 changes: 30 additions & 1 deletion apps/frontend/app/admin/contest/[id]/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ export default function Page({ params }: { params: { id: string } }) {
const [prevProblemIds, setPrevProblemIds] = useState<number[]>([])
const [problems, setProblems] = useState<ContestProblem[]>([])
const [isLoading, setIsLoading] = useState<boolean>(true)
const [enableCopyPaste, setEnableCopyPaste] = useState<boolean>(false)
const [isJudgeResultVisible, setIsJudgeResultVisible] =
useState<boolean>(false)
const [showInvitationCode, setShowInvitationCode] = useState<boolean>(false)
const { id } = params

Expand Down Expand Up @@ -67,20 +70,36 @@ export default function Page({ params }: { params: { id: string } }) {
setValue('endTime', new Date(contestFormData.endTime))
}
setValue('description', contestFormData.description)
setValue('enableCopyPaste', contestFormData.enableCopyPaste)
setValue('isJudgeResultVisible', contestFormData.isJudgeResultVisible)
setValue('invitationCode', contestFormData.invitationCode)
if (contestFormData.invitationCode) {
setShowInvitationCode(true)
}
if (contestFormData.enableCopyPaste) {
setEnableCopyPaste(true)
}
if (contestFormData.isJudgeResultVisible) {
setIsJudgeResultVisible(true)
}
} else {
const data = contestData.getContest
setValue('title', data.title)
setValue('description', data.description)
setValue('startTime', new Date(data.startTime))
setValue('endTime', new Date(data.endTime))
setValue('enableCopyPaste', data.enableCopyPaste)
setValue('isJudgeResultVisible', data.isJudgeResultVisible)
setValue('invitationCode', data.invitationCode)
if (data.invitationCode) {
setShowInvitationCode(true)
}
if (data.enableCopyPaste) {
setEnableCopyPaste(true)
}
if (data.isJudgeResultVisible) {
setIsJudgeResultVisible(true)
}
}
setIsLoading(false)
}
Expand Down Expand Up @@ -232,11 +251,21 @@ export default function Page({ params }: { params: { id: string } }) {
<DescriptionForm name="description" />
)}
</FormSection>
<SwitchField
name="enableCopyPaste"
title="Disable participants from Copy/Pasting"
hasValue={enableCopyPaste}
/>
<SwitchField
name="isJudgeResultVisible"
title="Hide scores from participants"
hasValue={isJudgeResultVisible}
/>
<SwitchField
name="invitationCode"
title="Invitation Code"
type="number"
isInput={true}
formElement="input"
placeholder="Enter a invitation code"
hasValue={showInvitationCode}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,38 @@
import {
Popover,
PopoverContent,
PopoverTrigger
} from '@/components/ui/popover'
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger
} from '@/components/ui/tooltip'
import { MdHelpOutline } from 'react-icons/md'
import Label from '../../_components/Label'

export default function ContestProblemListLabel() {
return (
<div className="flex items-center gap-2">
<Label>Contest Problem List</Label>
<Popover>
<PopoverTrigger asChild>
<button>
<MdHelpOutline className="text-gray-400 hover:text-gray-700" />
</button>
</PopoverTrigger>
<PopoverContent
side="top"
className="mb-2 w-[680px] bg-black px-4 py-2 text-white"
>
<ul className="text-xs font-normal">
<li>
The problems in the contest problem list are initially set to
&apos;not visible&apos; at the time of creating the contest
</li>
<li>
They become visible according to the specified start time and
remain inaccessible in the problem list
</li>
<li>
throughout the duration of the contest. After the contest period
ends, they become visible again in the problem list.
</li>
</ul>
</PopoverContent>
</Popover>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<button type="button">
<MdHelpOutline className="text-gray-400 hover:text-gray-700" />
</button>
</TooltipTrigger>
<TooltipContent
side="top"
className="mb-2 w-[640px] bg-white px-4 py-2 shadow-md"
>
<p className="text-xs font-normal text-black">
If a problem is included in at least one ongoing, or upcoming
contest, it will automatically become invisible state in the ‘All
Problem List’. You cannot change its visibility until all the
ongoing or upcoming contests it is part of have ended. After the
contests are all over, you can manually make the problem visible
again.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export default function ImportProblemButton({
startTime: getValues('startTime'),
endTime: getValues('endTime'),
description: getValues('description'),
enableCopyPaste: getValues('enableCopyPaste'),
isJudgeResultVisible: getValues('isJudgeResultVisible'),
invitationCode: getValues('invitationCode')
}
if (isCreatePage) {
Expand Down
27 changes: 25 additions & 2 deletions apps/frontend/app/admin/contest/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ export default function Page() {
const [problems, setProblems] = useState<ContestProblem[]>([])
const [isLoading, setIsLoading] = useState<boolean>(true)
const [isCreating, setIsCreating] = useState<boolean>(false)
const [enableCopyPaste, setEnableCopyPaste] = useState<boolean>(false)
const [isJudgeResultVisible, setIsJudgeResultVisible] =
useState<boolean>(false)
const [showInvitationCode, setShowInvitationCode] = useState<boolean>(false)

const router = useRouter()
Expand All @@ -40,7 +43,9 @@ export default function Page() {
resolver: zodResolver(createSchema),
defaultValues: {
isRankVisible: true,
isVisible: true
isVisible: true,
enableCopyPaste: false,
isJudgeResultVisible: false
}
})

Expand Down Expand Up @@ -131,6 +136,14 @@ export default function Page() {
setValue('endTime', new Date(contestFormData.endTime))
}
setValue('description', contestFormData.description)
if (contestFormData.enableCopyPaste) {
setValue('enableCopyPaste', contestFormData.enableCopyPaste)
setEnableCopyPaste(true)
}
if (contestFormData.isJudgeResultVisible) {
setValue('isJudgeResultVisible', contestFormData.isJudgeResultVisible)
setIsJudgeResultVisible(true)
}
if (contestFormData.invitationCode) {
setValue('invitationCode', contestFormData.invitationCode)
setShowInvitationCode(true)
Expand Down Expand Up @@ -181,11 +194,21 @@ export default function Page() {
<DescriptionForm name="description" />
)}
</FormSection>
<SwitchField
name="enableCopyPaste"
title="Disable participants from Copy/Pasting"
hasValue={enableCopyPaste}
/>
<SwitchField
name="isJudgeResultVisible"
title="Hide scores from participants"
hasValue={isJudgeResultVisible}
/>
<SwitchField
name="invitationCode"
title="Invitation Code"
type="number"
isInput={true}
formElement="input"
placeholder="Enter a invitation code"
hasValue={showInvitationCode}
/>
Expand Down
17 changes: 14 additions & 3 deletions apps/frontend/app/admin/contest/utils.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import { z } from 'zod'

export const createSchema = z.object({
title: z.string().min(1).max(100),
title: z
.string()
.min(1)
.max(100, 'The title can only be up to 100 characters long'),
isRankVisible: z.boolean(),
isVisible: z.boolean(),
description: z.string().min(1),
description: z
.string()
.min(1)
.refine((value) => value !== '<p></p>'),
startTime: z.date(),
endTime: z.date(),
invitationCode: z.string().min(6).max(6).nullish()
enableCopyPaste: z.boolean(),
isJudgeResultVisible: z.boolean(),
invitationCode: z
.string()
.regex(/^\d{6}$/, 'The invitation code must be a 6-digit number')
.nullish()
})

export const editSchema = createSchema.extend({
Expand Down
3 changes: 2 additions & 1 deletion apps/frontend/app/admin/problem/[id]/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,13 +225,14 @@ export default function Page({ params }: { params: { id: string } }) {
name="hint"
title="Hint"
placeholder="Enter a hint"
formElement="textarea"
hasValue={showHint}
/>
<SwitchField
name="source"
title="Source"
placeholder="Enter a source"
isInput
formElement="input"
hasValue={showSource}
/>
<TemplateField />
Expand Down
8 changes: 7 additions & 1 deletion apps/frontend/app/admin/problem/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,17 @@ export default function Page() {
<LimitForm />
</FormSection>

<SwitchField name="hint" title="Hint" placeholder="Enter a hint" />
<SwitchField
name="hint"
title="Hint"
placeholder="Enter a hint"
formElement="textarea"
/>
<SwitchField
name="source"
title="Source"
placeholder="Enter a source"
formElement="input"
/>

<TemplateField />
Expand Down
4 changes: 4 additions & 0 deletions apps/frontend/graphql/contest/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const CREATE_CONTEST = gql(`
invitationCode
isVisible
isRankVisible
enableCopyPaste
isJudgeResultVisible
description
endTime
startTime
Expand All @@ -22,6 +24,8 @@ const UPDATE_CONTEST = gql(`
invitationCode
isRankVisible
isVisible
enableCopyPaste
isJudgeResultVisible
description
endTime
startTime
Expand Down
2 changes: 2 additions & 0 deletions apps/frontend/graphql/contest/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const GET_CONTEST = gql(`
query GetContest($contestId: Int!) {
getContest(contestId: $contestId) {
id
enableCopyPaste
isJudgeResultVisible
invitationCode
description
endTime
Expand Down

0 comments on commit 26bbd37

Please sign in to comment.