diff --git a/public/assets/icon/icon_check.svg b/public/assets/icon/icon_check.svg new file mode 100644 index 000000000..8bd7df10c --- /dev/null +++ b/public/assets/icon/icon_check.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/icon/icon_close.png b/public/assets/icon/icon_close.png deleted file mode 100644 index 592c3d9e5..000000000 Binary files a/public/assets/icon/icon_close.png and /dev/null differ diff --git a/public/assets/icon/icon_close.svg b/public/assets/icon/icon_close.svg new file mode 100644 index 000000000..26085f48e --- /dev/null +++ b/public/assets/icon/icon_close.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/common/Header.tsx b/src/components/common/Header.tsx index 8d9c875c7..450d33e0e 100644 --- a/src/components/common/Header.tsx +++ b/src/components/common/Header.tsx @@ -1,4 +1,4 @@ -import { Link, useLocation } from "react-router-dom"; +import { Link, useLocation } from 'react-router-dom'; import { Email, HeaderControl, @@ -6,21 +6,21 @@ import { HeaderLogo, HeaderUserInfo, HeaderWrap, -} from "./headerStyle"; -import { Profile } from "../../styles/commonStyle"; -import LinkButton from "./atoms/LinkButton"; -import useFetch from "../../hook/useFetch"; -import { USERLOGINAPI } from "../../constant/api"; -import { IHeaderUserLoginInfoApi } from "./interface"; -import { useEffect, useState } from "react"; -const logo = "/assets/logo/logo.svg"; +} from './headerStyle'; +import { Profile } from '../../styles/commonStyle'; +import LinkButton from './atoms/LinkButton'; +import useFetch from '../../hook/useFetch'; +import { USERLOGINAPI } from '../../constant/api'; +import { IHeaderUserLoginInfoApi } from './interface'; +import { useEffect, useState } from 'react'; +const logo = '/assets/logo/logo.svg'; function Header() { const { pathname } = useLocation(); const { value } = useFetch(USERLOGINAPI); const [fixed, setFixed] = useState(true); useEffect(() => { - if (pathname === "/folder") { + if (pathname === '/folder') { setFixed(false); } }, [pathname]); @@ -40,7 +40,7 @@ function Header() { {userInfo?.email} ) : ( - + 로그인 )} diff --git a/src/components/common/atoms/Button.tsx b/src/components/common/atoms/Button.tsx index b3104f641..d36d2a3df 100644 --- a/src/components/common/atoms/Button.tsx +++ b/src/components/common/atoms/Button.tsx @@ -1,41 +1,29 @@ -import { ButtonModule } from "./buttonStyle"; +import { ButtonModule } from './buttonStyle'; interface IButtonModule { children: React.ReactNode; $btnClass: string; $BeforButtonIcon?: string; $id?: string; $afterButtonIcon?: string; - $type?: "button" | "reset" | "submit" | undefined; - clickEvent?: (value?: any, index?: number) => void; - $clickEventName?: string; - $clickIndex?: number | undefined; + $type?: 'button' | 'reset' | 'submit' | undefined; + onclick?: () => void; } export default function Button({ - $id, children, $btnClass, - $type = "button", - $BeforButtonIcon = "", - $afterButtonIcon = "", - clickEvent, - $clickEventName, - $clickIndex, + $type = 'button', + $BeforButtonIcon = '', + $afterButtonIcon = '', + onclick, }: IButtonModule) { - const buttonHenlerEvent = () => { - if (!clickEvent) return; - if ($clickEventName === "bookmarkId") { - clickEvent($id, $clickIndex); - } - }; - return ( {children} diff --git a/src/components/common/atoms/CheckBox.tsx b/src/components/common/atoms/CheckBox.tsx new file mode 100644 index 000000000..a39ae19ff --- /dev/null +++ b/src/components/common/atoms/CheckBox.tsx @@ -0,0 +1,33 @@ +import { CheckBoxWrap } from './checkBoxStyle'; +import { IModal } from '../modal/interface'; + +interface ICheckBoxData { + $data: IModal['$modalData']; +} + +function CheckBox({ $data }: ICheckBoxData) { + if (typeof $data) { + return ( + + {$data && + $data.data.map((list: any) => ( + + + + {list.name} + {list.link.count}개 링크 + + + ))} + + ); + } else { + return ( + + + + + ); + } +} +export default CheckBox; diff --git a/src/components/common/atoms/Input.tsx b/src/components/common/atoms/Input.tsx index 5273ca2c6..d604281b9 100644 --- a/src/components/common/atoms/Input.tsx +++ b/src/components/common/atoms/Input.tsx @@ -1,32 +1,64 @@ -import Button from "./Button"; -import { InputModule } from "./inputStyle"; +import { ChangeEvent, ReactNode, useEffect, useRef, useState } from 'react'; +import Button from './Button'; +import { InputModule } from './inputStyle'; interface IButtonModule { $type?: string; $inputClass?: string; $btnShow?: boolean; - $btnText?: string; $placeholder?: string; $beforeBgIcon?: string; $btnClass?: string; + children?: ReactNode; + $clickEvent?: string; + onchange?: (value: string) => void; } + function Input({ $btnShow = false, - $type = "text", + $type = 'text', $inputClass, $placeholder, - $btnText, - $beforeBgIcon = "", - $btnClass = "", + $beforeBgIcon = '', + $btnClass = '', + children, + $clickEvent, + onchange, }: IButtonModule) { + const [value, setValue] = useState(''); + const refInput = useRef(null); + const handleChangInput = (e: ChangeEvent) => { + const { value } = e.target; + setValue(value); + + if (onchange) { + // value값 전달 + onchange(value); + } + }; + + const handleButtonEvent = (text: string) => { + if (!$clickEvent) return; + if ($clickEvent === 'reset') { + setValue(''); + } + }; + return ( <> - {$btnShow && {$btnText}} + {$btnShow && ( + handleButtonEvent(value)}> + {children} + + )} > ); } diff --git a/src/components/common/atoms/buttonStyle.ts b/src/components/common/atoms/buttonStyle.ts index bb3ae035c..fd37d9460 100644 --- a/src/components/common/atoms/buttonStyle.ts +++ b/src/components/common/atoms/buttonStyle.ts @@ -1,7 +1,10 @@ -import styled from "styled-components"; -import { theme } from "../../../styles/theme"; +import styled from 'styled-components'; +import { theme } from '../../../styles/theme'; -export const ButtonModule = styled.button<{$afterButtonIcon:string, $BeforButtonIcon:string}>` +export const ButtonModule = styled.button<{ + $afterButtonIcon: string; + $BeforButtonIcon: string; +}>` &.button { &--outlined { padding: 0 12px; @@ -25,41 +28,71 @@ export const ButtonModule = styled.button<{$afterButtonIcon:string, $BeforButton &--icon-before { display: inline-block; padding-left: 22px; - background: url(${({$BeforButtonIcon}) => $BeforButtonIcon || ''}) no-repeat left center; + background: url(${({ $BeforButtonIcon }) => $BeforButtonIcon || ''}) + no-repeat left center; background-size: 18px 18px; } &--icon-after { display: inline-block; padding-right: 20px; - background: url(${({$afterButtonIcon}) => $afterButtonIcon || ''}) no-repeat right center; + background: url(${({ $afterButtonIcon }) => $afterButtonIcon || ''}) + no-repeat right center; background-size: 18px 18px; } &--gradient { + width: 100%; color: #fff; font-weight: 600; - text-align:center; - background:${theme.bgColor.gradient}; + text-align: center; + background: ${theme.bgColor.gradient}; border-radius: 8px; - &.full { - width: 100% !important; + /* &.full { + width: 100% !important; } &.large { width: 128px; - font-size: 18px; - line-height: 53px; - font-weight: 600; @media screen and (max-width: ${theme.screenSize.moLarge}) { width: 80px; - font-size: 14px; - line-height: 37px; } } &.mideum { width: 80px; - font-size: 14px; - line-height: 37px; - font-weight: 600; + } */ + } + &--red { + width: 100%; + color: ${theme.color.white}; + border-radius: 8px; + background-color: ${theme.color.red}; + } + &--modal-close { + position: absolute; + width: auto; + top: 16px; + right: 16px; + } + &--sns-share { + display: flex; + align-items: center; + flex-direction: column; + .share--text { + padding-top: 10px; } } } -` \ No newline at end of file + + &.large { + font-size: 18px; + line-height: 53px; + font-weight: 600; + @media screen and (max-width: ${theme.screenSize.moLarge}) { + font-size: 14px; + line-height: 37px; + } + } + &.mideum { + font-size: 14px; + line-height: 37px; + font-weight: 600; + } +`; diff --git a/src/components/common/atoms/checkBoxStyle.ts b/src/components/common/atoms/checkBoxStyle.ts new file mode 100644 index 000000000..a0ee8a054 --- /dev/null +++ b/src/components/common/atoms/checkBoxStyle.ts @@ -0,0 +1,56 @@ +import styled from 'styled-components'; +import { theme } from '../../../styles/theme'; + +export const CheckBoxWrap = styled.div` + &.chk { + &--list { + &-type1 { + max-height: 240px; + overflow-y: auto; + text-align: left; + .inner { + cursor: pointer; + input { + display: none !important; + } + label { + position: relative; + display: block; + padding: 0 8px; + line-height: 40px; + &::after { + content: ''; + display: none; + position: absolute; + top: 13px; + right: 8px; + width: 14px; + height: 14px; + background: url('/assets/icon/icon_check.svg'); + } + strong { + font-size: 16px; + } + span { + font-size: 14px; + color: ${theme.color.gray9}; + padding-left: 8px; + } + } + input:checked + label { + background-color: ${theme.color.grayf}; + &::after { + display: block; + } + strong { + color: ${theme.color.primary}; + } + } + &:hover { + background-color: ${theme.color.grayf}; + } + } + } + } + } +`; diff --git a/src/components/common/atoms/inputStyle.ts b/src/components/common/atoms/inputStyle.ts index 48817ef43..b2d7d0130 100644 --- a/src/components/common/atoms/inputStyle.ts +++ b/src/components/common/atoms/inputStyle.ts @@ -1,17 +1,22 @@ -import styled from "styled-components"; -import { theme } from "../../../styles/theme"; +import styled from 'styled-components'; +import { theme } from '../../../styles/theme'; -export const InputModule = styled.input<{$beforeBgIcon:string}>` +export const InputModule = styled.input<{ $beforeBgIcon: string }>` + padding: 0 15px; + width: 100%; + line-height: 58px; + border: 1px solid ${theme.color.primary}; + border-radius: 8px; box-sizing: border-box; font-size: 16px; &::placeholder { - color: #9FA6B2; + color: #9fa6b2; } &:-moz-placeholder { - color: #9FA6B2; + color: #9fa6b2; } &:-ms-placeholder { - color: #9FA6B2; + color: #9fa6b2; } &.input { &__link { @@ -19,9 +24,10 @@ export const InputModule = styled.input<{$beforeBgIcon:string}>` padding: 0 1rem 0 2.625rem; width: 100%; line-height: 54px; - color:${theme.color.gray6}; + color: ${theme.color.gray6}; border-radius: 0.625rem; - background: #f5f5f5 url(${({$beforeBgIcon})=> $beforeBgIcon || ''}) no-repeat left 1rem center; + background: #f5f5f5 url(${({ $beforeBgIcon }) => $beforeBgIcon || ''}) + no-repeat left 1rem center; background-size: 1rem 1rem; box-sizing: border-box; @media screen and (max-width: ${theme.screenSize.moLarge}) { @@ -33,8 +39,8 @@ export const InputModule = styled.input<{$beforeBgIcon:string}>` width: 100%; padding: 0 120px 0 52px; line-height: 67px; - background: #fff url(${({$beforeBgIcon})=> $beforeBgIcon || ''}) no-repeat left 20px center; - border: 1px solid ${theme.color.primary}; + background: #fff url(${({ $beforeBgIcon }) => $beforeBgIcon || ''}) + no-repeat left 20px center; border-radius: 15px; @media screen and (max-width: ${theme.screenSize.moLarge}) { padding: 0 100px 0 34px; @@ -45,4 +51,4 @@ export const InputModule = styled.input<{$beforeBgIcon:string}>` } } } -` \ No newline at end of file +`; diff --git a/src/components/common/modal/Modal.tsx b/src/components/common/modal/Modal.tsx new file mode 100644 index 000000000..a47a56938 --- /dev/null +++ b/src/components/common/modal/Modal.tsx @@ -0,0 +1,76 @@ +import { ModlaTitle } from '../../../styles/commonStyle'; +import ShareModal from '../../share/ShareModal'; +import Button from '../atoms/Button'; +import Input from '../atoms/Input'; +import CheckBox from '../atoms/CheckBox'; +import { + ModalWrap, + ModalContainer, + ModalHead, + ModalBody, + ModalFoot, + ModalDim, +} from './modalStyle'; +import { IModal } from './interface'; + +function bodyContent(body: string, data: IModal['$modalData']) { + if (body === 'input') { + return ; + } else if (body === 'sns') { + console.log(body, '여기'); + return ; + } else if (body === 'checkbox') { + if (!data) return null; + return ; + } +} + +interface IModalInfo extends IModal { + onOpen: boolean; + onClose: () => void; +} + +function Modal({ + onOpen, + onClose, + $title, + $titleDescText, + $body, + $buttonStyle, + $buttonText, + $modalData, +}: IModalInfo) { + const modalClose = () => { + onClose(); + }; + + if (!onOpen) return null; + return ( + <> + + + + + {$title} + {$titleDescText && {$titleDescText}} + + {$body && {bodyContent($body, $modalData)}} + {$buttonStyle && ( + + modalClose()}> + {$buttonText} + + + )} + modalClose()} + > + + + + + > + ); +} +export default Modal; diff --git a/src/components/common/modal/interface.ts b/src/components/common/modal/interface.ts new file mode 100644 index 000000000..0b3a4f19d --- /dev/null +++ b/src/components/common/modal/interface.ts @@ -0,0 +1,13 @@ +import { IFolderMenuButtonApi } from '../../../pages/folder/interface'; + +export interface IModalData { + data: any; +} +export interface IModal { + $title: string; + $titleDescText?: string | null; + $body?: string | null; + $buttonStyle?: string | null; + $buttonText?: string | null; + $modalData?: IModalData | null | IFolderMenuButtonApi; +} diff --git a/src/components/common/modal/modalStyle.ts b/src/components/common/modal/modalStyle.ts new file mode 100644 index 000000000..e82b6b50b --- /dev/null +++ b/src/components/common/modal/modalStyle.ts @@ -0,0 +1,47 @@ +import styled from 'styled-components'; +import { theme } from '../../../styles/theme'; + +export const ModalWrap = styled.section` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + padding: 0 20px; + z-index: 100; +`; +export const ModalDim = styled.div` + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.4); + z-index: 1; +`; +export const ModalContainer = styled.div` + position: absolute; + top: 50%; + left: 50%; + padding: 32px 40px; + width: 100%; + max-width: 360px; + border-radius: 15px; + transform: translate(-50%, -50%); + background-color: ${theme.color.white}; + z-index: 2; +`; +export const ModalHead = styled.div` + .desc { + padding-top: 8px; + color: ${theme.color.gray9}; + line-height: 1.375rem; + text-align: center; + } +`; +export const ModalBody = styled.div` + padding-top: 24px; +`; +export const ModalFoot = styled.div` + padding-top: 24px; +`; diff --git a/src/components/folder/ContantList.tsx b/src/components/folder/ContantList.tsx new file mode 100644 index 000000000..007431fea --- /dev/null +++ b/src/components/folder/ContantList.tsx @@ -0,0 +1,27 @@ +import PostCard from './PostCard'; +import { EmptyBox, PostCardWrap } from '../../pages/folder/folderStyle'; +import { IFolderContent } from '../../pages/folder/interface'; + +interface IFolderList { + contant: IFolderContent[] | [] | undefined | null; + loading: boolean; +} + +function ContantList({ contant, loading }: IFolderList) { + return ( + + {loading ? ( + Loading... + ) : contant ? ( + + {contant?.map((data) => ( + + ))} + + ) : ( + 저장된 링크가 없습니다. + )} + + ); +} +export default ContantList; diff --git a/src/components/folder/FolderButtonList.tsx b/src/components/folder/FolderButtonList.tsx new file mode 100644 index 000000000..c408ff9ea --- /dev/null +++ b/src/components/folder/FolderButtonList.tsx @@ -0,0 +1,52 @@ +import { memo } from 'react'; +import { BookMarkBtnList } from '../../pages/folder/folderStyle'; +import Button from '../common/atoms/Button'; +import { IFolderMenuButtonApi } from '../../pages/folder/interface'; + +interface IButtonList { + $menu: IFolderMenuButtonApi | null; + $loading: boolean; + $btnActive: number; + onClick: (api: string, index: number) => void; +} + +function FolderButtonList({ + $menu, + $loading, + $btnActive, + onClick, +}: IButtonList) { + const handleClick = (api: string, index: number) => { + onClick(api, index); + }; + return ( + + {$loading ? null : ( + <> + handleClick('all', -1)} + > + 전체 + + {$menu && + $menu.data?.map((item, i) => ( + handleClick(`${item.id}`, i)} + > + {item.name} + + ))} + > + )} + + ); +} + +export default memo(FolderButtonList); diff --git a/src/components/folder/FolderContentControll.tsx b/src/components/folder/FolderContentControll.tsx new file mode 100644 index 000000000..6def16e86 --- /dev/null +++ b/src/components/folder/FolderContentControll.tsx @@ -0,0 +1,58 @@ +import { memo } from 'react'; +import { ShareBox, ShareListBtn } from '../../pages/folder/folderStyle'; +import { SubTitle } from '../../styles/commonStyle'; +import Button from '../common/atoms/Button'; + +const folderControlBtn = [ + { + id: 'fcb1', + name: '공유', + imgSrc: '/assets/icon/icon_gray_share.svg', + body: 'folderShare', + }, + { + id: 'fcb2', + name: '이름 변경', + imgSrc: '/assets/icon/icon_gray_pen.svg', + body: 'folderChangeName', + }, + { + id: 'fcb3', + name: '삭제', + imgSrc: '/assets/icon/icon_gray_delete.svg', + body: 'folderDelete', + }, +]; + +interface iControll { + $title: string; + onclick: (type: any) => void; +} + +function FolderContentControll({ $title, onclick }: iControll) { + const handleModalOpen = (type: any) => { + onclick(type); + }; + return ( + + {$title} + {$title === '전체' || ( + + {folderControlBtn.map((btn) => ( + handleModalOpen(`${btn.body}`)} + > + {btn.name} + + ))} + + )} + + ); +} + +export default memo(FolderContentControll); diff --git a/src/components/folder/LinkAddHeader.tsx b/src/components/folder/LinkAddHeader.tsx new file mode 100644 index 000000000..81a70395d --- /dev/null +++ b/src/components/folder/LinkAddHeader.tsx @@ -0,0 +1,21 @@ +import { memo } from 'react'; +import { LinkAddHeadInner } from '../../pages/folder/folderStyle'; +import Input from '../common/atoms/Input'; + +function LinkAddHeader({ $inputIconImg }: { $inputIconImg: string }) { + return ( + + + 추가하기 + + + ); +} + +export default memo(LinkAddHeader); diff --git a/src/components/folder/PostCard.tsx b/src/components/folder/PostCard.tsx index 5d1f3b47b..a90d72c82 100644 --- a/src/components/folder/PostCard.tsx +++ b/src/components/folder/PostCard.tsx @@ -1,10 +1,23 @@ -import { Link } from "react-router-dom"; -import { DFlaxAlignCenterBtw, EllipsisLine } from "../../styles/commonStyle"; -import { calculateTimeAgo } from "../../utils/calcTilmAgo"; -import { BookMarkBtn, CardMenu, CardWrap } from "./PostCardStyle"; -import { useMemo, useState } from "react"; -import { IFolderContent } from "../../pages/folder/interface"; -const emptyImg = "/assets/logo/logo.svg"; +import { Link } from 'react-router-dom'; +import { DFlaxAlignCenterBtw, EllipsisLine } from '../../styles/commonStyle'; +import { calculateTimeAgo } from '../../utils/calcTilmAgo'; +import { BookMarkBtn, CardMenu, CardWrap } from './PostCardStyle'; +import { useMemo, useState } from 'react'; +import { + IFolderContent, + IFolderMenuButtonApi, +} from '../../pages/folder/interface'; +import Modal from '../common/modal/Modal'; +import { modalOrder } from '../../constant/modal'; +import { IModal } from '../common/modal/interface'; +import { FOLDERMENULISTAPI } from '../../constant/api'; +import useFetch from '../../hook/useFetch'; +const emptyImg = '/assets/logo/logo.svg'; + +function useFatchDataLoad(api: string) { + return useFetch(api); +} + export default function PostCard({ image_source, description, @@ -12,64 +25,97 @@ export default function PostCard({ }: IFolderContent) { const [bookMark, setBookMark] = useState(false); const [cardMenuShow, setCardMenuShow] = useState(false); + const [modalShow, setModalShow] = useState(false); + const [modalInfo, setModalInfo] = useState({ + $title: '', + $titleDescText: null, + $body: null, + $buttonStyle: null, + $buttonText: null, + $modalData: null, + }); + const { value: menu, isLoading: menuLoading } = + useFatchDataLoad(FOLDERMENULISTAPI); const handelerBookMarkActive = () => setBookMark((prev) => !prev); const handelerCardDropdown = () => setCardMenuShow((prev) => !prev); - const handleMenuItemClick = (e: React.MouseEvent) => { + const handleModalOpen = (type: string) => { + let modalInfo = modalOrder[type]; + if (type === 'folderInAdd') { + modalInfo = { + ...modalInfo, + $modalData: menu, + }; + } + setModalInfo(modalInfo); + setModalShow(true); setCardMenuShow(false); }; + const handleModalClose = () => { + setModalShow(false); + }; + const date = useMemo(() => { return new Date(`${created_at}`); }, []); return ( - - - 북마크버튼 - - - - - {image_source ? ( - - ) : ( - - )} - - - - {calculateTimeAgo(created_at ?? "")} - - - {description} - - {date.toLocaleString()} - - - - - + + - - - {cardMenuShow && ( - - - 삭제하기 - - - 폴더에 추가 - - - )} - - + 북마크버튼 + + + + + {image_source ? ( + + ) : ( + + )} + + + + {calculateTimeAgo(`${created_at}`)} + + + {description} + + {date.toLocaleString()} + + + + + + + + {cardMenuShow && ( + + handleModalOpen('folderDelete')} + > + 삭제하기 + + handleModalOpen('folderInAdd')} + > + 폴더에 추가 + + + )} + + + + > ); } diff --git a/src/components/folder/SearchInputBox.tsx b/src/components/folder/SearchInputBox.tsx new file mode 100644 index 000000000..1430d176b --- /dev/null +++ b/src/components/folder/SearchInputBox.tsx @@ -0,0 +1,28 @@ +import { memo } from 'react'; +import { BoxLinkSearch } from '../../pages/folder/folderStyle'; +import Input from '../common/atoms/Input'; + +interface ISearch { + $inputIconImg: string; + onchange: (value: string) => void; +} + +function SearchInputBox({ $inputIconImg, onchange }: ISearch) { + return ( + + + + + + ); +} + +export default memo(SearchInputBox); diff --git a/src/components/share/ShareModal.tsx b/src/components/share/ShareModal.tsx new file mode 100644 index 000000000..d48ad9bcd --- /dev/null +++ b/src/components/share/ShareModal.tsx @@ -0,0 +1,17 @@ +import { snsShare } from '../../constant/share'; +import Button from '../common/atoms/Button'; +import { ShareBox } from './shareStyle'; + +function ShareModal() { + return ( + + {snsShare.map((sns) => ( + + + {sns.name} + + ))} + + ); +} +export default ShareModal; diff --git a/src/components/share/shareStyle.ts b/src/components/share/shareStyle.ts new file mode 100644 index 000000000..19c631dd7 --- /dev/null +++ b/src/components/share/shareStyle.ts @@ -0,0 +1,4 @@ +import styled from "styled-components"; +import { DFlaxAlignCenterBtw } from "../../styles/commonStyle"; + +export const ShareBox = styled(DFlaxAlignCenterBtw)`` \ No newline at end of file diff --git a/src/constant/modal.ts b/src/constant/modal.ts new file mode 100644 index 000000000..15fcd9ace --- /dev/null +++ b/src/constant/modal.ts @@ -0,0 +1,49 @@ +import { IModal } from '../components/common/modal/interface'; + +interface IModalItem { + [key: string]: IModal; +} + +export const modalOrder: IModalItem = { + folderChangeName: { + $title: '폴더 이름 변경', + $body: 'input', + $buttonStyle: 'button--gradient large full', + $buttonText: '변경하기', + }, + + folderAdd: { + $title: '폴더 추가', + $body: 'input', + $buttonStyle: 'button--gradient large full', + $buttonText: '추가하기', + }, + + folderShare: { + $title: '폴더 공유', + $titleDescText: '폴더명', + $body: 'sns', + }, + + folderDelete: { + $title: '폴더 삭제', + $titleDescText: '폴더명', + $buttonStyle: 'button--red large full', + $buttonText: '삭제하기', + }, + + linkDelete: { + $title: '링크 삭제', + $titleDescText: 'httpw://www.abc.com', + $buttonStyle: 'button--gradient large full', + $buttonText: '삭제하기', + }, + + folderInAdd: { + $title: '폴더에 추가', + $titleDescText: '링크 주소', + $body: 'checkbox', + $buttonStyle: 'button--gradient large full', + $buttonText: '추가하기', + }, +}; diff --git a/src/constant/share.ts b/src/constant/share.ts new file mode 100644 index 000000000..09b5f81f9 --- /dev/null +++ b/src/constant/share.ts @@ -0,0 +1,20 @@ +const kakaoImg = '/assets/icon/icon_share_kakao.svg'; +const faceBookImg = '/assets/icon/icon_share_face.svg'; +const linkImg = '/assets/icon/icon_share_link.svg'; +export const snsShare = [ + { + id:'sns1', + name:'카카오톡', + src:kakaoImg, + }, + { + id:'sns2', + name:'페이스북', + src:faceBookImg, + }, + { + id:'sns3', + name:'링크 복사', + src:linkImg, + }, +]; \ No newline at end of file diff --git a/src/hook/useFetch.tsx b/src/hook/useFetch.tsx index 9ef3f28ee..07ac96f1b 100644 --- a/src/hook/useFetch.tsx +++ b/src/hook/useFetch.tsx @@ -1,8 +1,8 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState } from 'react'; export default function useFetch(url: string) { const [value, setValue] = useState(null); - const [isLoading, setIsLoading] = useState(false); + const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { @@ -16,7 +16,7 @@ export default function useFetch(url: string) { } catch (error: any) { setError(error); } finally { - setIsLoading(true); + setIsLoading(false); } } fromDate(); diff --git a/src/pages/folder/Index.tsx b/src/pages/folder/Index.tsx index 5f9ff936a..3d2e05f2f 100644 --- a/src/pages/folder/Index.tsx +++ b/src/pages/folder/Index.tsx @@ -1,180 +1,137 @@ -import PostCard from "../../components/folder/PostCard"; -import { ContainBody, ContainHead, SubTitle } from "../../styles/commonStyle"; -import { - BookMarkBtnList, - BookmarkBox, - BodyInner, - ShareBox, - EmptyBox, - BoxLinkSearch, - ShareListBtn, - PostCardWrap, - LinkAddHeadInner, -} from "./folderStyle"; +import { ContainBody, ContainHead } from '../../styles/commonStyle'; +import { BookmarkBox, BodyInner } from './folderStyle'; -import Button from "../../components/common/atoms/Button"; -import Input from "../../components/common/atoms/Input"; -import useFetch from "../../hook/useFetch"; -import { FOLDERCONTANTLISTAPI, FOLDERMENULISTAPI } from "../../constant/api"; -import { IFolderContentApi, IFolderMenuButtonApi } from "./interface"; -import { useEffect, useState } from "react"; +import Button from '../../components/common/atoms/Button'; +import useFetch from '../../hook/useFetch'; +import Modal from '../../components/common/modal/Modal'; +import { FOLDERCONTANTLISTAPI, FOLDERMENULISTAPI } from '../../constant/api'; +import { IFolderContentApi, IFolderMenuButtonApi } from './interface'; +import { useEffect, useState } from 'react'; +import { IModal } from '../../components/common/modal/interface'; +import { modalOrder } from '../../constant/modal'; +import ContantList from '../../components/folder/ContantList'; +import LinkAddHeader from '../../components/folder/LinkAddHeader'; +import SearchInputBox from '../../components/folder/SearchInputBox'; +import FolderButtonList from '../../components/folder/FolderButtonList'; +import FolderContentControll from '../../components/folder/FolderContentControll'; -const add = "/assets/icon/icon_primary_add.svg"; -const search = "/assets/icon/icon_search.svg"; -const link = "/assets/icon/icon_primaty_link.svg"; - -const folderControlBtn = [ - { - id: "fcb1", - name: "공유", - imgSrc: "/assets/icon/icon_gray_share.svg", - }, - { - id: "fcb2", - name: "이름 변경", - imgSrc: "/assets/icon/icon_gray_pen.svg", - }, - { - id: "fcb3", - name: "삭제", - imgSrc: "/assets/icon/icon_gray_delete.svg", - }, -]; - -export interface aaa { - setHeadFixed: any; -} +const addImage = '/assets/icon/icon_primary_add.svg'; +const searchImage = '/assets/icon/icon_search.svg'; +const linkImage = '/assets/icon/icon_primaty_link.svg'; function useFatchDataLoad(api: string) { return useFetch(api); } function Index() { - const [title, setTitle] = useState("전체"); + const [title, setTitle] = useState('전체'); const [btnActive, setBtnActive] = useState(-1); - const [dynamicAPI, setDynamicAPI] = useState(FOLDERCONTANTLISTAPI); + const [dynamicAPI, setDynamicAPI] = useState(FOLDERCONTANTLISTAPI); // 버튼리스트 클릭시 해당 컨텐트 노출 + const [modalShow, setModalShow] = useState(false); + const [modalInfo, setModalInfo] = useState({ + $title: '', + $titleDescText: null, + $body: null, + $buttonStyle: null, + $buttonText: null, + $modalData: null, + }); const { value: menu, isLoading: menuLoading } = useFatchDataLoad(FOLDERMENULISTAPI); const { value: contant, isLoading: contantLoading } = useFatchDataLoad(dynamicAPI); + const [search, setSearch] = useState(); - const handleClick = (api: string, index: number | undefined) => { - if (menu === undefined || api === "") return; + // 폴더리스트버튼 + const handleClick = (api: string, index: number) => { + if (menu === undefined || api === '') return; if (index !== undefined) { setBtnActive(index); } - if (api === "all") { + if (api === 'all') { setDynamicAPI(FOLDERCONTANTLISTAPI); - setTitle("전체"); + setTitle('전체'); return; } - // api/users/1/links?folderId={해당 폴더 ID} setDynamicAPI(`${FOLDERCONTANTLISTAPI}?folderId=${api}`); const result = menu?.data.filter((data) => +data.id === +api); - result && setTitle(result[0]?.name as ""); + result && setTitle(result[0]?.name as ''); + }; + + // 모달오픈 + const handleModalOpen = (type: string) => { + let modalInfo = modalOrder[type]; + if (type === 'folderInAdd') { + modalInfo = { + ...modalInfo, + $modalData: menu, + }; + } + setModalInfo(modalInfo); + setModalShow(true); }; - useEffect(() => {}, [menu, contant]); + // 모달닫기 + const handleModalClose = () => { + setModalShow(false); + }; + + // 검색어 filter + const handelSearch = (value: string) => { + let filter; + if (value) { + filter = contant?.data.filter((con) => { + if (!con) return; + return ( + con.description?.includes(value) || + con.title?.includes(value) || + con.url?.includes(value) + ); + }); + console.log(filter); + setSearch(filter); + return; + } + setSearch(contant?.data); + }; + + // contant list + const contantSearch = search ? search : contant?.data; return ( <> - - - + {/* 검색창 */} - - - - {/* 폴더버튼 리스트 */} + + {/* 폴더 리스트 버튼 */} - - {menuLoading && ( - <> - - 전체 - - {menu && - menu.data.map((menu: any, i) => ( - - {menu.name} - - ))} - > - )} - + - + handleModalOpen('folderAdd')} + > 폴더추가 - {/* 버튼 수정 */} - - {title} - {title === "전체" || ( - - {folderControlBtn.map((btn) => ( - - {btn.name} - - ))} - - )} - - - {contantLoading ? ( - contant && contant.data.length !== 0 ? ( - - {contant && - contant.data.map((data) => ( - - ))} - - ) : ( - 저장된 링크가 없습니다. - ) - ) : ( - Loading... - )} + {/* 설정 버튼 */} + + {/* 컨텐츠 리스트 */} + + > ); } diff --git a/src/pages/folder/Shared.tsx b/src/pages/folder/Shared.tsx index 8bbd9eb2e..8fbfeb07c 100644 --- a/src/pages/folder/Shared.tsx +++ b/src/pages/folder/Shared.tsx @@ -1,19 +1,13 @@ -import PostCard from "../../components/folder/PostCard"; -import { ContainBody, ContainHead } from "../../styles/commonStyle"; -import { - BodyInner, - EmptyBox, - ShareHeadInner, - PostCardWrap, - BoxLinkSearch, -} from "./folderStyle"; -import { TitleMs } from "../../styles/commonStyle"; -import Input from "../../components/common/atoms/Input"; -import { SHAREDCONTANTAPI } from "../../constant/api"; -import useFetch from "../../hook/useFetch"; -import { IFolderContent } from "./interface"; -const logo = "/assets/logo/logo_codeit.svg"; -const search = "/assets/icon/icon_search.svg"; +import { ContainBody, ContainHead } from '../../styles/commonStyle'; +import { BodyInner, ShareHeadInner, BoxLinkSearch } from './folderStyle'; +import { TitleMs } from '../../styles/commonStyle'; +import Input from '../../components/common/atoms/Input'; +import { SHAREDCONTANTAPI } from '../../constant/api'; +import useFetch from '../../hook/useFetch'; +import { IFolderContent } from './interface'; +import ContantList from '../../components/folder/ContantList'; +const logo = '/assets/logo/logo_codeit.svg'; +const search = '/assets/icon/icon_search.svg'; interface IFolder { $title?: string; @@ -38,36 +32,26 @@ function useFatchDataLoad(api: string) { function Shared() { const { value, isLoading } = useFatchDataLoad(SHAREDCONTANTAPI); + return ( <> - {isLoading ? ( - value ? ( - - {value && - value.map((data) => )} - - ) : ( - 저장된 링크가 없습니다. - ) - ) : ( - Loading... - )} + > diff --git a/src/pages/folder/folderStyle.ts b/src/pages/folder/folderStyle.ts index e6fe1f990..7f87c18ba 100644 --- a/src/pages/folder/folderStyle.ts +++ b/src/pages/folder/folderStyle.ts @@ -1,6 +1,12 @@ -import styled, { css } from "styled-components"; -import { ContainBodyInner, ContainHeadInner, DFlaxAlignCenter, dflexBtw, innerLarge } from "../../styles/commonStyle"; -import { theme } from "../../styles/theme"; +import styled, { css } from 'styled-components'; +import { + ContainBodyInner, + ContainHeadInner, + DFlaxAlignCenter, + dflexBtw, + innerLarge, +} from '../../styles/commonStyle'; +import { theme } from '../../styles/theme'; export const ShareHeadInner = styled(ContainHeadInner)` img { @@ -14,17 +20,18 @@ export const ShareHeadInner = styled(ContainHeadInner)` line-height: 3rem; padding-top: 1.25rem; } -` +`; export const LinkAddHeadInner = styled.div` position: relative; max-width: 864px; margin: 60px auto 90px; padding: 0 32px 0; - .button--gradient { + .button--gradient.mideum { position: absolute; right: 52px; top: 16px; + width: 80px; } @media screen and (max-width: ${theme.screenSize.moLarge}) { margin: 20px auto 40px; @@ -33,33 +40,33 @@ export const LinkAddHeadInner = styled.div` top: 8px; } } -` +`; export const BodyInner = styled(ContainBodyInner)` ${innerLarge} -` +`; export const PostCardWrap = styled.div` display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5625rem 1.25rem; - @media screen and (max-width: 1124px){ + @media screen and (max-width: 1124px) { grid-template-columns: repeat(2, 1fr); gap: 1.5rem 1.5rem; } - @media screen and (max-width: ${theme.screenSize.moLarge}){ + @media screen and (max-width: ${theme.screenSize.moLarge}) { grid-template-columns: repeat(1, 1fr); gap: 1.25rem; } -` +`; export const EmptyBox = styled.div` padding: 41px 0 35px; line-height: 24px; text-align: center; -` +`; export const FolderBtnBox = css` ${dflexBtw} padding-bottom: 24px; gap: 0 32px; -` +`; export const BookmarkBox = styled.div` ${FolderBtnBox} .button--icon-after { @@ -70,7 +77,7 @@ export const BookmarkBox = styled.div` padding-bottom: 28px; .button--icon-after { position: fixed; - bottom:101px; + bottom: 101px; left: 50%; padding-right: 16px; width: 127px; @@ -78,13 +85,14 @@ export const BookmarkBox = styled.div` color: ${theme.color.white}; border: 1px solid ${theme.color.white}; border-radius: 50em; - background: ${theme.color.primary} url('/assets/icon/icon_white_add.svg') no-repeat; + background: ${theme.color.primary} url('/assets/icon/icon_white_add.svg') + no-repeat; background-position: right 22px center; transform: translateX(-50%); z-index: 5; } } -` +`; export const ShareBox = styled.div` ${FolderBtnBox} @media screen and (max-width: ${theme.screenSize.moLarge}) { @@ -95,25 +103,26 @@ export const ShareBox = styled.div` padding-bottom: 12px; } } -` +`; export const BookMarkBtnList = styled(DFlaxAlignCenter)` flex: 1; flex-shrink: 0; flex-wrap: wrap; - gap:8px; -` + gap: 8px; +`; export const ShareListBtn = styled(DFlaxAlignCenter)` - gap:0 12px; + gap: 0 12px; .button--icon-before { font-size: 14px; font-weight: 600; line-height: 29px; color: ${theme.color.gray9}; } -` +`; export const BoxLinkSearch = styled.div` + position: relative; margin-bottom: 40px; @media screen and (max-width: ${theme.screenSize.moLarge}) { - margin-bottom:32px; + margin-bottom: 32px; } -` \ No newline at end of file +`; diff --git a/src/pages/folder/interface.ts b/src/pages/folder/interface.ts index 5dd3232e4..307ffd9ea 100644 --- a/src/pages/folder/interface.ts +++ b/src/pages/folder/interface.ts @@ -1,7 +1,7 @@ // folder menu button api export interface IFolderMenuButton { id: number; - created_at?: string; + created_at?: Date; name?: string; user_id?: number; favorite?: boolean; @@ -15,15 +15,15 @@ export interface IFolderMenuButtonApi { // folder contant api export interface IFolderContent { - id: number, - created_at: string, - updated_at: string | null, - url: string, - title: string | null, - description: string | null, - image_source: string | null, - folder_id: string | null + id: number; + created_at: Date; + updated_at: Date | null; + url: string; + title: string | null; + description: string | null; + image_source: string | null; + folder_id: string | null; } export interface IFolderContentApi { data: IFolderContent[]; -} \ No newline at end of file +} diff --git a/src/styles/commonStyle.ts b/src/styles/commonStyle.ts index 5d90eece8..a2d0830a5 100644 --- a/src/styles/commonStyle.ts +++ b/src/styles/commonStyle.ts @@ -2,7 +2,6 @@ import styled, { css } from "styled-components"; import { theme } from "./theme"; -import { NavLink } from "react-router-dom"; // ==== 정렬 ===== @@ -49,6 +48,12 @@ export const TitleMs = styled.h3` line-height: 1.79rem; } ` +export const ModlaTitle = styled.h2` + font-size: ${theme.font.l}; + font-weight: 700; + text-align: center; +` + export const SubTitle = styled.h3` font-size: 1.5rem; line-height: 1.8125rem; diff --git a/src/styles/globalStyle.ts b/src/styles/globalStyle.ts index de7692f94..e62294d6b 100644 --- a/src/styles/globalStyle.ts +++ b/src/styles/globalStyle.ts @@ -1,16 +1,11 @@ // reset css -import { ExecutionProps, createGlobalStyle } from "styled-components"; -import { theme } from "./theme"; +import { ExecutionProps, createGlobalStyle } from 'styled-components'; +import { theme } from './theme'; export const GlobalStyle = createGlobalStyle` - @font-face { - font-family: 'Pretendard-Regular'; - src: url('https://cdn.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff') format('woff'); - font-weight: 400; - font-style: normal; - } + @import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css'); html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, @@ -33,7 +28,7 @@ export const GlobalStyle = createGlobalStyle` font-weight: 400; vertical-align: baseline; box-sizing: border-box; - font-family: 'Pretendard-Regular', Arial, Helvetica, sans-serif; + font-family: 'Pretendard', Arial, Helvetica, sans-serif; } article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
{date.toLocaleString()}