Skip to content

Commit

Permalink
feat(fe): implement notice create and detail page (#2248)
Browse files Browse the repository at this point in the history
* feat(fe): add create notice gql mutation

* feat(fe): implement notice create page

* feat(fe): add simple notice detail page

* fix(fe): replace sanitize with KatexContent

* chore(fe): route to detail page after creating notice
  • Loading branch information
B0XERCAT authored Nov 28, 2024
1 parent b8b63ea commit 68d949c
Show file tree
Hide file tree
Showing 11 changed files with 244 additions and 7 deletions.
8 changes: 4 additions & 4 deletions apps/frontend/app/(client)/(main)/notice/[noticeId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import KatexContent from '@/components/KatexContent'
import { baseUrl } from '@/libs/constants'
import { dateFormatter } from '@/libs/utils'
import { sanitize } from 'isomorphic-dompurify'
import Link from 'next/link'
import { RxHamburgerMenu } from 'react-icons/rx'

Expand Down Expand Up @@ -34,9 +34,9 @@ export default async function NoticeDetail({
<p>{dateFormatter(createTime, 'YYYY-MM-DD')}</p>
</div>
</header>
<main
className="prose w-full max-w-full p-5 py-12"
dangerouslySetInnerHTML={{ __html: sanitize(content) }}
<KatexContent
content={content}
classname="prose w-full max-w-full p-5 py-12"
/>
<footer className="flex flex-col">
<div className="flex justify-end border-b border-b-gray-200 py-1">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useFormContext, useController } from 'react-hook-form'
import { FaEye, FaEyeSlash } from 'react-icons/fa'
import ErrorMessage from '../../_components/ErrorMessage'
import ErrorMessage from './ErrorMessage'

export default function VisibleForm({
blockEdit = false
Expand Down
39 changes: 39 additions & 0 deletions apps/frontend/app/admin/notice/[noticeId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use client'

import KatexContent from '@/components/KatexContent'
import { ScrollArea, ScrollBar } from '@/components/shadcn/scroll-area'
import { GET_NOTICE } from '@/graphql/notice/queries'
import { useQuery } from '@apollo/client'
import Link from 'next/link'
import { FaAngleLeft } from 'react-icons/fa6'

export default function Page({ params }: { params: { noticeId: string } }) {
const { noticeId } = params

const noticeData = useQuery(GET_NOTICE, {
variables: {
groupId: 1,
noticeId: Number(noticeId)
}
}).data?.getNotice

return (
<ScrollArea className="shrink-0">
<main className="flex flex-col gap-6 px-20 py-16">
<div className="flex items-center justify-between">
<div className="flex items-center gap-4">
<Link href="/admin/notice">
<FaAngleLeft className="h-12 hover:text-gray-700/80" />
</Link>
<span className="text-4xl font-bold">{noticeData?.title}</span>
</div>
</div>
<KatexContent
content={noticeData?.content}
classname="prose mb-12 w-full max-w-full border-y-2 border-y-gray-300 p-5 py-12"
/>
</main>
<ScrollBar orientation="horizontal" />
</ScrollArea>
)
}
58 changes: 58 additions & 0 deletions apps/frontend/app/admin/notice/_components/CreateNoticeForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
'use client'

import { CREATE_NOTICE } from '@/graphql/notice/mutation'
import { useMutation } from '@apollo/client'
import type { CreateNoticeInput } from '@generated/graphql'
import { zodResolver } from '@hookform/resolvers/zod'
import { useRouter } from 'next/navigation'
import type { ReactNode } from 'react'
import { FormProvider, useForm } from 'react-hook-form'
import { toast } from 'sonner'
import { createSchema } from '../_libs/schemas'

interface CreateNoticeFormProps {
children: ReactNode
}

export default function CreateNoticeForm({ children }: CreateNoticeFormProps) {
const methods = useForm<CreateNoticeInput>({
resolver: zodResolver(createSchema),
defaultValues: {
title: '',
content: '',
isFixed: false,
isVisible: true
}
})
const router = useRouter()

const [createNotice] = useMutation(CREATE_NOTICE, {
onError: () => {
toast.error('Failed to create problem')
},
onCompleted: (data) => {
const noticeId = data.createNotice.id
toast.success('Notice created successfully')
router.push(`/admin/notice/${noticeId}`)
router.refresh()
}
})

const onSubmit = methods.handleSubmit(async () => {
const noticeInput = methods.getValues()
await createNotice({
variables: {
groupId: 1,
noticeInput
}
})
})

return (
<>
<form className="flex w-[760px] flex-col gap-6" onSubmit={onSubmit}>
<FormProvider {...methods}>{children}</FormProvider>
</form>
</>
)
}
54 changes: 54 additions & 0 deletions apps/frontend/app/admin/notice/_components/FixedForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use client'

import { useFormContext, useController } from 'react-hook-form'
import { TbPinned, TbPinnedOff } from 'react-icons/tb'
import ErrorMessage from '../../_components/ErrorMessage'

export default function FixedForm({
blockEdit = false
}: {
blockEdit?: boolean
}) {
const {
control,
formState: { errors }
} = useFormContext()

const { field: isFixedField } = useController({
name: 'isFixed',
control,
defaultValue: true
})

return (
<>
<div className="mt-3 flex items-center gap-2">
<div className="flex gap-14">
<label className="flex gap-2">
<input
type="radio"
onBlur={isFixedField.onBlur}
onChange={() => isFixedField.onChange(true)}
checked={isFixedField.value === true}
className="text-primary-light"
disabled={blockEdit}
/>
<TbPinned className="text-gray-400" size={20} />
</label>
<label className="flex gap-2">
<input
type="radio"
onBlur={isFixedField.onBlur}
onChange={() => isFixedField.onChange(false)}
checked={isFixedField.value === false}
className="text-primary-light"
disabled={blockEdit}
/>
<TbPinnedOff className="text-gray-400" size={20} />
</label>
</div>
</div>
{errors.isFixed && <ErrorMessage message="required" />}
</>
)
}
8 changes: 8 additions & 0 deletions apps/frontend/app/admin/notice/_libs/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { z } from 'zod'

export const createSchema = z.object({
title: z.string().min(1),
content: z.string().min(1),
isFixed: z.boolean(),
isVisible: z.boolean()
})
55 changes: 55 additions & 0 deletions apps/frontend/app/admin/notice/create/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Button } from '@/components/shadcn/button'
import { ScrollArea, ScrollBar } from '@/components/shadcn/scroll-area'
import Link from 'next/link'
import { FaAngleLeft } from 'react-icons/fa6'
import { IoMdCheckmarkCircleOutline } from 'react-icons/io'
import DescriptionForm from '../../_components/DescriptionForm'
import FormSection from '../../_components/FormSection'
import TitleForm from '../../_components/TitleForm'
import VisibleForm from '../../_components/VisibleForm'
import CreateNoticeForm from '../_components/CreateNoticeForm'
import FixedForm from '../_components/FixedForm'

export default function Page() {
return (
<ScrollArea className="shrink-0">
<main className="flex flex-col gap-6 px-20 py-16">
<div className="-ml-8 flex items-center gap-4">
<Link href="/admin/problem">
<FaAngleLeft className="h-12 hover:text-gray-700/80" />
</Link>
<span className="text-4xl font-bold">Create Notice</span>
</div>

<CreateNoticeForm>
<div className="flex gap-32">
<FormSection title="Visible">
<VisibleForm />
</FormSection>

<FormSection title="Fixed">
<FixedForm />
</FormSection>
</div>

<FormSection title="Title">
<TitleForm placeholder="Enter a notice title" />
</FormSection>

<FormSection title="Content">
<DescriptionForm name="content" />
</FormSection>

<Button
type="submit"
className="flex h-[36px] w-[100px] items-center gap-2 px-0"
>
<IoMdCheckmarkCircleOutline fontSize={20} />
<div className="mb-[2px] text-base">Create</div>
</Button>
</CreateNoticeForm>
</main>
<ScrollBar orientation="horizontal" />
</ScrollArea>
)
}
2 changes: 1 addition & 1 deletion apps/frontend/app/admin/problem/[problemId]/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ import DescriptionForm from '../../../_components/DescriptionForm'
import FormSection from '../../../_components/FormSection'
import SwitchField from '../../../_components/SwitchField'
import TitleForm from '../../../_components/TitleForm'
import VisibleForm from '../../../_components/VisibleForm'
import { CautionDialog } from '../../_components/CautionDialog'
import InfoForm from '../../_components/InfoForm'
import LimitForm from '../../_components/LimitForm'
import PopoverVisibleInfo from '../../_components/PopoverVisibleInfo'
import TemplateField from '../../_components/TemplateField'
import TestcaseField from '../../_components/TestcaseField'
import VisibleForm from '../../_components/VisibleForm'
import { editSchema } from '../../_libs/schemas'
import { validateScoreWeight } from '../../_libs/utils'
import { ScoreCautionDialog } from './_components/ScoreCautionDialog'
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/app/admin/problem/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import DescriptionForm from '../../_components/DescriptionForm'
import FormSection from '../../_components/FormSection'
import SwitchField from '../../_components/SwitchField'
import TitleForm from '../../_components/TitleForm'
import VisibleForm from '../../_components/VisibleForm'
import InfoForm from '../_components/InfoForm'
import LimitForm from '../_components/LimitForm'
import PopoverVisibleInfo from '../_components/PopoverVisibleInfo'
import TemplateField from '../_components/TemplateField'
import TestcaseField from '../_components/TestcaseField'
import VisibleForm from '../_components/VisibleForm'
import CreateProblemForm from './_components/CreateProblemForm'

export default function Page() {
Expand Down
11 changes: 11 additions & 0 deletions apps/frontend/graphql/notice/mutation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { gql } from '@generated'

const CREATE_NOTICE = gql(`
mutation CreateNotice($groupId: Int!, $noticeInput: CreateNoticeInput!) {
createNotice(groupId: $groupId, input: $noticeInput) {
id
}
}
`)

export { CREATE_NOTICE }
12 changes: 12 additions & 0 deletions apps/frontend/graphql/notice/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { gql } from '@apollo/client'

const GET_NOTICE = gql(`
query GetNotice($groupId: Int!, $noticeId: Int!) {
getNotice(groupId: $groupId, noticeId: $noticeId) {
title
content
}
}
`)

export { GET_NOTICE }

0 comments on commit 68d949c

Please sign in to comment.