Merge remote-tracking branch 'origin/iipbs/fo/dev'
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import { Client } from "@common/network";
|
||||
import { BASE_URL } from "../config/api";
|
||||
import { SITE_ID } from "../config/env";
|
||||
import { setCookie } from "../lib";
|
||||
|
||||
setCookie("site-id-key", SITE_ID);
|
||||
|
||||
export const client = Client.getInstance({
|
||||
baseURL: BASE_URL,
|
||||
siteId: SITE_ID,
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export { client } from "./client";
|
||||
@@ -0,0 +1,6 @@
|
||||
export { useMutationWrapper } from "./use-mutation-wrapper";
|
||||
export { useAlertStore } from "./use-alert-store";
|
||||
export { useMenuStore } from "./use-menu-store";
|
||||
export { useUserStore } from "./use-user-store";
|
||||
export { usePrompt } from "./use-prompt";
|
||||
export { getCookie, setCookie, removeCookie } from "./cookie";
|
||||
@@ -0,0 +1,14 @@
|
||||
import { useStore } from "zustand";
|
||||
import { useContext } from "react";
|
||||
import type { AlertStore } from "../store/alert-store";
|
||||
import { StoreContext } from "../providers/store-provider";
|
||||
|
||||
export const useAlertStore = <T>(selector: (store: AlertStore) => T): T => {
|
||||
const storeContext = useContext(StoreContext);
|
||||
|
||||
if (!storeContext) {
|
||||
throw new Error("useAlertStore must be used within StoreProvider");
|
||||
}
|
||||
|
||||
return useStore(storeContext.alertStore, selector);
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import { useStore } from "zustand";
|
||||
import { useContext } from "react";
|
||||
import type { MenuStore } from "../store/menu-store";
|
||||
import { StoreContext } from "../providers/store-provider";
|
||||
|
||||
export const useMenuStore = <T>(selector: (store: MenuStore) => T): T => {
|
||||
const storeContext = useContext(StoreContext);
|
||||
|
||||
if (!storeContext) {
|
||||
throw new Error("useMenuStore must be used within StoreProvider");
|
||||
}
|
||||
|
||||
return useStore(storeContext.menuStore, selector);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { AxiosError } from "axios";
|
||||
import { useMutation, type UseMutationOptions } from "@tanstack/react-query";
|
||||
import { useAlertStore } from "./use-alert-store";
|
||||
|
||||
export const useMutationWrapper = <
|
||||
TData = unknown,
|
||||
TError = unknown,
|
||||
TVariables = void,
|
||||
TContext = unknown,
|
||||
>(
|
||||
mutationOptions: UseMutationOptions<TData, TError, TVariables, TContext>,
|
||||
) => {
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
return useMutation({
|
||||
...mutationOptions,
|
||||
onError: (error) => {
|
||||
let errorMessage = "";
|
||||
|
||||
if (error instanceof AxiosError) {
|
||||
if (error.response?.status)
|
||||
errorMessage += `status: ${error.response.status}\n`;
|
||||
if (error.response?.data?.message)
|
||||
errorMessage += `${error.response.data.message}\n`;
|
||||
if (error.response?.data?.errors[0]?.message)
|
||||
errorMessage += `${error.response.data.errors[0].message}\n`;
|
||||
}
|
||||
|
||||
alert(`[Error] ${errorMessage}`);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useContext } from "react";
|
||||
import { PromptContext } from "../providers/prompt-provider";
|
||||
|
||||
export const usePrompt = () => {
|
||||
const { message, resolve, setMessage, setResolve } =
|
||||
useContext(PromptContext);
|
||||
|
||||
const prompt = (message: string) => {
|
||||
setMessage(message);
|
||||
return new Promise<{ resolved: boolean; value?: string }>((resolve) => {
|
||||
setResolve(() => resolve);
|
||||
});
|
||||
};
|
||||
|
||||
const onSubmit = (value: string) => {
|
||||
resolve?.({ resolved: true, value });
|
||||
setMessage("");
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
resolve?.({ resolved: false });
|
||||
setMessage("");
|
||||
};
|
||||
|
||||
return { message, isOpen: !!message, prompt, onSubmit, onCancel };
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import { useStore } from "zustand";
|
||||
import { useContext } from "react";
|
||||
import type { UserStore } from "../store/user-store";
|
||||
import { StoreContext } from "../providers/store-provider";
|
||||
|
||||
export const useUserStore = <T>(selector: (store: UserStore) => T): T => {
|
||||
const storeContext = useContext(StoreContext);
|
||||
|
||||
if (!storeContext) {
|
||||
throw new Error("useUserStore must be used within StoreProvider");
|
||||
}
|
||||
|
||||
return useStore(storeContext.userStore, selector);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export { StoreProvider } from "./store-provider";
|
||||
export { PromptProvider } from "./prompt-provider";
|
||||
@@ -0,0 +1,37 @@
|
||||
import { createContext, useState } from "react";
|
||||
|
||||
interface PromptContext {
|
||||
message?: string;
|
||||
resolve?: ({
|
||||
resolved,
|
||||
value,
|
||||
}: {
|
||||
resolved: boolean;
|
||||
value?: string;
|
||||
}) => void;
|
||||
setMessage: (message: string) => void;
|
||||
setResolve: (resolve: () => void) => void;
|
||||
}
|
||||
|
||||
export const PromptContext = createContext<PromptContext>({
|
||||
message: "",
|
||||
resolve() {},
|
||||
setMessage() {},
|
||||
setResolve() {},
|
||||
});
|
||||
|
||||
export const PromptProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [message, setMessage] = useState<string>();
|
||||
const [resolve, setResolve] =
|
||||
useState<
|
||||
({ resolved, value }: { resolved: boolean; value?: string }) => void
|
||||
>();
|
||||
|
||||
return (
|
||||
<PromptContext.Provider
|
||||
value={{ message, resolve, setMessage, setResolve }}
|
||||
>
|
||||
{children}
|
||||
</PromptContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, useRef } from "react";
|
||||
import {
|
||||
createAlertStore,
|
||||
createAttachmentStore,
|
||||
createMenuStore,
|
||||
createUserStore,
|
||||
} from "../store";
|
||||
|
||||
type StoreContextType = {
|
||||
alertStore: ReturnType<typeof createAlertStore>;
|
||||
attachmentStore: ReturnType<typeof createAttachmentStore>;
|
||||
menuStore: ReturnType<typeof createMenuStore>;
|
||||
userStore: ReturnType<typeof createUserStore>;
|
||||
};
|
||||
|
||||
export const StoreContext = createContext<StoreContextType | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
export const StoreProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const storeRef = useRef<StoreContextType | null>(null);
|
||||
if (storeRef.current === null) {
|
||||
storeRef.current = {
|
||||
alertStore: createAlertStore(),
|
||||
attachmentStore: createAttachmentStore(),
|
||||
menuStore: createMenuStore(),
|
||||
userStore: createUserStore(),
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<StoreContext.Provider value={storeRef.current}>
|
||||
{children}
|
||||
</StoreContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { createStore } from "zustand/vanilla";
|
||||
|
||||
export type AlertState = {
|
||||
message: string;
|
||||
callback?: () => void;
|
||||
};
|
||||
|
||||
export type AlertActions = {
|
||||
setMessage: (message: string, callback?: () => void) => void;
|
||||
};
|
||||
|
||||
export type AlertStore = AlertState & AlertActions;
|
||||
|
||||
export const defaultInitState: AlertState = {
|
||||
message: "",
|
||||
callback: undefined,
|
||||
};
|
||||
|
||||
export const createAlertStore = (initState: AlertState = defaultInitState) => {
|
||||
return createStore<AlertStore>()((set) => ({
|
||||
...initState,
|
||||
setMessage: (message: string, callback?: () => void) =>
|
||||
set(() => ({ message, callback })),
|
||||
}));
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { createStore } from "zustand/vanilla";
|
||||
|
||||
export type AttachmentState = {
|
||||
attachmentFileCode: string;
|
||||
};
|
||||
|
||||
export type AttachmentActions = {
|
||||
setAttachmentFileCode: (attachmentFileCode: string) => void;
|
||||
};
|
||||
|
||||
export type AttachmentStore = AttachmentState & AttachmentActions;
|
||||
|
||||
export const defaultInitState: AttachmentState = {
|
||||
attachmentFileCode: "",
|
||||
};
|
||||
|
||||
export const createAttachmentStore = (
|
||||
initState: AttachmentState = defaultInitState,
|
||||
) => {
|
||||
return createStore<AttachmentStore>()((set) => ({
|
||||
...initState,
|
||||
setAttachmentFileCode: (attachmentFileCode: string) =>
|
||||
set({ attachmentFileCode }),
|
||||
}));
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export { createAlertStore } from "./alert-store";
|
||||
export { createAttachmentStore } from "./attachment-store";
|
||||
export { createMenuStore, type BbsType } from "./menu-store";
|
||||
export { createUserStore, type IUserInfo } from "./user-store";
|
||||
@@ -0,0 +1,93 @@
|
||||
import { createStore } from "zustand/vanilla";
|
||||
|
||||
export const BBS_TYPE = {
|
||||
BBSNOTICE: "BBSNOTICE",
|
||||
BBSNORMAL: "BBSNORMAL",
|
||||
BBSFAQ: "BBSFAQ",
|
||||
BBSIMAGE: "BBSIMAGE",
|
||||
BBSMOVIE: "BBSMOVIE",
|
||||
} as const;
|
||||
|
||||
export type BbsType = (typeof BBS_TYPE)[keyof typeof BBS_TYPE];
|
||||
|
||||
export interface IBbs {
|
||||
bbsId: number;
|
||||
bbsTypeCd: BbsType;
|
||||
bbsTypeNm: string;
|
||||
bbsNm: string;
|
||||
bbsExpln: string;
|
||||
rfrncUseYn: string;
|
||||
atflUseYn: string;
|
||||
atflMaxCnt: number;
|
||||
atflMaxFileSz: number;
|
||||
ansUseYn: string;
|
||||
ansUseAuthrt: string;
|
||||
cmntUseYn: string;
|
||||
dgstfnEvlUseYn: string;
|
||||
smsUseYn: string;
|
||||
upndFixUseYn: string;
|
||||
pstgPrdUseYn: string;
|
||||
popupUseYn: string;
|
||||
delYn: string;
|
||||
useYn: string;
|
||||
mdfcnDt: string;
|
||||
mdfrNm: string;
|
||||
}
|
||||
|
||||
interface IMenu {
|
||||
menuCd: number;
|
||||
menuKornNm: string;
|
||||
menuEngNm: string;
|
||||
parentId: number | null;
|
||||
sortSeq: number;
|
||||
iconNm?: string;
|
||||
children: IMenu[] | null;
|
||||
levelNo: number;
|
||||
urlPath: string;
|
||||
menuLnkgTypeCd: string;
|
||||
menuLnkgSn: number;
|
||||
menuPath: string;
|
||||
qrcd: string;
|
||||
useYn: string;
|
||||
otptYn: string;
|
||||
newWndwYn: string;
|
||||
requiredUrlPath: boolean;
|
||||
}
|
||||
|
||||
interface IAuthority {
|
||||
mngAuthrtYn: string;
|
||||
inqAuthrtYn: string;
|
||||
regAuthrtYn: string;
|
||||
mdfcnAuthrtYn: string;
|
||||
delAuthrtYn: string;
|
||||
}
|
||||
|
||||
export type MenuState = {
|
||||
bbsInfo: IBbs | null;
|
||||
currentMenuInfo: IMenu | null;
|
||||
pageMenuAuthority: IAuthority | null;
|
||||
};
|
||||
|
||||
export type MenuActions = {
|
||||
setBbsInfo: (bbs: IBbs) => void;
|
||||
setCurrentMenuInfo: (menu: IMenu) => void;
|
||||
setPageMenuAuthority: (authority: IAuthority) => void;
|
||||
};
|
||||
|
||||
export type MenuStore = MenuState & MenuActions;
|
||||
|
||||
export const defaultInitState: MenuState = {
|
||||
currentMenuInfo: null,
|
||||
bbsInfo: null,
|
||||
pageMenuAuthority: null,
|
||||
};
|
||||
|
||||
export const createMenuStore = (initState: MenuState = defaultInitState) => {
|
||||
return createStore<MenuStore>()((set) => ({
|
||||
...initState,
|
||||
setBbsInfo: (bbs: IBbs) => set({ bbsInfo: bbs }),
|
||||
setCurrentMenuInfo: (menu: IMenu) => set({ currentMenuInfo: menu }),
|
||||
setPageMenuAuthority: (authority: IAuthority) =>
|
||||
set({ pageMenuAuthority: authority }),
|
||||
}));
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
import { createStore } from "zustand/vanilla";
|
||||
|
||||
export interface IUserInfo {
|
||||
regDt: string;
|
||||
mdfcnDt: string;
|
||||
rgtr: string;
|
||||
mdfr: string;
|
||||
adminRoleYn: string;
|
||||
userUnqId: string;
|
||||
userId: string;
|
||||
userName: string;
|
||||
email: string;
|
||||
encryptedPassword: string;
|
||||
refreshToken: string;
|
||||
userStateCode: string;
|
||||
lastLoginDate: string;
|
||||
loginFailCount: number;
|
||||
pswdHnt: string;
|
||||
pswdAns: string;
|
||||
mphn: string;
|
||||
telno: string;
|
||||
zip: string;
|
||||
addr: string;
|
||||
daddr: string;
|
||||
laeSeCd: string;
|
||||
ogdpSeCd: string;
|
||||
aprvYn: string;
|
||||
autzrId: string;
|
||||
expryYmd: string;
|
||||
lastPswdChgDt: string;
|
||||
googleId: string;
|
||||
kakaoId: string;
|
||||
naverId: string;
|
||||
isSocialUser: boolean;
|
||||
hasPassword: boolean;
|
||||
}
|
||||
|
||||
export type UserState = {
|
||||
userInfo: IUserInfo | null;
|
||||
};
|
||||
|
||||
export type UserActions = {
|
||||
setUserInfo: (userInfo: IUserInfo | null) => void;
|
||||
};
|
||||
|
||||
export type UserStore = UserState & UserActions;
|
||||
|
||||
export const defaultInitState: UserState = {
|
||||
userInfo: null,
|
||||
};
|
||||
|
||||
export const createUserStore = (initState: UserState = defaultInitState) => {
|
||||
return createStore<UserStore>()((set) => ({
|
||||
...initState,
|
||||
setUserInfo: (userInfo: IUserInfo | null) => set(() => ({ userInfo })),
|
||||
}));
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogClose,
|
||||
Button,
|
||||
} from "@common/components";
|
||||
import { useAlertStore } from "../lib/use-alert-store";
|
||||
|
||||
function AlertDialog() {
|
||||
const { message, callback, setMessage } = useAlertStore((state) => state);
|
||||
const handleOpenChange = () => {
|
||||
setMessage("");
|
||||
callback?.();
|
||||
};
|
||||
return (
|
||||
<Dialog open={!!message} onOpenChange={handleOpenChange}>
|
||||
<DialogContent aria-describedby={undefined}>
|
||||
<div className="py-10">
|
||||
<p className="whitespace-pre-line text-center text-[1.4rem] text-foreground">
|
||||
{message}
|
||||
</p>
|
||||
</div>
|
||||
<DialogFooter className="!justify-center">
|
||||
<DialogClose asChild>
|
||||
<Button>확인</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export { AlertDialog };
|
||||
@@ -0,0 +1,48 @@
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogClose,
|
||||
Button,
|
||||
type ButtonProps,
|
||||
} from "@common/components";
|
||||
|
||||
type ConfirmDialogProps = {
|
||||
isOpen?: boolean;
|
||||
confirmButtonProps?: ButtonProps & { label?: string };
|
||||
cancelButtonProps?: ButtonProps & { label?: string };
|
||||
};
|
||||
|
||||
function ConfirmDialog({
|
||||
isOpen,
|
||||
confirmButtonProps,
|
||||
cancelButtonProps,
|
||||
}: ConfirmDialogProps) {
|
||||
const { message, isOpen: isGlobalOpen, onConfirm, onCancel } = useConfirm();
|
||||
return (
|
||||
<Dialog open={isOpen ?? isGlobalOpen} onOpenChange={onCancel}>
|
||||
<DialogContent aria-describedby={undefined}>
|
||||
<div className="py-10">
|
||||
<p className="whitespace-pre-line text-center text-[1.4rem] text-foreground">
|
||||
{message}
|
||||
</p>
|
||||
</div>
|
||||
<DialogFooter className="!justify-center">
|
||||
<DialogClose asChild>
|
||||
<Button onClick={onConfirm} {...confirmButtonProps}>
|
||||
{confirmButtonProps?.label ?? "확인"}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button onClick={onCancel} color="white" {...cancelButtonProps}>
|
||||
{cancelButtonProps?.label ?? "취소"}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export { ConfirmDialog };
|
||||
@@ -0,0 +1,13 @@
|
||||
import { useMenuStore } from "../lib/use-menu-store";
|
||||
|
||||
function MapLayout({ children }: { children: React.ReactNode }) {
|
||||
const currentMenuInfo = useMenuStore((state) => state.currentMenuInfo);
|
||||
|
||||
return (
|
||||
<div className="h-auto min-h-full bg-container pb-2">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { MapLayout };
|
||||
@@ -0,0 +1,22 @@
|
||||
import { PageTitle } from "@common/components";
|
||||
import { useMenuStore } from "../lib/use-menu-store";
|
||||
|
||||
function PageLayout({ children }: { children: React.ReactNode }) {
|
||||
const currentMenuInfo = useMenuStore((state) => state.currentMenuInfo);
|
||||
|
||||
const menuPaths = currentMenuInfo?.menuPath
|
||||
?.split(">")
|
||||
.map((path) => path.trim());
|
||||
|
||||
return (
|
||||
<div className="h-auto min-h-full bg-container pb-2">
|
||||
<PageTitle
|
||||
title={currentMenuInfo?.menuKornNm ?? ""}
|
||||
menuPaths={menuPaths ?? []}
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { PageLayout };
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogClose,
|
||||
Input,
|
||||
} from "@common/components";
|
||||
import { usePrompt } from "@/src/shared/lib/use-prompt";
|
||||
|
||||
function PromptDialog() {
|
||||
const { message, isOpen, onSubmit, onCancel } = usePrompt();
|
||||
const [value, setValue] = useState("");
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={onCancel}>
|
||||
<DialogContent aria-describedby={undefined}>
|
||||
<div className="flex flex-col gap-6 pb-4 pt-8">
|
||||
<p className="whitespace-pre-line text-center text-[1.4rem] text-foreground">
|
||||
{message}
|
||||
</p>
|
||||
<Input
|
||||
type="text"
|
||||
value={value}
|
||||
className="!text-[1.3rem]"
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<DialogFooter className="!justify-center">
|
||||
<DialogClose asChild>
|
||||
<Button onClick={() => onSubmit(value)}>확인</Button>
|
||||
</DialogClose>
|
||||
<DialogClose asChild>
|
||||
<Button onClick={onCancel} color="white">
|
||||
취소
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export { PromptDialog };
|
||||
Reference in New Issue
Block a user