diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/register/page.tsx b/apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/register/page.tsx new file mode 100644 index 000000000..e4c9b57b1 --- /dev/null +++ b/apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/register/page.tsx @@ -0,0 +1,2 @@ +import { CreatePage } from "@/pages/notice"; +export default CreatePage; diff --git a/apps/common/features/src/app/providers/app-provider.tsx b/apps/common/features/src/app/providers/app-provider.tsx index eaf0463a3..afe4ee651 100644 --- a/apps/common/features/src/app/providers/app-provider.tsx +++ b/apps/common/features/src/app/providers/app-provider.tsx @@ -2,6 +2,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ConfirmProvider } from "@frontend-opensource/use-react-hooks"; +import { AuthWrapper } from "@/entities/auth"; import { MenuWrapper } from "@/entities/menu"; import { StoreProvider } from "@/shared/providers"; import { AlertDialog } from "@/shared/ui/alert-dialog"; @@ -17,9 +18,11 @@ const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => { - {children} - - + + {children} + + + diff --git a/apps/common/features/src/entities/auth/index.ts b/apps/common/features/src/entities/auth/index.ts index 8c7981ac9..97e142225 100644 --- a/apps/common/features/src/entities/auth/index.ts +++ b/apps/common/features/src/entities/auth/index.ts @@ -1 +1,2 @@ export { LoginApi } from "./api/login-service"; +export { AuthWrapper } from "./lib/auth-wrapper"; diff --git a/apps/common/features/src/entities/auth/lib/auth-wrapper.tsx b/apps/common/features/src/entities/auth/lib/auth-wrapper.tsx new file mode 100644 index 000000000..6af88ae96 --- /dev/null +++ b/apps/common/features/src/entities/auth/lib/auth-wrapper.tsx @@ -0,0 +1,40 @@ +"use client"; + +import { useEffect } from "react"; +import { client } from "@/shared/api"; +import type { IUserInfo } from "@/shared/store"; +import { getCookie, useUserStore } from "@/shared/lib"; +import { ENDPOINT, AUTH_USER_ID } from "@/shared/config"; +import { LoginApi } from "../api/login-service"; + +const AuthWrapper = ({ children }: { children: React.ReactNode }) => { + const { userInfo, setUserInfo } = useUserStore((state) => state); + + async function authCheck() { + const userId = getCookie(AUTH_USER_ID); + if (!userId) { + throw new Error("Missing required authentication tokens"); + } + + const response = await client.get( + `${ENDPOINT.USER_SERVICE.USERS}/${userId}`, + ); + if (!userInfo) setUserInfo(response.data); + } + + useEffect(() => { + (async () => { + try { + await authCheck(); + } catch (error) { + console.error(error); + await LoginApi.silentRefresh(); + await authCheck(); + } + })(); + }, []); + + return <>{children}>; +}; + +export { AuthWrapper }; diff --git a/apps/common/features/src/entities/code/ui/code-select.tsx b/apps/common/features/src/entities/code/ui/code-select.tsx index bd175a8a9..441f334e3 100644 --- a/apps/common/features/src/entities/code/ui/code-select.tsx +++ b/apps/common/features/src/entities/code/ui/code-select.tsx @@ -11,9 +11,15 @@ import { CodeApi } from "../api/code-service"; type CodeSelectProps = { upCd: string; + placeholder?: string; } & React.ComponentPropsWithoutRef; -function CodeSelect({ upCd, defaultValue, ...props }: CodeSelectProps) { +function CodeSelect({ + upCd, + defaultValue, + placeholder = "선택", + ...props +}: CodeSelectProps) { const { data: code } = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.CODE, upCd], queryFn: () => CodeApi.getCode(upCd), @@ -21,7 +27,7 @@ function CodeSelect({ upCd, defaultValue, ...props }: CodeSelectProps) { }); const defaultValueText = - code?.find((item) => item.lwpoCd === defaultValue)?.lwpoCdNm ?? "선택"; + code?.find((item) => item.lwpoCd === defaultValue)?.lwpoCdNm ?? placeholder; return ( diff --git a/apps/common/features/src/entities/posting/ui/posting-dialog.tsx b/apps/common/features/src/entities/posting/ui/posting-dialog.tsx index bbddf9778..b0e155c44 100644 --- a/apps/common/features/src/entities/posting/ui/posting-dialog.tsx +++ b/apps/common/features/src/entities/posting/ui/posting-dialog.tsx @@ -96,11 +96,13 @@ function PostingDialog({ postingType, postingTargets }: PostingDialogProps) { const isAllSelected = siteList && selectedRows.length === siteList.content.length; - const postingPeriod = (siteId: string) => { - const periodCode = Number(rememberPeriodCode[siteId]); + const postingPeriodFormat = (siteId: string) => { + const periodCode = parseInt(rememberPeriodCode[siteId] ?? ""); + return postingPeriod(periodCode); + }; + const postingPeriod = (periodCode: number) => { if (periodCode === 99) return "상시"; - return `${dayjs().format("YYYY-MM-DD")} ~ ${dayjs() .add(periodCode, "day") .format("YYYY-MM-DD")}`; @@ -175,7 +177,9 @@ function PostingDialog({ postingType, postingTargets }: PostingDialogProps) { const handlePeriodCodeChange = (siteId: string) => (value: string) => { const from = dayjs().format("YYYY-MM-DD 00:00:00"); - const to = dayjs().add(Number(value), "day").format("YYYY-MM-DD 23:59:59"); + const to = dayjs() + .add(parseInt(value), "day") + .format("YYYY-MM-DD 23:59:59"); setRememberPeriodCode((prev) => ({ ...prev, @@ -272,17 +276,18 @@ function PostingDialog({ postingType, postingTargets }: PostingDialogProps) { onValueChange={handlePeriodCodeChange(site.siteId)} disabled={!selectedRows.includes(i)} /> - {!!Number(rememberPeriodCode[site.siteId]) && ( + {!!parseInt(rememberPeriodCode[site.siteId] ?? "") && ( - 게시 기간: {postingPeriod(site.siteId)} + 게시 기간: {postingPeriodFormat(site.siteId)} )} - {Number(rememberPeriodCode[site.siteId]) === 0 && ( + {parseInt(rememberPeriodCode[site.siteId] ?? "") === + 0 && ( handlePeriodDateChange(site.siteId)( diff --git a/apps/common/features/src/pages/notice/index.ts b/apps/common/features/src/pages/notice/index.ts index f904f2695..13b188dcf 100644 --- a/apps/common/features/src/pages/notice/index.ts +++ b/apps/common/features/src/pages/notice/index.ts @@ -1,3 +1,4 @@ export { NoticePage } from "./ui/notice-page"; export { NoticePost } from "./ui/notice-post"; export { ModifyPage } from "./ui/modify-page"; +export { CreatePage } from "./ui/create-page"; diff --git a/apps/common/features/src/pages/notice/ui/create-page.tsx b/apps/common/features/src/pages/notice/ui/create-page.tsx index 7e49ac6f4..e525a5949 100644 --- a/apps/common/features/src/pages/notice/ui/create-page.tsx +++ b/apps/common/features/src/pages/notice/ui/create-page.tsx @@ -4,14 +4,15 @@ import { useRouter } from "next/navigation"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation } from "@tanstack/react-query"; -import { useAlertStore } from "@/shared/lib/use-alert-store"; import { postFormSchema, type PostFormData } from "@/entities/post"; import { PostApi } from "@/entities/post"; +import { useAlertStore, useUserStore } from "@/shared/lib"; import { NoticeForm } from "./notice-form"; import { NoticeLayout } from "./notice-layout"; function CreatePage() { const router = useRouter(); + const { userInfo } = useUserStore((state) => state); const { setMessage: alert } = useAlertStore((state) => state); const { mutateAsync: createPost } = useMutation({ @@ -23,10 +24,14 @@ function CreatePage() { const form = useForm({ resolver: zodResolver(postFormSchema), - defaultValues: { + values: { bbsId: 0, pstTtl: "", - pstPswd: "", + wrtrNm: userInfo?.userName ?? "", + pstPswd: userInfo?.userUnqId ?? "", + popupYn: "N", + upndFixYn: "N", + prdPstgYn: "N", }, }); diff --git a/apps/common/features/src/pages/notice/ui/notice-form.tsx b/apps/common/features/src/pages/notice/ui/notice-form.tsx index cacc68787..c52740f56 100644 --- a/apps/common/features/src/pages/notice/ui/notice-form.tsx +++ b/apps/common/features/src/pages/notice/ui/notice-form.tsx @@ -1,5 +1,7 @@ +import dayjs from "dayjs"; +import { useEffect, useState } from "react"; import type { UseFormReturn, FieldPath } from "react-hook-form"; -import type { Post, PostFormData, PostUpdateFormData } from "@/entities/post"; +import { useLocalStorage } from "@frontend-opensource/use-react-hooks"; import { Input, Label, @@ -11,9 +13,14 @@ import { TableHead, TableRow, Textarea, - Button, + DateRangePicker, } from "@common/components"; -import { Form, FormControl, FormField, FormItem } from "@/shared/ui/form"; +import { FormWrapper } from "@/widgets/form-wrapper"; +import { CodeSelect } from "@/entities/code"; +import type { Board } from "@/entities/board"; +import type { Post, PostFormData, PostUpdateFormData } from "@/entities/post"; +import { FormControl, FormField, FormItem } from "@/shared/ui/form"; +import { useUserStore } from "@/shared/lib"; type NoticeFormProps = { form: UseFormReturn; @@ -24,63 +31,68 @@ function NoticeForm({ form, handleSave, }: NoticeFormProps) { - const onSubmit = async (data: T) => { - await handleSave(data); - }; + const { userInfo } = useUserStore((state) => state); + const [storedBbsInfo] = useLocalStorage("bbs-info", ""); + + const [bbsInfo, setBbsInfo] = useState(null); + + useEffect(() => { + if (storedBbsInfo) setBbsInfo(JSON.parse(storedBbsInfo)); + }, [storedBbsInfo]); return ( - - - - - - - - - - - + + + + + + + + + + 제목 + + } + render={({ field }) => ( + + + + + + )} + /> + + + + 작성자 + + } + render={({ field }) => ( + + + + + + )} + /> + + + {!userInfo?.userUnqId && ( - 제목 - - } - render={({ field }) => ( - - - - - - )} - /> - - - - 작성자 - - } - render={({ field }) => ( - - - - - - )} - /> - 비밀번호 ({ {...field} type="password" value={field.value ?? ""} - className="!text-[1.3rem]" + className="w-[20rem] !text-[1.3rem]" placeholder="비밀번호를 입력해주세요" /> @@ -102,31 +114,33 @@ function NoticeForm({ /> - - 내용 - - } - render={({ field }) => ( - - - - - - )} - /> - - + )} + + 내용 + + } + render={({ field }) => ( + + + + + + )} + /> + + + {bbsInfo?.rfrncUseYn === "Y" && ( 참조 URL - + } @@ -145,118 +159,160 @@ function NoticeForm({ /> + )} + {bbsInfo?.popupUseYn === "Y" && ( 팝업여부 - + } render={({ field }) => ( - - - - 사용 - - - - 사용안함 - - + + + + + 사용 + + + + 사용안함 + + + + )} /> + )} + {bbsInfo?.upndFixUseYn === "Y" && ( 상단고정 - + } render={({ field }) => ( - - - - 사용 - - - - 사용안함 - - + + + + + 사용 + + + + 사용안함 + + + + )} /> + )} + {bbsInfo?.pstgPrdUseYn === "Y" && ( 게시기간 - + } render={({ field }) => ( - - - - 사용 - - - - 사용안함 - - + + + + + 사용 + + + + 사용안함 + + + + )} /> - - 첨부파일 - - } - render={({ field }) => ( - - - - )} - /> - - - - - - - 저장 - - - - - + )} + + 첨부파일 + + } + render={() => ( + + + + )} + /> + + + + + + ); } +const PeriodSelector = ({ disabled }: { disabled: boolean }) => { + const [periodCode, setPeriodCode] = useState(null); + const handlePeriodCodeChange = (value: string) => { + setPeriodCode(parseInt(value)); + }; + + const postingPeriod = (periodCode: number | null) => { + if (!periodCode) return ""; + if (periodCode === 99) return "상시"; + return `${dayjs().format("YYYY-MM-DD")} ~ ${dayjs() + .add(periodCode, "day") + .format("YYYY-MM-DD")}`; + }; + + return ( + + + {!!periodCode && ( + + 게시 기간: {postingPeriod(periodCode)} + + )} + {periodCode === 0 && } + + ); +}; + export { NoticeForm }; diff --git a/apps/common/features/src/pages/notice/ui/notice-page.tsx b/apps/common/features/src/pages/notice/ui/notice-page.tsx index ceb63ec6a..d43355f27 100644 --- a/apps/common/features/src/pages/notice/ui/notice-page.tsx +++ b/apps/common/features/src/pages/notice/ui/notice-page.tsx @@ -44,7 +44,7 @@ function NoticePage({ bbsId }: { bbsId: string }) { queryFn: () => PostApi.getPostList({ ...postParams, - bbsId: Number(bbsId), + bbsId: parseInt(bbsId), page: pagination.pageIndex, size: pagination.pageSize, }), @@ -84,23 +84,7 @@ function NoticePage({ bbsId }: { bbsId: string }) { - 상태 - { - setPostParams({ ...postParams, delYn }); - }} - > - - - - - 정상 - 삭제 - - - - 검색 + 검색 { @@ -133,13 +117,13 @@ function NoticePage({ bbsId }: { bbsId: string }) { 공지사항 목록 - router.push("/bbs/register")}> + router.push(`${pathname}/register`)}> 등록 { - table.setPageSize(Number(value)); + table.setPageSize(parseInt(value)); }} > diff --git a/apps/common/features/src/shared/lib/index.ts b/apps/common/features/src/shared/lib/index.ts index bfbe51702..ce8092221 100644 --- a/apps/common/features/src/shared/lib/index.ts +++ b/apps/common/features/src/shared/lib/index.ts @@ -1,3 +1,4 @@ export { getCookie, setCookie, removeCookie } from "./cookie"; +export { useAlertStore } from "./use-alert-store"; export { useMenuStore } from "./use-menu-store"; export { useUserStore } from "./use-user-store"; diff --git a/apps/common/features/src/shared/lib/use-tus.ts b/apps/common/features/src/shared/lib/use-tus.ts index fdaeb1f65..30976c23b 100644 --- a/apps/common/features/src/shared/lib/use-tus.ts +++ b/apps/common/features/src/shared/lib/use-tus.ts @@ -34,7 +34,7 @@ export const useTus = () => { }, onProgress(bytesUploaded, bytesTotal) { const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2); - setProgress(Number(percentage)); + setProgress(parseInt(percentage)); }, onAfterResponse(req, res) { const fileInfo = res.getHeader("X-File-Info");