diff --git a/admin/public/assets/icons/close.svg b/admin/public/assets/icons/close.svg new file mode 100644 index 00000000..d7e2759b --- /dev/null +++ b/admin/public/assets/icons/close.svg @@ -0,0 +1,3 @@ + + + diff --git a/admin/src/components/Button/index.tsx b/admin/src/components/Button/index.tsx new file mode 100644 index 00000000..d425fab9 --- /dev/null +++ b/admin/src/components/Button/index.tsx @@ -0,0 +1,28 @@ +import { HTMLProps } from "react"; +import { cva } from "class-variance-authority"; + +interface ButtonProps extends HTMLProps { + isValid?: boolean; + type: "lg" | "sm"; +} + +const ButtonVariants = cva(`transition-all`, { + variants: { + isValid: { + true: "text-neutral-950 border-neutral-950 bg-white hover:bg-neutral-100", + false: "text-neutral-300 border-neutral-300 bg-neutral-100", + }, + type: { + lg: "w-[266px] rounded-full py-[16px] border-2", + sm: "inline px-[12px] py-[8px] rounded-xl border", + }, + }, +}); + +export default function Button({ isValid = true, type, children, ...restProps }: ButtonProps) { + return ( + + ); +} diff --git a/admin/src/components/Dropdown/index.tsx b/admin/src/components/Dropdown/index.tsx new file mode 100644 index 00000000..497a4d09 --- /dev/null +++ b/admin/src/components/Dropdown/index.tsx @@ -0,0 +1,45 @@ +import { useState } from "react"; + +interface DropdownProps { + options: string[]; + selectedIdx: number; + handleClickOption: (idx: number) => void; +} + +export default function Dropdown({ options, selectedIdx, handleClickOption }: DropdownProps) { + const [isVisibleOptions, setIsVisibleOptions] = useState(false); + + const handleClick = (idx: number) => { + handleClickOption(idx); + setIsVisibleOptions(false); + }; + + return ( + <> +
setIsVisibleOptions(false)} + /> +
+
setIsVisibleOptions(!isVisibleOptions)}> + {options[selectedIdx]} +
+ {isVisibleOptions && ( +
+
+ {options.map((option, idx) => ( +

handleClick(idx)} + className="break-keep text-nowrap" + > + {option} +

+ ))} +
+
+ )} +
+ + ); +} diff --git a/admin/src/components/Header/index.tsx b/admin/src/components/Header/index.tsx new file mode 100644 index 00000000..5e0977c3 --- /dev/null +++ b/admin/src/components/Header/index.tsx @@ -0,0 +1,7 @@ +export default function Header() { + return ( +
+ 현대자동차 캐스퍼 일렉트릭 신차 출시 이벤트 어드민 +
+ ); +} diff --git a/admin/src/components/Input/index.tsx b/admin/src/components/Input/index.tsx new file mode 100644 index 00000000..91e18d73 --- /dev/null +++ b/admin/src/components/Input/index.tsx @@ -0,0 +1,19 @@ +import { HTMLProps } from "react"; + +interface InputProps extends HTMLProps { + label?: string; +} + +export default function Input({ label, value, onChange, ...restProps }: InputProps) { + return ( +
+ {label &&

{label}

} + +
+ ); +} diff --git a/admin/src/components/Modal/index.tsx b/admin/src/components/Modal/index.tsx new file mode 100644 index 00000000..48f849ee --- /dev/null +++ b/admin/src/components/Modal/index.tsx @@ -0,0 +1,27 @@ +import { PropsWithChildren } from "react"; + +export interface ModalProps extends PropsWithChildren { + handleClose: () => void; +} + +export default function Modal({ handleClose, children }: ModalProps) { + return ( +
+
+
+ {children} + + +
+
+ ); +} diff --git a/admin/src/components/TabHeader/index.tsx b/admin/src/components/TabHeader/index.tsx new file mode 100644 index 00000000..a38c422c --- /dev/null +++ b/admin/src/components/TabHeader/index.tsx @@ -0,0 +1,32 @@ +import { cva } from "class-variance-authority"; + +interface TabHeaderProps { + tabList: string[]; + handleClickTab: (idx: number) => void; + selectedIdx: number; +} + +const TabButtonVariants = cva(`border-b-2`, { + variants: { + selected: { + true: "h-body-1-bold border-neutral-950", + false: "h-body-1-regular border-transparent", + }, + }, +}); + +export default function TabHeader({ tabList, selectedIdx, handleClickTab }: TabHeaderProps) { + return ( +
+ {tabList.map((tab, idx) => ( + + ))} +
+ ); +} diff --git a/admin/src/components/Table/index.tsx b/admin/src/components/Table/index.tsx new file mode 100644 index 00000000..574e886f --- /dev/null +++ b/admin/src/components/Table/index.tsx @@ -0,0 +1,43 @@ +import { ReactNode } from "react"; + +interface TableProps { + headers: ReactNode[]; + data: ReactNode[][]; +} + +export default function Table({ headers, data }: TableProps) { + return ( +
+
+ + + + {headers.map((header, idx) => ( + + ))} + + + + {data.map((tableData, idx) => ( + + {tableData.map((dataNode, idx) => ( + + ))} + + ))} + +
+ {header} +
+ {dataNode} +
+
+
+ ); +} diff --git a/admin/src/hooks/useModal.tsx b/admin/src/hooks/useModal.tsx new file mode 100644 index 00000000..fbd9714f --- /dev/null +++ b/admin/src/hooks/useModal.tsx @@ -0,0 +1,28 @@ +import { useEffect, useState } from "react"; +import Modal, { ModalProps } from "@/components/Modal"; + +export default function useModal() { + const [isOpen, setIsOpen] = useState(false); + + useEffect(() => { + return () => { + document.body.style.overflow = "unset"; + }; + }, []); + + const handleOpenModal = () => { + document.body.style.overflow = "hidden"; + setIsOpen(true); + }; + + const handleCloseModal = () => { + document.body.style.overflow = "unset"; + setIsOpen(false); + }; + + const ModalComponent = ({ children }: Omit) => { + return isOpen ? {children} : null; + }; + + return { handleOpenModal, ModalComponent }; +} diff --git a/admin/src/index.css b/admin/src/index.css index 42c578dd..8d716516 100644 --- a/admin/src/index.css +++ b/admin/src/index.css @@ -2,44 +2,41 @@ @tailwind components; @tailwind utilities; +input:focus { + outline: none; +} + @font-face { - font-family: "HyundaiSansHeadOffice-Bold"; - src: url("/public/fonts/hyundai-sans/HyundaiSansHeadKROTFBold.otf") - format("opentype"); + font-family: "HyundaiSansHeadOffice-Bold"; + src: url("/public/fonts/hyundai-sans/HyundaiSansHeadKROTFBold.otf") format("opentype"); } @font-face { - font-family: "HyundaiSansHeadOffice-Medium"; - src: url("/public/fonts/hyundai-sans/HyundaiSansHeadKROTFMedium.otf") - format("opentype"); + font-family: "HyundaiSansHeadOffice-Medium"; + src: url("/public/fonts/hyundai-sans/HyundaiSansHeadKROTFMedium.otf") format("opentype"); } @font-face { - font-family: "HyundaiSansHeadOffice-Regular"; - src: url("/public/fonts/hyundai-sans/HyundaiSansHeadKROTFRegular.otf") - format("opentype"); + font-family: "HyundaiSansHeadOffice-Regular"; + src: url("/public/fonts/hyundai-sans/HyundaiSansHeadKROTFRegular.otf") format("opentype"); } @font-face { - font-family: "HyundaiSansHeadOffice-Light"; - src: url("/public/fonts/hyundai-sans/HyundaiSansHeadKROTFLight.otf") - format("opentype"); + font-family: "HyundaiSansHeadOffice-Light"; + src: url("/public/fonts/hyundai-sans/HyundaiSansHeadKROTFLight.otf") format("opentype"); } @font-face { - font-family: "HyundaiSansTextOffice-Bold"; - src: url("/public/fonts/hyundai-sans/HyundaiSansTextKROTFBold.otf") - format("opentype"); + font-family: "HyundaiSansTextOffice-Bold"; + src: url("/public/fonts/hyundai-sans/HyundaiSansTextKROTFBold.otf") format("opentype"); } @font-face { - font-family: "HyundaiSansTextOffice-Medium"; - src: url("/public/fonts/hyundai-sans/HyundaiSansTextKROTFMedium.otf") - format("opentype"); + font-family: "HyundaiSansTextOffice-Medium"; + src: url("/public/fonts/hyundai-sans/HyundaiSansTextKROTFMedium.otf") format("opentype"); } @font-face { - font-family: "HyundaiSansTextOffice-Regular"; - src: url("/public/fonts/hyundai-sans/HyundaiSansTextKROTFRegular.otf") - format("opentype"); + font-family: "HyundaiSansTextOffice-Regular"; + src: url("/public/fonts/hyundai-sans/HyundaiSansTextKROTFRegular.otf") format("opentype"); } diff --git a/admin/src/pages/Login/index.tsx b/admin/src/pages/Login/index.tsx new file mode 100644 index 00000000..19fa3d5e --- /dev/null +++ b/admin/src/pages/Login/index.tsx @@ -0,0 +1,87 @@ +import { ChangeEvent, useState } from "react"; +import Button from "@/components/Button"; +import Dropdown from "@/components/Dropdown"; +import Header from "@/components/Header"; +import Input from "@/components/Input"; +import TabHeader from "@/components/TabHeader"; +import Table from "@/components/Table"; +import useModal from "@/hooks/useModal"; + +export default function Login() { + const [selectedIdx, setSelectedIdx] = useState(0); + const [value, setValue] = useState(""); + + const { handleOpenModal, ModalComponent } = useModal(); + + const [selectedDropdownIdx, setSelectedDropdownIdx] = useState(0); + + const headers = [ + "ID", + "이벤트 진행 날짜", + "오픈 시간", + "종료 시간", + "활성화 시간", + "선택지 관리", + "경품 관리", + "선착순 당첨 인원 수", + "진행 상태", + setSelectedDropdownIdx(idx)} + />, + "관리", + ]; + const data = new Array(20).fill(null).map(() => [ + 7, + "2024-07-19", + "22:00:00", + "22:10:00", + "00시간 10분 00초", + , + , +
+

315

+

편집

+
, + "오픈 전", + , + , + ]); + + return ( + <> +
+ setSelectedIdx(idx)} + /> + + + + + setValue((e as ChangeEvent).target.value)} + /> + setValue((e as ChangeEvent).target.value)} + /> + + +
hihi
+
+ + + + ); +} diff --git a/admin/src/router.tsx b/admin/src/router.tsx index 5455aab1..d500b1c3 100644 --- a/admin/src/router.tsx +++ b/admin/src/router.tsx @@ -1,8 +1,9 @@ import { createBrowserRouter } from "react-router-dom"; +import Login from "./pages/Login"; export const router = createBrowserRouter([ { path: "/", - element:

herd

, + element: , }, ]);