diff --git a/dist/App.js b/dist/App.js new file mode 100644 index 0000000000..d8bc624244 --- /dev/null +++ b/dist/App.js @@ -0,0 +1,5 @@ +import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime"; +const App = () => { + return _jsx(_Fragment, {}); +}; +export default App; diff --git a/dist/Main.js b/dist/Main.js new file mode 100644 index 0000000000..40062bf33b --- /dev/null +++ b/dist/Main.js @@ -0,0 +1,9 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; +import Shared from "./pages/Shared"; +import Folder from "./pages/Folder"; +import { Home } from "./pages/Home"; +function Main() { + return (_jsx(BrowserRouter, { children: _jsxs(Routes, { children: [_jsx(Route, { path: "/", element: _jsx(Home, {}) }), _jsx(Route, { path: "/shared", element: _jsx(Shared, {}) }), _jsx(Route, { path: "/folder", element: _jsx(Folder, {}) })] }) })); +} +export default Main; diff --git a/dist/api/api.js b/dist/api/api.js new file mode 100644 index 0000000000..70c1f8cc4e --- /dev/null +++ b/dist/api/api.js @@ -0,0 +1,45 @@ +const BASE_URL = "https://bootcamp-api.codeit.kr/api/"; +export const getFolderInfo = async () => { + try { + const response = await fetch(`${BASE_URL}sample/folder`); + const result = await response.json(); + return result; + } + catch (error) { + console.log(error); + } +}; +export const getUserInfo = async () => { + try { + const response = await fetch(`${BASE_URL}sample/user`); + const result = await response.json(); + return result; + } + catch (error) { + console.log(error); + } +}; +export const getFolderList = async () => { + try { + const response = await fetch(`${BASE_URL}users/1/folders`); + const result = await response.json(); + // console.log(result); + return result; + } + catch (error) { + console.log(error); + } +}; +export const getAllLinkData = async (id) => { + const url = id + ? `${BASE_URL}users/1/folders/${id}` + : `${BASE_URL}users/1/links`; + try { + const response = await fetch(url); + const result = await response.json(); + return result; + } + catch (error) { + console.log(error); + } +}; diff --git a/dist/components/Folder/FolderInput.js b/dist/components/Folder/FolderInput.js new file mode 100644 index 0000000000..43d64f1394 --- /dev/null +++ b/dist/components/Folder/FolderInput.js @@ -0,0 +1,76 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import { styled } from "styled-components"; +import Link from "../../assets/icons/link.svg"; +import { BlueButton } from "../common/BlueButton"; +import { forwardRef } from "react"; +const FolderInput = forwardRef(({ setIsVisible, $isAddLinkVisible }, ref) => { + const onAddLinkButtonClick = () => { + setIsVisible("폴더 추가"); + }; + return (_jsx(BackGround, { ref: ref, "$isAddLinkVisible": $isAddLinkVisible, children: !$isAddLinkVisible ? (_jsx(BackGroundFixed, { children: _jsxs(InputBoxFixed, { children: [_jsx("img", { src: Link, alt: "LinkIcon" }), _jsx(Input, { placeholder: "\uB9C1\uD06C\uB97C \uCD94\uAC00\uD574 \uBCF4\uC138\uC694." }), _jsx(BlueButton, { width: "80px", height: "auto", padding: "10px 16px", margin: "0px", text: "\uCD94\uAC00\uD558\uAE30", fontSize: "", radius: "8px", onBtnHandle: () => onAddLinkButtonClick() })] }) })) : (_jsxs(InputBox, { "$isAddLinkVisible": $isAddLinkVisible, children: [_jsx("img", { src: Link, alt: "LinkIcon" }), _jsx(Input, { placeholder: "\uB9C1\uD06C\uB97C \uCD94\uAC00\uD574 \uBCF4\uC138\uC694." }), _jsx(BlueButton, { width: "80px", height: "auto", padding: "10px 16px", margin: "0px", text: "\uCD94\uAC00\uD558\uAE30", fontSize: "", radius: "8px", onBtnHandle: () => onAddLinkButtonClick() })] })) })); +}); +export default FolderInput; +const BackGround = styled.div ` + background-color: var(--Grey_100); + display: flex; + justify-content: center; + position: relative; +`; +const BackGroundFixed = styled.div ` + background-color: var(--Grey_100); + display: flex; + justify-content: center; + position: fixed; + left: 0; + right: 0; + bottom: 0; + z-index: 9999; +`; +const InputBox = styled.div ` + width: 800px; + padding: 16px 20px; + border-radius: 15px; + border: 1px solid var(--Linkbrary-primary-color, #6d6afe); + background: var(--Linkbrary-white, #fff); + margin: 60px auto 90px; + display: flex; + flex-direction: row; + + @media (max-width: 1124px) { + width: 704px; + } + @media (max-width: 774px) { + width: 325px; + } +`; +const InputBoxFixed = styled(InputBox) ` + margin: 24px auto; + + @media (max-width: 360px) { + margin: 16px auto; + } +`; +const Input = styled.input ` + width: 100%; + border: none; + margin-left: 12px; + + &:focus { + outline: none; + } + + &::placeholder { + color: #9fa6b2; + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 24px; + } + + @media (max-width: 774px) { + &::placeholder { + font-size: 14px; + } + } +`; diff --git a/dist/components/Folder/FolderTitle.js b/dist/components/Folder/FolderTitle.js new file mode 100644 index 0000000000..5d79fd9736 --- /dev/null +++ b/dist/components/Folder/FolderTitle.js @@ -0,0 +1,71 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import styled from "styled-components"; +import share from "../../assets/icons/share.svg"; +import pen from "../../assets/icons/pen.svg"; +import trash from "../../assets/icons/trash.svg"; +const FolderTitle = ({ titleName, setIsModal }) => { + return (_jsxs(Container, { children: [_jsx(Title, { children: titleName }), titleName !== "전체" && (_jsxs(OptionBox, { children: [_jsxs(Option, { onClick: () => { + setIsModal("공유"); + }, children: [_jsx(OptionIcon, { src: share }), _jsx(OptionText, { children: "\uACF5\uC720" })] }), _jsxs(Option, { onClick: () => { + setIsModal("이름 변경"); + }, children: [_jsx(OptionIcon, { src: pen }), _jsx(OptionText, { children: "\uC774\uB984 \uBCC0\uACBD" })] }), _jsxs(Option, { onClick: () => { + setIsModal("삭제"); + }, children: [_jsx(OptionIcon, { src: trash }), _jsx(OptionText, { children: "\uC0AD\uC81C" })] })] }))] })); +}; +const Container = styled.div ` + width: 1060px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + margin: 24px auto; + + @media (max-width: 1124px) { + width: 704px; + } + @media (max-width: 774px) { + width: 325px; + flex-direction: column; + align-items: flex-start; + gap: 12px; + margin: 28px auto 20px; + } +`; +const Title = styled.span ` + color: #000; + font-family: Pretendard; + font-size: 24px; + font-style: normal; + font-weight: 600; + line-height: normal; + letter-spacing: -0.2px; +`; +const OptionBox = styled.div ` + display: flex; + flex-direction: row; + align-items: center; + gap: 12px; +`; +const Option = styled.div ` + display: flex; + flex-direction: row; + align-items: center; + gap: 4px; + + &:hover { + cursor: pointer; + } +`; +const OptionIcon = styled.img ` + width: 18px; + height: 18px; +`; +const OptionText = styled.span ` + color: #9fa6b2; + font-family: Pretendard; + font-size: 14px; + font-style: normal; + font-weight: 600; + line-height: normal; +`; +export default FolderTitle; diff --git a/dist/components/Folder/Menus.js b/dist/components/Folder/Menus.js new file mode 100644 index 0000000000..0343ce3082 --- /dev/null +++ b/dist/components/Folder/Menus.js @@ -0,0 +1,94 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import { useState } from "react"; +import { getFolderList } from "../../api/api"; +import GlobalStyle from "../common/GlobalStyle"; +import styled from "styled-components"; +import union from "../../assets/icons/Union.svg"; +import { COLORS } from "../../constants/colors"; +import { useGetPromise } from "../../hooks/uesGetPromise"; +const Menus = ({ changeTitle, changeID, setIsVisible }) => { + const listsData = useGetPromise(getFolderList); + const lists = listsData?.data ?? []; + if (lists[0]) { + lists[0].name === "전체" || lists.unshift({ id: 0, name: "전체" }); + } + const initialButtonColors = lists.reduce((colors, list) => { + colors[list.name] = COLORS.White; + return colors; + }, {}); + const [buttonColors, setButtonColors] = useState(initialButtonColors); + const handleClick = async (name, id) => { + changeTitle(name); + changeID(id); + setButtonColors((prevColors) => { + return { + ...initialButtonColors, + [name]: prevColors[name] === COLORS.White ? COLORS.Primary : COLORS.White, + }; + }); + }; + return (_jsxs(Container, { children: [_jsx(GlobalStyle, {}), _jsx(ButtonDiv, { children: lists.map((val) => (_jsx(Button, { onClick: () => handleClick(val.name, val.id), color: buttonColors[val.name], id: val.name, children: val.name }, val.id))) }), _jsxs(AddFolderDiv, { onClick: () => setIsVisible("폴더 추가"), children: [_jsx(AddFolder, { children: "\uD3F4\uB354 \uCD94\uAC00" }), _jsx("img", { src: union, alt: "unionIcon" })] })] })); +}; +const Container = styled.div ` + width: 1080px; + margin: 0px auto; + display: flex; + padding: 8px 12px; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + + @media (max-width: 1124px) { + width: 704px; + } + @media (max-width: 774px) { + width: 325px; + padding: 0px; + } +`; +const ButtonDiv = styled.div ` + display: flex; + flex-direction: row; + gap: 8px; + flex-wrap: wrap; + gap: 12px 8px; +`; +const Button = styled.button ` + min-width: max-content; + padding: 8px 12px; + border-radius: 5px; + border: 1px solid ${COLORS.Primary}; + background-color: ${({ color }) => color || COLORS.White}; + color: ${({ color = COLORS.White }) => color === COLORS.White ? "#000000" : "#FFFFFF"}; + transition: all 0.3s ease-in-out; + + &:hover { + cursor: pointer; + } +`; +const AddFolderDiv = styled.div ` + margin: 8px; + display: flex; + flex-direction: row; + + &:hover { + cursor: pointer; + } + + @media (max-width: 774px) { + display: none; + } +`; +const AddFolder = styled.span ` + color: #6d6afe; + text-align: center; + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: normal; + letter-spacing: -0.3px; + margin-right: 4px; +`; +export default Menus; diff --git a/dist/components/common/BlueButton.js b/dist/components/common/BlueButton.js new file mode 100644 index 0000000000..16eab82c9d --- /dev/null +++ b/dist/components/common/BlueButton.js @@ -0,0 +1,23 @@ +import { jsx as _jsx } from "react/jsx-runtime"; +import { styled } from "styled-components"; +import { COLORS } from "../../constants/colors"; +export const BlueButton = ({ text, width, height, margin, padding, fontSize, radius, onBtnHandle, }) => { + return (_jsx(Button, { width: width, height: height, margin: margin, padding: padding, color: COLORS.White, fontSize: fontSize, radius: radius, onClick: () => (onBtnHandle ? onBtnHandle() : null), children: text })); +}; +const Button = styled.button ` + display: block; + width: ${({ width }) => width || "auto"}; + min-width: max-content; + height: ${({ height }) => height || "auto"}; + border: 0px; + border-radius: ${({ radius }) => radius || "0px"}; + margin: ${({ margin }) => margin || "auto"}; + padding: ${({ padding }) => padding || "auto"}; + background: linear-gradient(91deg, #6d6afe 0.12%, #6ae3fe 101.84%); + cursor: pointer; + + color: ${({ color }) => color}; + font-size: ${({ fontSize }) => fontSize || "14px"}; + font-weight: 600; + line-height: 21.6px; +`; diff --git a/dist/components/common/FolderItem.js b/dist/components/common/FolderItem.js new file mode 100644 index 0000000000..83859adebf --- /dev/null +++ b/dist/components/common/FolderItem.js @@ -0,0 +1,111 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import styled from "styled-components"; +import { useState } from "react"; +import { CalcTime } from "../../utils/calculator"; +import { ReactComponent as Star } from "../../assets/icons/card_star.svg"; +import { ReactComponent as Kebab } from "../../assets/icons/kebab.svg"; +import logo from "../../assets/icons/logo.png"; +import { PopOver } from "./modals/PopOver"; +import "../../styles/shared.css"; +function FolderItem({ item, $isModalVisible, setIsModalVisible }) { + const [isHovering, setIsHovering] = useState(false); + const { imageSource, createdAt, description, url, id } = item; + const { created_at, favorite, image_source } = item; + const [isPopOverVisible, setIsPopOverVisible] = useState(false); + let time = ""; + let img_src = ""; + if (created_at) { + time = CalcTime(created_at); + img_src = image_source; + } + else { + time = CalcTime(createdAt); + img_src = imageSource; + } + const handleMouseOver = () => { + setIsHovering(true); + }; + const handleMouseOut = () => { + setIsHovering(false); + }; + return (_jsx("a", { href: url, target: "_blank", rel: "noreferrer", children: _jsxs(Folder, { onMouseOver: handleMouseOver, onMouseOut: handleMouseOut, children: [_jsxs(ImageContainer, { children: [img_src ? (_jsx("img", { src: img_src, alt: id, className: `folderImage ${isHovering ? "grow" : "folder-img"}` })) : (_jsx(DefaultImage, { children: _jsx("img", { src: logo, alt: "logo" }) })), _jsx(Star, { className: "star", fill: favorite ? "purple" : "black" })] }), _jsxs(TextBox, { children: [_jsxs(TimeContainer, { children: [_jsx(TimeText, { children: time }), _jsx(Kebab, { onClick: (e) => { + e.preventDefault(); + setIsPopOverVisible(!isPopOverVisible); + } }), _jsx(PopOver, { "$isPopOverVisible": isPopOverVisible, setIsPopOverVisible: setIsPopOverVisible, "$options": ["삭제하기", "폴더에 추가"], "$modalType": ["삭제", "폴더에 추가"], "$top": "20px", "$right": "-70px", "$isModalVisible": $isModalVisible, setIsModalVisible: setIsModalVisible })] }), _jsx(Description, { children: description }), _jsx(DateText, { children: "2023. 3. 26" })] })] }) })); +} +const Folder = styled.div ` + display: flex; + flex-direction: column; + justify-content: space-between; + border-radius: 15px; + box-shadow: 0px 5px 25px 0px rgba(0, 0, 0, 0.08); + position: relative; +`; +const ImageContainer = styled.div ` + width: 100%; + height: 230px; + overflow: hidden; + margin: 0px; + border-radius: 15px 15px 0px 0px; + display: flex; + justify-content: center; + align-items: center; +`; +const DefaultImage = styled.div ` + width: 100%; + height: 100%; + background-color: #dddfff; + display: flex; + justify-content: center; + align-items: center; +`; +const TextBox = styled.div ` + width: 100%; + height: 135px; + display: flex; + flex-direction: column; + gap: 10px; + padding: 15px 20px; + border-radius: 0px 0px 15px 15px; +`; +const Description = styled.div ` + width: 100%; + height: 49px; + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 24px; + margin: 0px; + color: #000; + + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; +`; +const TimeContainer = styled.div ` + display: flex; + flex-direction: row; + justify-content: space-between; + position: relative; +`; +const TimeText = styled.span ` + font-size: 13px; + font-weight: 400; + color: #666; + margin: 0px; +`; +const DateText = styled.span ` + overflow: hidden; + color: #333; + text-overflow: ellipsis; + white-space: nowrap; + font-family: Pretendard; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; + margin: 0px; +`; +export default FolderItem; diff --git a/dist/components/common/FolderList.js b/dist/components/common/FolderList.js new file mode 100644 index 0000000000..6e06ce1fab --- /dev/null +++ b/dist/components/common/FolderList.js @@ -0,0 +1,8 @@ +import { jsx as _jsx } from "react/jsx-runtime"; +import FolderItem from "./FolderItem"; +function FolderList({ items, $isModalVisible, setIsModalVisible }) { + return (_jsx("article", { children: _jsx("div", { className: "folders-gridBox", children: items.map((item) => { + return (_jsx(FolderItem, { item: item, "$isModalVisible": $isModalVisible, setIsModalVisible: setIsModalVisible }, item.id)); + }) }) })); +} +export default FolderList; diff --git a/dist/components/common/FooterElement.js b/dist/components/common/FooterElement.js new file mode 100644 index 0000000000..c17b975e5c --- /dev/null +++ b/dist/components/common/FooterElement.js @@ -0,0 +1,10 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import facebookIcon from "../../assets/icons/icon_facebook.png"; +import twitterIcon from "../../assets/icons/icon_twitter.png"; +import youtubeIcon from "../../assets/icons/icon_youtube.png"; +import instagramIcon from "../../assets/icons/icon_instagram.png"; +import "../../styles/common.css"; +function FooterElement() { + return (_jsx("footer", { children: _jsxs("div", { className: "footer-frame", children: [_jsx("div", { id: "footer-codeit", children: _jsx("p", { children: "@codeit - 2023" }) }), _jsxs("div", { id: "footer-notice", children: [_jsx("a", { href: "privacy.html", children: _jsx("p", { children: "Privacy Policy" }) }), _jsx("a", { href: "faq.html", children: _jsx("p", { children: "FAQ" }) })] }), _jsxs("div", { id: "footer-icons", children: [_jsx("a", { href: "https://www.facebook.com/", target: "_blank", rel: "noopener noreferrer", children: _jsx("img", { src: facebookIcon, alt: "facebook_icon" }) }), _jsx("a", { href: "https://twitter.com/", target: "_blank", rel: "noopener noreferrer", children: _jsx("img", { src: twitterIcon, alt: "twitter`_icon" }) }), _jsx("a", { href: "https://www.youtube.com/", target: "_blank", rel: "noopener noreferrer", children: _jsx("img", { src: youtubeIcon, alt: "youtube_icon" }) }), _jsx("a", { href: "https://www.instagram.com/", target: "_blank", rel: "noopener noreferrer", children: _jsx("img", { src: instagramIcon, alt: "instagram_icon" }) })] })] }) })); +} +export default FooterElement; diff --git a/dist/components/common/GlobalStyle.js b/dist/components/common/GlobalStyle.js new file mode 100644 index 0000000000..9184bb91ab --- /dev/null +++ b/dist/components/common/GlobalStyle.js @@ -0,0 +1,9 @@ +import { createGlobalStyle } from "styled-components"; +const GlobalStyle = createGlobalStyle ` + * { + margin: 0; + padding: 0; + box-sizing: border-box; + } +`; +export default GlobalStyle; diff --git a/dist/components/common/HeaderElement.js b/dist/components/common/HeaderElement.js new file mode 100644 index 0000000000..3ddc26a9aa --- /dev/null +++ b/dist/components/common/HeaderElement.js @@ -0,0 +1,31 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import styled from "styled-components"; +import logo from "../../assets/Linkbrary.png"; +import profile from "../../assets/icons/icon_myprofile.png"; +import { getUserInfo } from "../../api/api"; +import { useGetPromise } from "../../hooks/uesGetPromise"; +import "../../styles/common.css"; +function HeaderElement({ $positionval }) { + const user = useGetPromise(getUserInfo); + const email = user?.email; + const profileImageSource = user?.profileImageSource; + return (_jsxs(Header, { "$positionval": $positionval, children: [_jsx("img", { src: logo, alt: "logo" }), _jsx("div", { className: "myProfile", children: user ? (_jsxs("div", { id: "myProfileName", children: [_jsx("div", { id: "myProfile-back_img", children: _jsx("img", { src: profileImageSource ? profileImageSource : profile, id: "myProfile-img", alt: "myProfile-img" }) }), _jsx("span", { id: "myEmail", children: email })] })) : (_jsx("a", { href: "/signup.html", children: _jsx("button", { id: "LoginBtn", type: "button", children: "\uB85C\uADF8\uC778" }) })) })] })); +} +const Header = styled.div ` + background-color: var(--Grey_100); + padding: 20px 200px; + position: ${({ $positionval }) => ($positionval ? $positionval : "sticky")}; + top: 0; + z-index: 2; + display: flex; + justify-content: space-between; + align-items: center; + + @media (max-width: 1124px) { + padding: 32px; + } + @media (max-width: 774px) { + padding: 18px 32px; + } +`; +export default HeaderElement; diff --git a/dist/components/common/Input.js b/dist/components/common/Input.js new file mode 100644 index 0000000000..5899f6a0c4 --- /dev/null +++ b/dist/components/common/Input.js @@ -0,0 +1,32 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import styled from "styled-components"; +import searchIcon from "../../assets/icons/icon_search.png"; +import { ReactComponent as Delete } from "../../assets/icons/delete.svg"; +const Input = ({ setInputValue, inputValue, onEnterButtonHandle }) => { + const onClickDeleteButtonHandle = () => { + setInputValue(""); + }; + const onKeyPressHandle = (e) => { + if (e.key === "Enter") { + onEnterButtonHandle(); + } + }; + return (_jsxs("div", { id: "search-bar", children: [_jsx("img", { src: searchIcon, alt: "searchIcon" }), _jsx("input", { type: "text", placeholder: "\uB9C1\uD06C\uB97C \uAC80\uC0C9\uD574 \uBCF4\uC138\uC694.", value: inputValue, onChange: (e) => setInputValue(e.target.value), onKeyDown: onKeyPressHandle }), _jsx(DeleteAllButton, { onClick: () => onClickDeleteButtonHandle(), children: _jsx(Delete, {}) })] })); +}; +const DeleteAllButton = styled.button ` + width: 25px; + height: 25px; + border-radius: 999px; + border: none; + background-color: #ccd5e3; + display: flex; + justify-content: center; + align-items: center; + color: white; + + @media (max-width: 767px) { + width: 29px; + height: 25px; + } +`; +export default Input; diff --git a/dist/components/common/RedButton.js b/dist/components/common/RedButton.js new file mode 100644 index 0000000000..5b99f31364 --- /dev/null +++ b/dist/components/common/RedButton.js @@ -0,0 +1,22 @@ +import { jsx as _jsx } from "react/jsx-runtime"; +import { styled } from "styled-components"; +import { COLORS } from "../../constants/colors"; +export const RedButton = ({ text, width, height, margin, padding, fontSize, radius, }) => { + return (_jsx(Button, { width: width, height: height, margin: margin, padding: padding, color: COLORS.White, fontSize: fontSize, radius: radius, children: text })); +}; +const Button = styled.button ` + display: block; + width: ${({ width }) => width || "auto"}; + height: ${({ height }) => height || "auto"}; + border: 0px; + border-radius: ${({ radius }) => radius || "0px"}; + margin: ${({ margin }) => margin || "auto"}; + padding: ${({ padding }) => padding || "auto"}; + background: ${COLORS.Red}; + cursor: pointer; + + color: ${({ color }) => color}; + font-size: ${({ fontSize }) => fontSize || "14px"}; + font-weight: 600; + line-height: 21.6px; +`; diff --git a/dist/components/common/modals/AddFolderModal.js b/dist/components/common/modals/AddFolderModal.js new file mode 100644 index 0000000000..74173de2f1 --- /dev/null +++ b/dist/components/common/modals/AddFolderModal.js @@ -0,0 +1,73 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import styled from "styled-components"; +import { COLORS } from "../../../constants/colors"; +import { BlueButton } from "../BlueButton"; +import closeIcon from "../../../assets/icons/closeModal.png"; +export const AddFolderModal = ({ $isModalVisible, setIsModalVisible }) => { + const handleCloseBtn = () => { + setIsModalVisible(null); + }; + return (_jsx(Background, { "$isVisible": $isModalVisible, children: _jsxs(Modal, { children: [_jsx(Close, { onClick: () => handleCloseBtn(), children: _jsx("img", { src: closeIcon, alt: closeIcon }) }), _jsx(Title, { children: "\uD3F4\uB354 \uCD94\uAC00" }), _jsx(Input, { placeholder: "\uB0B4\uC6A9 \uC785\uB825" }), _jsx(BlueButton, { text: "\uCD94\uAC00\uD558\uAE30", width: "280px", height: "auto", margin: "0px", padding: "16px 20px", fontSize: "16px", radius: "8px", onBtnHandle: () => { } })] }) })); +}; +const Background = styled.div ` + display: ${({ $isVisible }) => $isVisible === "폴더 추가" ? "block" : "none"}; + z-index: 2; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.8); + transition: visibility 0.3s ease; +`; +const Modal = styled.div ` + position: absolute; + top: 20%; + left: 50%; + transform: translate(-50%, 50%); + padding: 32px 40px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 24px; + border-radius: 15px; + background-color: ${COLORS.White}; + transition: visibility 0.3s ease; +`; +const Close = styled.button ` + border: none; + position: absolute; + top: 16px; + right: 16px; + + &:hover { + cursor: pointer; + } +`; +const Title = styled.div ` + color: #373740; + font-family: Pretendard; + font-size: 20px; + font-style: normal; + font-weight: 700; + line-height: normal; +`; +const Input = styled.input ` + width: 280px; + padding: 18px 15px; + border-radius: 8px; + border: 1px solid ${COLORS.Grey_300}; + background: ${COLORS.White}; + color: var(--Linkbrary-gray100, #373740); + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 24px; + margin-bottom: 10px; + + &:focus { + border: 1px solid ${COLORS.Primary}; + } +`; diff --git a/dist/components/common/modals/AddToFolder.js b/dist/components/common/modals/AddToFolder.js new file mode 100644 index 0000000000..14f8dc1067 --- /dev/null +++ b/dist/components/common/modals/AddToFolder.js @@ -0,0 +1,110 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import { styled } from "styled-components"; +import { COLORS } from "../../../constants/colors"; +import closeIcon from "../../../assets/icons/closeModal.png"; +import { BlueButton } from "../BlueButton"; +export const AddToFolder = ({ $isModalVisible, setIsModalVisible }) => { + const handleCloseBtn = () => { + setIsModalVisible(null); + }; + return (_jsx(Background, { "$isVisible": $isModalVisible, onClick: (e) => { + e.preventDefault(); + }, children: _jsxs(Modal, { children: [_jsx(Close, { onClick: (e) => { + handleCloseBtn(); + }, children: _jsx("img", { src: closeIcon, alt: closeIcon }) }), _jsxs(Title, { children: [_jsx("h3", { children: "\uD3F4\uB354\uC5D0 \uCD94\uAC00" }), _jsx("p", { children: "\uB9C1\uD06C \uC8FC\uC18C" })] }), _jsxs(Folders, { children: [_jsxs(Folder, { children: ["\uCF54\uB529\uD301 ", _jsx("p", { children: "7\uAC1C \uB9C1\uD06C" })] }), _jsxs(Folder, { children: ["\uCC44\uC6A9 \uC0AC\uC774\uD2B8 ", _jsx("p", { children: "12\uAC1C \uB9C1\uD06C" })] }), _jsxs(Folder, { children: ["\uC720\uC6A9\uD55C \uAE00 ", _jsx("p", { children: "30\uAC1C \uB9C1\uD06C" })] }), _jsxs(Folder, { children: ["\uB098\uB9CC\uC758 \uC7A5\uC18C ", _jsx("p", { children: "3\uAC1C \uB9C1\uD06C" })] })] }), _jsx(BlueButton, { text: "\uCD94\uAC00\uD558\uAE30", width: "280px", height: "auto", margin: "0px", onBtnHandle: () => { }, padding: "16px 20px", fontSize: "16px", radius: "8px" })] }) })); +}; +const Background = styled.div ` + display: ${({ $isVisible }) => $isVisible === "폴더에 추가" ? "block" : "none"}; + z-index: 2; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.8); + transition: visibility 0.3s ease; + + &:hover { + cursor: default; + } +`; +const Modal = styled.div ` + position: absolute; + top: 10%; + left: 50%; + transform: translate(-50%, 50%); + padding: 32px 40px; + background-color: #fff; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 24px; + border-radius: 15px; + background: ${COLORS.White}; + transition: visibility 0.3s ease; +`; +const Close = styled.button ` + border: none; + position: absolute; + top: 16px; + right: 16px; + + &:hover { + cursor: pointer; + } +`; +const Title = styled.div ` + text-align: center; + font-family: Pretendard; + font-style: normal; + + & > h3 { + color: #373740; + font-size: 20px; + font-weight: 700; + line-height: normal; + margin-bottom: 8px; + } + + & > p { + color: ${COLORS.Grey_400}; + font-size: 14px; + font-weight: 400; + line-height: 22px; /* 157.143% */ + } +`; +const Folders = styled.div ` + width: 264px; +`; +const Folder = styled.div ` + width: 100%; + padding: 8px; + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + + color: #373740; + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 24px; + margin-right: 8px; + + &:hover { + cursor: pointer; + background: ${COLORS.Grey_100}; + color: ${COLORS.Primary}; + } + + & > p { + color: ${COLORS.Grey_400}; + font-family: Pretendard; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; + } +`; diff --git a/dist/components/common/modals/DeleteModal.js b/dist/components/common/modals/DeleteModal.js new file mode 100644 index 0000000000..55542f0f50 --- /dev/null +++ b/dist/components/common/modals/DeleteModal.js @@ -0,0 +1,72 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import styled from "styled-components"; +import { COLORS } from "../../../constants/colors"; +import closeIcon from "../../../assets/icons/closeModal.png"; +import { RedButton } from "../../../components/common/RedButton"; +export const DeleteModal = ({ $isModalVisible, setIsModalVisible }) => { + const handleCloseBtn = () => { + setIsModalVisible(null); + }; + return (_jsx(Background, { "$isVisible": $isModalVisible, children: _jsxs(Modal, { children: [_jsx(Close, { onClick: (e) => { + e.preventDefault(); + handleCloseBtn(); + }, children: _jsx("img", { src: closeIcon, alt: closeIcon }) }), _jsxs(Title, { children: [_jsx("h3", { children: "\uD3F4\uB354 \uC0AD\uC81C" }), _jsx("p", { children: "\uD3F4\uB354\uBA85" })] }), _jsx(RedButton, { text: "\uC0AD\uC81C\uD558\uAE30", width: "280px", height: "auto", margin: "0px", padding: "16px 20px", fontSize: "16px", radius: "8px" })] }) })); +}; +const Background = styled.div ` + display: ${({ $isVisible }) => ($isVisible === "삭제" ? "block" : "none")}; + z-index: 2; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.8); + transition: visibility 0.3s ease; +`; +const Modal = styled.div ` + position: absolute; + top: 30%; + left: 50%; + transform: translate(-50%, 50%); + padding: 32px 40px; + background-color: #fff; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 24px; + border-radius: 15px; + background: ${COLORS.White}; + transition: visibility 0.3s ease; +`; +const Close = styled.button ` + border: none; + position: absolute; + top: 16px; + right: 16px; + + &:hover { + cursor: pointer; + } +`; +const Title = styled.div ` + & > h3 { + color: #373740; + font-family: Pretendard; + font-size: 20px; + font-style: normal; + font-weight: 700; + line-height: normal; + margin-bottom: 8px; + } + + & > p { + color: ${COLORS.Grey_400}; + text-align: center; + font-family: Pretendard; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 22px; /* 157.143% */ + } +`; diff --git a/dist/components/common/modals/EditNameModal.js b/dist/components/common/modals/EditNameModal.js new file mode 100644 index 0000000000..834ee1b6a3 --- /dev/null +++ b/dist/components/common/modals/EditNameModal.js @@ -0,0 +1,74 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import { styled } from "styled-components"; +import { COLORS } from "../../../constants/colors"; +import closeIcon from "../../../assets/icons/closeModal.png"; +import { BlueButton } from "../BlueButton"; +export const EditNameModal = ({ $isModalVisible, setIsModalVisible }) => { + const handleCloseBtn = () => { + setIsModalVisible(null); + }; + return (_jsx(Background, { "$isVisible": $isModalVisible, children: _jsxs(Modal, { children: [_jsx(Close, { onClick: () => handleCloseBtn(), children: _jsx("img", { src: closeIcon, alt: closeIcon }) }), _jsx(Title, { children: "\uD3F4\uB354 \uC774\uB984 \uBCC0\uACBD" }), _jsx(Input, {}), _jsx(BlueButton, { text: "\uBCC0\uACBD\uD558\uAE30", width: "280px", height: "auto", margin: "0px", padding: "16px 20px", fontSize: "16px", radius: "8px", onBtnHandle: () => { } })] }) })); +}; +const Background = styled.div ` + display: ${({ $isVisible }) => $isVisible === "이름 변경" ? "block" : "none"}; + z-index: 2; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.8); + transition: visibility 0.3s ease; +`; +const Modal = styled.div ` + position: absolute; + top: 30%; + left: 50%; + transform: translate(-50%, 50%); + padding: 32px 40px; + background-color: #fff; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 24px; + border-radius: 15px; + background: ${COLORS.White}; + transition: visibility 0.3s ease; +`; +const Close = styled.button ` + border: none; + position: absolute; + top: 16px; + right: 16px; + + &:hover { + cursor: pointer; + } +`; +const Title = styled.div ` + color: #373740; + font-family: Pretendard; + font-size: 20px; + font-style: normal; + font-weight: 700; + line-height: normal; +`; +const Input = styled.input ` + width: 280px; + padding: 18px 15px; + border-radius: 8px; + border: 1px solid ${COLORS.Primary}; + background: ${COLORS.White}; + color: var(--Linkbrary-gray100, #373740); + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 24px; + margin-bottom: 10px; + + &:focus { + border: 1px solid ${COLORS.Primary}; + } +`; diff --git a/dist/components/common/modals/PopOver.js b/dist/components/common/modals/PopOver.js new file mode 100644 index 0000000000..7adc664ef8 --- /dev/null +++ b/dist/components/common/modals/PopOver.js @@ -0,0 +1,38 @@ +import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; +import { styled } from "styled-components"; +import { COLORS } from "../../../constants/colors"; +import { DeleteModal } from "../../../components/common/modals/DeleteModal"; +import { AddToFolder } from "../../../components/common/modals/AddToFolder"; +export const PopOver = ({ $isPopOverVisible, setIsPopOverVisible, $options, $modalType, $top, $right, $isModalVisible, setIsModalVisible, }) => { + return (_jsxs(_Fragment, { children: [_jsx(DeleteModal, { "$isModalVisible": $isModalVisible, setIsModalVisible: setIsModalVisible }), _jsx(AddToFolder, { "$isModalVisible": $isModalVisible, setIsModalVisible: setIsModalVisible }), _jsx(MenuOptions, { "$isVisible": $isPopOverVisible, "$top": $top, "$right": $right, children: $options.map((option, index) => (_jsx(Option, { onClick: (e) => { + e.preventDefault(); + setIsModalVisible($modalType[index]); + }, children: option }, option))) })] })); +}; +const MenuOptions = styled.div ` + width: 100px; + position: absolute; + right: ${({ $right }) => $right ?? 0}; + top: ${({ $top }) => $top ?? 0}; + border: 1px; + display: ${({ $isVisible }) => ($isVisible ? "block" : "none")}; + background-color: #fff; + z-index: 1; +`; +const Option = styled.p ` + padding: 7px 12px; + background-color: #fff; + color: #333236; + font-family: Pretendard; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; + text-align: center; + + &:hover { + cursor: pointer; + color: ${COLORS.Primary}; + background-color: ${COLORS.Grey_100}; + } +`; diff --git a/dist/components/common/modals/SharedModal.js b/dist/components/common/modals/SharedModal.js new file mode 100644 index 0000000000..98c78e0f88 --- /dev/null +++ b/dist/components/common/modals/SharedModal.js @@ -0,0 +1,124 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import styled from "styled-components"; +import { COLORS } from "../../../constants/colors"; +import closeIcon from "../../../assets/icons/closeModal.png"; +import kakao from "../../../assets/icons/icon_kakao.png"; +import facebook from "../../../assets/icons/icon_facebook.png"; +import link from "../../../assets/icons/link.png"; +export const SharedModal = ({ $isModalVisible, setIsModalVisible }) => { + const ICONS = [ + { + name: "카카오톡", + backgroundColor: "#F5E14B", + imgUrl: kakao, + }, + { + name: "페이스북", + backgroundColor: "#1877F2", + imgUrl: facebook, + }, + { + name: "링크 복사", + imgUrl: link, + }, + ]; + const handleCloseBtn = () => { + setIsModalVisible(null); + }; + return (_jsx(Background, { "$isVisible": $isModalVisible, children: _jsxs(Modal, { children: [_jsx(Close, { onClick: () => handleCloseBtn(), children: _jsx("img", { src: closeIcon, alt: closeIcon }) }), _jsxs(Title, { children: [_jsx("h3", { children: "\uD3F4\uB354 \uACF5\uC720" }), _jsx("p", { children: "\uD3F4\uB354\uBA85" })] }), _jsx(Icons, { children: ICONS.map((icon) => (_jsxs(Icon, { children: [_jsx(IconImg, { "$backgroundColor": icon.backgroundColor, children: _jsx("img", { src: icon.imgUrl, alt: icon.name }) }), _jsx("span", { children: icon.name })] }, icon.name))) })] }) })); +}; +const Background = styled.div ` + display: ${({ $isVisible }) => ($isVisible === "공유" ? "block" : "none")}; + z-index: 2; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.8); + transition: visibility 0.3s ease; +`; +const Modal = styled.div ` + position: absolute; + top: 30%; + left: 50%; + transform: translate(-50%, 50%); + padding: 32px 40px; + background-color: #fff; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 24px; + border-radius: 15px; + background: ${COLORS.White}; + transition: visibility 0.3s ease; +`; +const Close = styled.button ` + border: none; + position: absolute; + top: 16px; + right: 16px; + + &:hover { + cursor: pointer; + } +`; +const Title = styled.div ` + & > h3 { + color: #373740; + font-family: Pretendard; + font-size: 20px; + font-style: normal; + font-weight: 700; + line-height: normal; + margin-bottom: 8px; + } + + & > p { + color: ${COLORS.Grey_400}; + text-align: center; + font-family: Pretendard; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 22px; /* 157.143% */ + } +`; +const Icons = styled.div ` + display: flex; + flex-direction: row; + gap: 32px; + padding: 0px 32px; +`; +const Icon = styled.div ` + width: max-content; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 10px; + + & > span { + color: #373740; + text-align: center; + font-size: 13px; + font-style: normal; + font-weight: 400; + line-height: 15px; + } +`; +const IconImg = styled.div ` + width: 42px; + height: 42px; + background-color: ${({ $backgroundColor }) => $backgroundColor ?? null}; + border-radius: 40px; + display: flex; + justify-content: center; + align-items: center; + + & > img { + width: 18px; + height: 18px; + } +`; diff --git a/dist/components/home/CardFrame.js b/dist/components/home/CardFrame.js new file mode 100644 index 0000000000..14a5b9a276 --- /dev/null +++ b/dist/components/home/CardFrame.js @@ -0,0 +1,124 @@ +import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; +import { styled } from "styled-components"; +import card1 from "../../assets/cards/card1.png"; +import card2 from "../../assets/cards/card2.png"; +import card3 from "../../assets/cards/card3.png"; +import card4 from "../../assets/cards/card4.png"; +import { COLORS } from "../../constants/colors"; +export const CardFrame = ({ num, reversed, height }) => { + const card = CARDS[`card${num}`]; + return (_jsx(Section, { children: _jsx(SectionFrame, { reversed: reversed, children: reversed ? (_jsxs(_Fragment, { children: [_jsxs(Description, { children: [card.headline, _jsx(SectionCardsMobile, { children: _jsx("img", { src: card.imgUrl, alt: `card${num}Img` }) }), card.description] }), _jsx(SectionCards, { children: _jsx("img", { src: card.imgUrl, alt: `card${num}Img` }) })] })) : (_jsxs(_Fragment, { children: [_jsx(SectionCards, { children: _jsx("img", { src: card.imgUrl, alt: `card${num}Img` }) }), _jsxs(Description, { children: [card.headline, _jsx(SectionCardsMobile, { children: _jsx("img", { src: card.imgUrl, alt: `card${num}Img` }) }), card.description] })] })) }) })); +}; +const Section = styled.section ` + width: 100%; + height: ${({ height }) => height ?? 550}px; + display: flex; + background-color: ${COLORS.White}; + + @media (max-width: 1124px) { + height: 445px; + } + @media (max-width: 774px) { + height: auto; + } +`; +const SectionFrame = styled.div ` + display: flex; + align-items: center; + justify-content: center; + gap: 157px; + + @media (max-width: 1124px) { + height: 315px; + gap: 50px; + } + @media (max-width: 774px) { + flex-direction: column; + height: auto; + } +`; +const SectionCards = styled.div ` + width: 550px; + height: 450px; + display: flex; + justify-content: center; + align-items: center; + + @media (max-width: 1124px) { + width: 384px; + height: 315px; + } + @media (max-width: 774px) { + width: 384px; + height: 315px; + display: none; + } + + & > img { + width: 100%; + } +`; +const SectionCardsMobile = styled(SectionCards) ` + display: none; + + @media (max-width: 774px) { + display: block; + } +`; +const Description = styled.div ` + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + + & > h3 { + font-family: Pretendard; + font-size: 48px; + font-style: normal; + font-weight: 700; + line-height: normal; + letter-spacing: -0.3px; + margin-bottom: 10px; + } + + @media (max-width: 774px) { + width: 100%; + padding: 40px 32px; + gap: 20px; + + & > h3 { + font-size: 24px; + margin-bottom: 0px; + } + & > h3 > br { + display: none; + } + } +`; +const StrongText = styled.span ` + background: linear-gradient(${({ color }) => color}); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +`; +const CARDS = { + card1: { + headline: (_jsxs("h3", { children: [_jsx(StrongText, { color: "96deg, #fe8a8a 3%, #a4ceff 74.97%", children: "\uC6D0\uD558\uB294 \uB9C1\uD06C" }), "\uB97C ", _jsx("br", {}), "\uC800\uC7A5\uD558\uC138\uC694"] })), + imgUrl: card1, + description: (_jsxs("p", { children: ["\uB098\uC911\uC5D0 \uC77D\uACE0 \uC2F6\uC740 \uAE00, \uB2E4\uC2DC \uBCF4\uACE0 \uC2F6\uC740 \uC601\uC0C1,", _jsx("br", {}), "\uC0AC\uACE0 \uC2F6\uC740 \uC637, \uAE30\uC5B5\uD558\uACE0 \uC2F6\uC740 \uBAA8\uB4E0 \uAC83\uC744", _jsx("br", {}), "\uD55C \uACF5\uAC04\uC5D0 \uC800\uC7A5\uD558\uC138\uC694."] })), + }, + card2: { + headline: (_jsxs("h3", { children: ["\uB9C1\uD06C\uB97C \uD3F4\uB354\uB85C ", _jsx("br", {}), _jsx(StrongText, { color: "277deg, #6fbaff, #ffd88b", children: "\uAD00\uB9AC" }), "\uD558\uC138\uC694"] })), + imgUrl: card2, + description: (_jsxs("p", { children: ["\uB098\uB9CC\uC758 \uD3F4\uB354\uB97C \uBB34\uC81C\uD55C\uC73C\uB85C \uB9CC\uB4E4\uACE0", _jsx("br", {}), "\uB2E4\uC591\uD558\uAC8C \uD65C\uC6A9\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."] })), + }, + card3: { + headline: (_jsxs("h3", { children: ["\uC800\uC7A5\uD55C \uB9C1\uD06C\uB97C ", _jsx("br", {}), _jsx(StrongText, { color: "99deg, #6d7ccd 27%, rgba(82, 136, 133, 0.22) 52%", children: "\uACF5\uC720" }), "\uD574 \uBCF4\uC138\uC694."] })), + imgUrl: card3, + description: (_jsxs("p", { children: ["\uC5EC\uB7EC \uB9C1\uD06C\uB97C \uD3F4\uB354\uC5D0 \uB2F4\uACE0 \uACF5\uC720\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4.", _jsx("br", {}), "\uAC00\uC871,\uCE5C\uAD6C,\uB3D9\uB8CC\uB4E4\uC5D0\uAC8C \uC27D\uACE0 \uBE60\uB974\uAC8C \uB9C1\uD06C\uB97C", _jsx("br", {}), "\uACF5\uC720\uD574 \uBCF4\uC138\uC694."] })), + }, + card4: { + headline: (_jsxs("h3", { children: ["\uC800\uC7A5\uD55C \uB9C1\uD06C\uB97C ", _jsx("br", {}), _jsx(StrongText, { color: "271deg, #fe578f -79.84%, #68e8f9 107.18%", children: "\uAC80\uC0C9" }), "\uD574 \uBCF4\uC138\uC694."] })), + imgUrl: card4, + description: _jsx("p", { children: "\uC911\uC694\uD55C \uC815\uBCF4\uB4E4\uC744 \uAC80\uC0C9\uC73C\uB85C \uC27D\uAC8C \uCC3E\uC544\uBCF4\uC138\uC694." }), + }, +}; diff --git a/dist/components/home/Headline.js b/dist/components/home/Headline.js new file mode 100644 index 0000000000..e1356c77bd --- /dev/null +++ b/dist/components/home/Headline.js @@ -0,0 +1,37 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import styled from "styled-components"; +import { BlueButton } from "../common/BlueButton"; +export const Headline = () => { + return (_jsxs(Container, { children: [_jsx(TextBox, { children: _jsxs(HeadlineText, { children: [_jsx(Strong, { children: "\uC138\uC0C1\uC758 \uBAA8\uB4E0 \uC815\uBCF4" }), "\uB97C", _jsx("br", {}), "\uC27D\uAC8C \uC800\uC7A5\uD558\uACE0 ", _jsx(LineBreak, {}), "\uAD00\uB9AC\uD574 \uBCF4\uC138\uC694."] }) }), _jsx(BlueButton, { text: "\uB9C1\uD06C \uCD94\uAC00\uD558\uAE30", width: "350px", height: "auto", margin: "40px auto", padding: "10px 20px", fontSize: "18px", radius: "8px", onBtnHandle: () => { } })] })); +}; +const Container = styled.div ` + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +`; +const TextBox = styled.p ` + width: 100%; + text-align: center; +`; +const HeadlineText = styled.span ` + font-size: 64px; + font-weight: 700; + + @media (max-width: 774px) { + font-size: 32px; + } +`; +const Strong = styled(HeadlineText) ` + background: linear-gradient(90deg, #6d6afe, #ff9f9f); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +`; +const LineBreak = styled.br ` + display: none; + + @media (max-width: 1124px) { + display: block; + } +`; diff --git a/dist/components/shared/SharedSection.js b/dist/components/shared/SharedSection.js new file mode 100644 index 0000000000..13a27109e6 --- /dev/null +++ b/dist/components/shared/SharedSection.js @@ -0,0 +1,30 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import { useState, useEffect } from "react"; +import { getFolderInfo } from "../../api/api"; +import smileIcon from "../../assets/icons/icon_smile.png"; +import styled from "styled-components"; +const SharedSection = () => { + const [folderName, setFolderName] = useState([]); + const [owner, setOwner] = useState([]); + const { profileImageSource, name } = owner; + async function handleLoad() { + const folderInfo = await getFolderInfo(); + const { name, owner } = folderInfo.folder; + setFolderName(name); + setOwner(owner); + } + useEffect(() => { + handleLoad(); + }, []); + return (_jsxs("section", { className: "codeit-mark-section", children: [_jsx(OwnerProfile, { src: profileImageSource || smileIcon, alt: "smile icon" }), _jsx("span", { children: name }), _jsx("div", { id: "favorites", children: _jsx("h1", { children: folderName }) })] })); +}; +const OwnerProfile = styled.img ` + display: flex; + justify-content: center; + width: 60px; + height: 60px; + align-items: center; + border-radius: 47px; + margin-bottom: 12px; +`; +export default SharedSection; diff --git a/dist/constants/colors.js b/dist/constants/colors.js new file mode 100644 index 0000000000..05b1b5f2e1 --- /dev/null +++ b/dist/constants/colors.js @@ -0,0 +1,11 @@ +export const COLORS = { + Primary: "#6d6afe", + Red: "#ff5b56", + Black: "#111322", + White: "#fff", + Grey_100: "#f0f6ff", + Grey_200: "#e7effb", + Grey_300: "#ccd5e3", + Grey_400: "#9fa6b2", + Grey_500: "#3e3e43", +}; diff --git a/dist/constants/errorMsg.js b/dist/constants/errorMsg.js new file mode 100644 index 0000000000..83f2017581 --- /dev/null +++ b/dist/constants/errorMsg.js @@ -0,0 +1,15 @@ +const ERROR_MESSAGE = { + email: { + empty: "이메일을 입력해주세요.", + invalid: "올바른 이메일이 아닙니다.", + check: "이메일을 확인해 주세요.", + inUse: "이미 존재하는 이메일입니다.", + }, + password: { + empty: "비밀번호을 입력해 주세요", + invalid: "비밀번호는 영문,숫자 조합 8자 이상 입력해주세요.", + check: "비밀번호을 확인해 주세요", + recheck: "비밀번호가 일치하지 않아요.", + }, +}; +export { ERROR_MESSAGE }; diff --git a/dist/hooks/uesGetPromise.js b/dist/hooks/uesGetPromise.js new file mode 100644 index 0000000000..18b34b8e8e --- /dev/null +++ b/dist/hooks/uesGetPromise.js @@ -0,0 +1,21 @@ +import { useState, useEffect, useCallback } from "react"; +export const useGetPromise = (func) => { + const [values, setValues] = useState([]); + const HandleLoad = useCallback(async () => { + let results; + try { + results = await func(); + // await console.log(results); + } + catch (error) { + console.error(error); + } + if (!results) + return; + setValues(results); + }, []); + useEffect(() => { + HandleLoad(); + }, [HandleLoad]); + return values; +}; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 0000000000..18bff61ad2 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,5 @@ +import { jsx as _jsx } from "react/jsx-runtime"; +import ReactDOM from "react-dom/client"; +import Main from "./Main"; +const root = ReactDOM.createRoot(document.getElementById("root")); +root.render(_jsx(Main, {})); diff --git a/dist/pages/Folder.js b/dist/pages/Folder.js new file mode 100644 index 0000000000..c9737026b4 --- /dev/null +++ b/dist/pages/Folder.js @@ -0,0 +1,128 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import { useState, useEffect, useRef } from "react"; +import styled from "styled-components"; +import { getAllLinkData } from "../api/api"; +import HeaderElement from "../components/common/HeaderElement"; +import FooterElement from "../components/common/FooterElement"; +import GlobalStyle from "../components/common/GlobalStyle"; +import FolderInput from "../components/Folder/FolderInput"; +import FolderList from "../components/common/FolderList"; +import Input from "../components/common/Input"; +import Menus from "../components/Folder/Menus"; +import FolderTitle from "../components/Folder/FolderTitle"; +import { SharedModal } from "../components/common/modals/SharedModal"; +import { EditNameModal } from "../components/common/modals/EditNameModal"; +import { DeleteModal } from "../components/common/modals/DeleteModal"; +import { AddFolderModal } from "../components/common/modals/AddFolderModal"; +import { COLORS } from "../constants/colors"; +const Folder = () => { + const [titleName, setTitleName] = useState("전체"); + const [listId, setListId] = useState(""); + const [data, setData] = useState([]); + const [isModalVisible, setIsModalVisible] = useState(null); + const [searchInputValue, setSearchInputValue] = useState(""); + useEffect(() => { + const fetchData = async () => { + try { + const response = await getAllLinkData(listId); + const result = await response.data; + setData(result); + } + catch (error) { + console.error(error); + } + }; + fetchData(); + }, [listId]); + const onSearchEnterClickHandle = () => { + findCardsByKeyword(searchInputValue); + }; + const addLinkDivRef = useRef(null); + const footerDivRef = useRef(null); + const [isAddLinkVisible, setIsAddLinkVisible] = useState(false); + const onIntersectionHandle = async (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setIsAddLinkVisible(true); + } + else { + setIsAddLinkVisible(false); + } + }); + }; + useEffect(() => { + if (addLinkDivRef.current) { + const observer1 = new IntersectionObserver(onIntersectionHandle, { + threshold: 0.1, + }); + if (addLinkDivRef.current) { + observer1.observe(addLinkDivRef.current); + } + const observer2 = new IntersectionObserver(onIntersectionHandle, { + threshold: 0.1, + }); + if (footerDivRef.current) { + observer2.observe(footerDivRef.current); + } + return () => { + observer1.disconnect(); + observer2.disconnect(); + }; + } + }, []); + const findCardsByKeyword = (keyword) => { + const results = []; + if (data) { + data.forEach((card) => { + const cardInfo = card.title + card.description + card.url; + if (cardInfo && cardInfo.includes(keyword)) { + results.push(card); + } + else { + console.log("없음"); + } + }); + } + setData(results); + }; + return (_jsxs(Container, { children: [_jsx(SharedModal, { "$isModalVisible": isModalVisible, setIsModalVisible: setIsModalVisible }), _jsx(EditNameModal, { "$isModalVisible": isModalVisible, setIsModalVisible: setIsModalVisible }), _jsx(DeleteModal, { "$isModalVisible": isModalVisible, setIsModalVisible: setIsModalVisible }), _jsx(AddFolderModal, { "$isModalVisible": isModalVisible, setIsModalVisible: setIsModalVisible }), _jsx(GlobalStyle, {}), _jsx(HeaderElement, { "$positionval": "static" }), _jsx(FolderInput, { setIsVisible: setIsModalVisible, "$isAddLinkVisible": isAddLinkVisible, ref: addLinkDivRef }), _jsx(Input, { inputValue: searchInputValue, setInputValue: setSearchInputValue, onEnterButtonHandle: onSearchEnterClickHandle }), _jsx(Menus, { changeTitle: setTitleName, changeID: setListId, setIsVisible: setIsModalVisible }), _jsx(FolderTitle, { titleName: titleName, setIsModal: setIsModalVisible }), data[0] ? (_jsx(FolderList, { items: data, "$isModalVisible": isModalVisible, setIsModalVisible: setIsModalVisible })) : (_jsx(NoLinkMsg, { children: "\uC800\uC7A5\uB41C \uB9C1\uD06C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." })), _jsx(AddFolderBtn, { "$isAddLinkVisible": isAddLinkVisible, children: "\uD3F4\uB354 \uCD94\uAC00 +" }), _jsx("div", { ref: footerDivRef, children: _jsx(FooterElement, {}) })] })); +}; +const Container = styled.div ` + margin: 0px; +`; +const NoLinkMsg = styled.p ` + color: #000; + text-align: center; + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 24px; /* 150% */ + margin-top: 40px; +`; +const AddFolderBtn = styled.button ` + border: none; + border-radius: 20px; + border: 1px solid ${COLORS.White}; + background: ${COLORS.Primary}; + position: sticky; + left: 50%; + transform: translateX(-50%); + bottom: ${({ $isAddLinkVisible }) => ($isAddLinkVisible ? "50px" : "150px")}; + padding: 8px 24px; + display: none; + + color: ${COLORS.Grey_200}; + text-align: center; + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: normal; + letter-spacing: -0.3px; + + @media (max-width: 375px) { + display: block; + } +`; +export default Folder; diff --git a/dist/pages/Home.js b/dist/pages/Home.js new file mode 100644 index 0000000000..b89548b649 --- /dev/null +++ b/dist/pages/Home.js @@ -0,0 +1,38 @@ +import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; +import styled from "styled-components"; +import HeaderElement from "../components/common/HeaderElement"; +import FooterElement from "../components/common/FooterElement"; +import { Headline } from "../components/home/Headline"; +import { CardFrame } from "../components/home/CardFrame"; +import cards_img from "../assets/cards/cards_img.png"; +import { COLORS } from "../constants/colors"; +export const Home = () => { + return (_jsxs(_Fragment, { children: [_jsx(HeaderElement, { "$positionval": "" }), _jsxs(HeadlineContainer, { children: [_jsx(Headline, {}), _jsx(CardsContainer, { children: _jsx("img", { src: cards_img, alt: cards_img }) })] }), _jsx(CardFrame, { num: 1, height: 620, reversed: true }), _jsx(CardFrame, { num: 2, height: null, reversed: false }), _jsx(CardFrame, { num: 3, height: null, reversed: true }), _jsx(CardFrame, { num: 4, height: null, reversed: false }), _jsx(FooterElement, {})] })); +}; +const HeadlineContainer = styled.div ` + padding-top: 70px; + display: flex; + flex-direction: column; + align-items: center; + background-color: ${COLORS.Grey_100}; +`; +const CardsContainer = styled.div ` + width: 1118px; + height: 590px; + overflow-y: hidden; + + & > img { + width: 100%; + margin-top: 50px; + border-radius: 25px; + } + + @media (max-width: 1124px) { + width: 698px; + height: 343px; + } + @media (max-width: 774px) { + width: 325px; + height: 160px; + } +`; diff --git a/dist/pages/Shared.js b/dist/pages/Shared.js new file mode 100644 index 0000000000..7a72b9f89c --- /dev/null +++ b/dist/pages/Shared.js @@ -0,0 +1,20 @@ +import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime"; +import { useState } from "react"; +import { getFolderInfo } from "../api/api"; +import HeaderElement from "../components/common/HeaderElement"; +import FooterElement from "../components/common/FooterElement"; +import SharedSection from "../components/shared/SharedSection"; +import Input from "../components/common/Input"; +import FolderList from "../components/common/FolderList"; +import { DeleteModal } from "../components/common/modals/DeleteModal"; +import { AddFolderModal } from "../components/common/modals/AddFolderModal"; +import { useGetPromise } from "../hooks/uesGetPromise"; +import "../styles/shared.css"; +function Shared() { + const foldersData = useGetPromise(getFolderInfo); + const folders = foldersData?.folder?.links || []; + const [searchInputValue, setSearchInputValue] = useState(""); + const [isModalVisible, setIsModalVisible] = useState(null); + return (_jsxs(_Fragment, { children: [_jsx(DeleteModal, { "$isModalVisible": isModalVisible, setIsModalVisible: setIsModalVisible }), _jsx(AddFolderModal, { "$isModalVisible": isModalVisible, setIsModalVisible: setIsModalVisible }), _jsx(HeaderElement, { "$positionval": "" }), _jsx(SharedSection, {}), _jsx(Input, { inputValue: searchInputValue, setInputValue: setSearchInputValue, onEnterButtonHandle: () => { } }), _jsx(FolderList, { items: folders, "$isModalVisible": isModalVisible, setIsModalVisible: setIsModalVisible }), _jsx(FooterElement, {})] })); +} +export default Shared; diff --git a/dist/reportWebVitals.js b/dist/reportWebVitals.js new file mode 100644 index 0000000000..d62e395879 --- /dev/null +++ b/dist/reportWebVitals.js @@ -0,0 +1,12 @@ +const reportWebVitals = (onPerfEntry) => { + if (onPerfEntry && onPerfEntry instanceof Function) { + import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { + getCLS(onPerfEntry); + getFID(onPerfEntry); + getFCP(onPerfEntry); + getLCP(onPerfEntry); + getTTFB(onPerfEntry); + }); + } +}; +export default reportWebVitals; diff --git a/src/setupTests.js b/dist/setupTests.js similarity index 100% rename from src/setupTests.js rename to dist/setupTests.js diff --git a/dist/utils/calculator.js b/dist/utils/calculator.js new file mode 100644 index 0000000000..61bafa3934 --- /dev/null +++ b/dist/utils/calculator.js @@ -0,0 +1,31 @@ +function timeToString(time) { + let diffSec = parseInt(time / 1000); + if (diffSec <= 120) { + return "1 minute ago"; + } + let diffMinute = parseInt(diffSec / 60); + if (diffMinute <= 59) { + return `${diffMinute} minutes ago`; + } + let diffHour = parseInt(diffMinute / 60); + if (diffHour <= 23) { + return `${diffHour} hours ago`; + } + let diffDay = parseInt(diffHour / 24); + if (diffDay <= 30) { + return `${diffDay} days ago`; + } + let diffMonth = parseInt(diffDay / 30); + if (diffMonth <= 11) { + return `${diffMonth} months ago`; + } + let diffYear = parseInt(diffMonth / 12); + return `${diffYear} years ago`; +} +export function CalcTime(time) { + let nowTime = Date.now(); + let writingTime = Date.parse(time); + let diffTime = nowTime - writingTime; + let sentence = timeToString(diffTime); + return sentence; +} diff --git a/package-lock.json b/package-lock.json index a1e590ee62..5f84a8f5b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,8 +13,16 @@ "@testing-library/user-event": "^13.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-scripts": "5.0.1", + "react-router-dom": "^6.22.3", + "react-scripts": "^5.0.1", + "styled-components": "^6.1.8", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "@types/node": "^20.11.30", + "@types/react": "^18.2.71", + "@types/react-dom": "^18.2.22", + "typescript": "^5.4.3" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2270,6 +2278,24 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -3241,6 +3267,14 @@ } } }, + "node_modules/@remix-run/router": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", + "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -4297,9 +4331,12 @@ "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "node_modules/@types/node": { - "version": "20.5.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.9.tgz", - "integrity": "sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==" + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -4332,9 +4369,9 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" }, "node_modules/@types/react": { - "version": "18.2.21", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz", - "integrity": "sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==", + "version": "18.2.71", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.71.tgz", + "integrity": "sha512-PxEsB9OjmQeYGffoWnYAd/r5FiJuUw2niFQHPc2v2idwh8wGPkkYzOHuinNJJY6NZqfoTCiOIizDOz38gYNsyw==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -4342,9 +4379,9 @@ } }, "node_modules/@types/react-dom": { - "version": "18.2.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", - "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", + "version": "18.2.22", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.22.tgz", + "integrity": "sha512-fHkBXPeNtfvri6gdsMYyW+dW7RXFo6Ad09nLFK0VQWR7yGLai/Cyvyj696gbwYvBnhGtevUG9cET0pmUbMtoPQ==", "dependencies": { "@types/react": "*" } @@ -4412,6 +4449,11 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" }, + "node_modules/@types/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw==" + }, "node_modules/@types/testing-library__jest-dom": { "version": "5.14.9", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", @@ -5826,6 +5868,14 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -6261,6 +6311,14 @@ "postcss": "^8.4" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", @@ -6442,6 +6500,16 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -13085,9 +13153,9 @@ } }, "node_modules/postcss": { - "version": "8.4.29", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz", - "integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { "type": "opencollective", @@ -14671,6 +14739,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", + "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", + "dependencies": { + "@remix-run/router": "1.15.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", + "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", + "dependencies": { + "@remix-run/router": "1.15.3", + "react-router": "6.22.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -15485,6 +15583,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -15980,6 +16083,38 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.8.tgz", + "integrity": "sha512-PQ6Dn+QxlWyEGCKDS71NGsXoVLKfE1c3vApkvDYS5KAK+V8fNWGhbSUEo9Gg2iaID2tjLXegEW3bZDUGpofRWw==", + "dependencies": { + "@emotion/is-prop-valid": "1.2.1", + "@emotion/unitless": "0.8.0", + "@types/stylis": "4.2.0", + "css-to-react-native": "3.2.0", + "csstype": "3.1.2", + "postcss": "8.4.31", + "shallowequal": "1.1.0", + "stylis": "4.3.1", + "tslib": "2.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/tslib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", + "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -15995,6 +16130,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.1.tgz", + "integrity": "sha512-EQepAV+wMsIaGVGX1RECzgrcqRRU/0sYOHkeLsZ3fzHaHXZy4DaOOX0vOlGQdlsjkh3mFHAIlVimpwAs4dslyQ==" + }, "node_modules/sucrase": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", @@ -16624,16 +16764,15 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "peer": true, + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/unbox-primitive": { @@ -16655,6 +16794,11 @@ "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", diff --git a/package.json b/package.json index 7ff0d6b58c..9edb0ee0a6 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,9 @@ "@testing-library/user-event": "^13.5.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-scripts": "5.0.1", + "react-router-dom": "^6.22.3", + "react-scripts": "^5.0.1", + "styled-components": "^6.1.8", "web-vitals": "^2.1.4" }, "scripts": { @@ -34,5 +36,11 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "@types/node": "^20.11.30", + "@types/react": "^18.2.71", + "@types/react-dom": "^18.2.22", + "typescript": "^5.4.3" } } diff --git a/public/index.html b/public/index.html index aa069f27cb..97ffe674e6 100644 --- a/public/index.html +++ b/public/index.html @@ -1,43 +1,26 @@ - + - - + Linkbrary + + + + + - - - - - React App -
- diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 74b5e05345..0000000000 --- a/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/src/App.js b/src/App.js index 3784575723..449616265a 100644 --- a/src/App.js +++ b/src/App.js @@ -1,25 +1,5 @@ -import logo from './logo.svg'; -import './App.css'; - -function App() { - return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
- ); -} +const App = () => { + return <>; +}; export default App; diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index 1f03afeece..0000000000 --- a/src/App.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/src/Main.js b/src/Main.js new file mode 100644 index 0000000000..b7ad7084e4 --- /dev/null +++ b/src/Main.js @@ -0,0 +1,17 @@ +import { BrowserRouter, Routes, Route } from "react-router-dom"; +import Shared from "./pages/Shared"; +import Folder from "./pages/Folder"; +import { Home } from "./pages/Home"; + +function Main() { + return ( + + + } /> + } /> + } /> + + + ); +} +export default Main; diff --git a/src/api/api.ts b/src/api/api.ts new file mode 100644 index 0000000000..584ac567c6 --- /dev/null +++ b/src/api/api.ts @@ -0,0 +1,44 @@ +const BASE_URL = "https://bootcamp-api.codeit.kr/api/"; + +export const getFolderInfo = async () => { + try { + const response = await fetch(`${BASE_URL}sample/folder`); + const result = await response.json(); + return result; + } catch (error) { + console.log(error); + } +}; + +export const getUserInfo = async () => { + try { + const response = await fetch(`${BASE_URL}sample/user`); + const result = await response.json(); + return result; + } catch (error) { + console.log(error); + } +}; + +export const getFolderList = async () => { + try { + const response = await fetch(`${BASE_URL}users/1/folders`); + const result = await response.json(); + // console.log(result); + return result; + } catch (error) { + console.log(error); + } +}; +export const getAllLinkData = async (id) => { + const url = id + ? `${BASE_URL}users/1/folders/${id}` + : `${BASE_URL}users/1/links`; + try { + const response = await fetch(url); + const result = await response.json(); + return result; + } catch (error) { + console.log(error); + } +}; diff --git a/src/assets/Linkbrary.png b/src/assets/Linkbrary.png new file mode 100644 index 0000000000..e26932d8df Binary files /dev/null and b/src/assets/Linkbrary.png differ diff --git a/src/assets/cards/card1-1.png b/src/assets/cards/card1-1.png new file mode 100644 index 0000000000..cadee1b331 Binary files /dev/null and b/src/assets/cards/card1-1.png differ diff --git a/src/assets/cards/card1-2.png b/src/assets/cards/card1-2.png new file mode 100644 index 0000000000..392e43137c Binary files /dev/null and b/src/assets/cards/card1-2.png differ diff --git a/src/assets/cards/card1-3.png b/src/assets/cards/card1-3.png new file mode 100644 index 0000000000..5d2cc83bd7 Binary files /dev/null and b/src/assets/cards/card1-3.png differ diff --git a/src/assets/cards/card1.png b/src/assets/cards/card1.png new file mode 100644 index 0000000000..6c6c327e5f Binary files /dev/null and b/src/assets/cards/card1.png differ diff --git a/src/assets/cards/card2.png b/src/assets/cards/card2.png new file mode 100644 index 0000000000..7aeacac605 Binary files /dev/null and b/src/assets/cards/card2.png differ diff --git a/src/assets/cards/card3.png b/src/assets/cards/card3.png new file mode 100644 index 0000000000..a54ecea283 Binary files /dev/null and b/src/assets/cards/card3.png differ diff --git a/src/assets/cards/card4.png b/src/assets/cards/card4.png new file mode 100644 index 0000000000..4a71bc4720 Binary files /dev/null and b/src/assets/cards/card4.png differ diff --git a/src/assets/cards/cards_img.png b/src/assets/cards/cards_img.png new file mode 100644 index 0000000000..a88950b7d1 Binary files /dev/null and b/src/assets/cards/cards_img.png differ diff --git a/src/assets/icons/Union.svg b/src/assets/icons/Union.svg new file mode 100644 index 0000000000..d9bcabf601 --- /dev/null +++ b/src/assets/icons/Union.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/card_star.svg b/src/assets/icons/card_star.svg new file mode 100644 index 0000000000..7801cefbb2 --- /dev/null +++ b/src/assets/icons/card_star.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/icons/closeModal.png b/src/assets/icons/closeModal.png new file mode 100644 index 0000000000..e4039bf814 Binary files /dev/null and b/src/assets/icons/closeModal.png differ diff --git a/src/assets/icons/delete.svg b/src/assets/icons/delete.svg new file mode 100644 index 0000000000..a0c413f810 --- /dev/null +++ b/src/assets/icons/delete.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/eye-off.png b/src/assets/icons/eye-off.png new file mode 100644 index 0000000000..1e6b215406 Binary files /dev/null and b/src/assets/icons/eye-off.png differ diff --git a/src/assets/icons/eye-on.png b/src/assets/icons/eye-on.png new file mode 100644 index 0000000000..923096a665 Binary files /dev/null and b/src/assets/icons/eye-on.png differ diff --git a/src/assets/icons/icon_box.png b/src/assets/icons/icon_box.png new file mode 100644 index 0000000000..ff157064a1 Binary files /dev/null and b/src/assets/icons/icon_box.png differ diff --git a/src/assets/icons/icon_box_primary_color.png b/src/assets/icons/icon_box_primary_color.png new file mode 100644 index 0000000000..e91e7a2a8f Binary files /dev/null and b/src/assets/icons/icon_box_primary_color.png differ diff --git a/src/assets/icons/icon_box_y.png b/src/assets/icons/icon_box_y.png new file mode 100644 index 0000000000..0fe7e8a070 Binary files /dev/null and b/src/assets/icons/icon_box_y.png differ diff --git a/src/assets/icons/icon_facebook.png b/src/assets/icons/icon_facebook.png new file mode 100644 index 0000000000..58333d45fa Binary files /dev/null and b/src/assets/icons/icon_facebook.png differ diff --git a/src/assets/icons/icon_google.png b/src/assets/icons/icon_google.png new file mode 100644 index 0000000000..e6f5aff87c Binary files /dev/null and b/src/assets/icons/icon_google.png differ diff --git a/src/assets/icons/icon_instagram.png b/src/assets/icons/icon_instagram.png new file mode 100644 index 0000000000..7a91d111ac Binary files /dev/null and b/src/assets/icons/icon_instagram.png differ diff --git a/src/assets/icons/icon_kakao.png b/src/assets/icons/icon_kakao.png new file mode 100644 index 0000000000..c1501c2f54 Binary files /dev/null and b/src/assets/icons/icon_kakao.png differ diff --git a/src/assets/icons/icon_myprofile.png b/src/assets/icons/icon_myprofile.png new file mode 100644 index 0000000000..891c9e1103 Binary files /dev/null and b/src/assets/icons/icon_myprofile.png differ diff --git a/src/assets/icons/icon_search.png b/src/assets/icons/icon_search.png new file mode 100644 index 0000000000..11b3ddf796 Binary files /dev/null and b/src/assets/icons/icon_search.png differ diff --git a/src/assets/icons/icon_smile.png b/src/assets/icons/icon_smile.png new file mode 100644 index 0000000000..e69b514105 Binary files /dev/null and b/src/assets/icons/icon_smile.png differ diff --git a/src/assets/icons/icon_twitter.png b/src/assets/icons/icon_twitter.png new file mode 100644 index 0000000000..4bf766ebed Binary files /dev/null and b/src/assets/icons/icon_twitter.png differ diff --git a/src/assets/icons/icon_youtube.png b/src/assets/icons/icon_youtube.png new file mode 100644 index 0000000000..f51731d409 Binary files /dev/null and b/src/assets/icons/icon_youtube.png differ diff --git a/src/assets/icons/kebab.svg b/src/assets/icons/kebab.svg new file mode 100644 index 0000000000..8b79b0f5ae --- /dev/null +++ b/src/assets/icons/kebab.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/icons/link.png b/src/assets/icons/link.png new file mode 100644 index 0000000000..4fc0aa43c3 Binary files /dev/null and b/src/assets/icons/link.png differ diff --git a/src/assets/icons/link.svg b/src/assets/icons/link.svg new file mode 100644 index 0000000000..041e1cab30 --- /dev/null +++ b/src/assets/icons/link.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/icons/logo.png b/src/assets/icons/logo.png new file mode 100644 index 0000000000..84aba79348 Binary files /dev/null and b/src/assets/icons/logo.png differ diff --git a/src/assets/icons/pen.svg b/src/assets/icons/pen.svg new file mode 100644 index 0000000000..20548fbbba --- /dev/null +++ b/src/assets/icons/pen.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/icons/share.svg b/src/assets/icons/share.svg new file mode 100644 index 0000000000..7dbbb92a38 --- /dev/null +++ b/src/assets/icons/share.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/icons/trash.svg b/src/assets/icons/trash.svg new file mode 100644 index 0000000000..1697c68c6b --- /dev/null +++ b/src/assets/icons/trash.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/login_btn.png b/src/assets/login_btn.png new file mode 100644 index 0000000000..93658ab084 Binary files /dev/null and b/src/assets/login_btn.png differ diff --git a/src/assets/login_logo.png b/src/assets/login_logo.png new file mode 100644 index 0000000000..617557cc7a Binary files /dev/null and b/src/assets/login_logo.png differ diff --git a/src/components/Folder/FolderInput.tsx b/src/components/Folder/FolderInput.tsx new file mode 100644 index 0000000000..edac9e7d90 --- /dev/null +++ b/src/components/Folder/FolderInput.tsx @@ -0,0 +1,126 @@ +import { styled } from "styled-components"; +import Link from "../../assets/icons/link.svg"; +import { BlueButton } from "../common/BlueButton"; +import { forwardRef, SetStateAction, Dispatch } from "react"; + +interface PropsType { + setIsVisible: Dispatch>; + $isAddLinkVisible: boolean; +} + +const FolderInput = forwardRef( + ({ setIsVisible, $isAddLinkVisible }, ref) => { + const onAddLinkButtonClick = () => { + setIsVisible("폴더 추가"); + }; + + return ( + + {!$isAddLinkVisible ? ( + + + LinkIcon + + onAddLinkButtonClick()} + > + + + ) : ( + + LinkIcon + + onAddLinkButtonClick()} + > + + )} + + ); + } +); + +export default FolderInput; + +const BackGround = styled.div` + background-color: var(--Grey_100); + display: flex; + justify-content: center; + position: relative; +`; + +const BackGroundFixed = styled.div` + background-color: var(--Grey_100); + display: flex; + justify-content: center; + position: fixed; + left: 0; + right: 0; + bottom: 0; + z-index: 9999; +`; + +const InputBox = styled.div<{ margin: string }>` + width: 800px; + padding: 16px 20px; + border-radius: 15px; + border: 1px solid var(--Linkbrary-primary-color, #6d6afe); + background: var(--Linkbrary-white, #fff); + margin: 60px auto 90px; + display: flex; + flex-direction: row; + + @media (max-width: 1124px) { + width: 704px; + } + @media (max-width: 774px) { + width: 325px; + } +`; + +const InputBoxFixed = styled(InputBox)` + margin: 24px auto; + + @media (max-width: 360px) { + margin: 16px auto; + } +`; + +const Input = styled.input` + width: 100%; + border: none; + margin-left: 12px; + + &:focus { + outline: none; + } + + &::placeholder { + color: #9fa6b2; + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 24px; + } + + @media (max-width: 774px) { + &::placeholder { + font-size: 14px; + } + } +`; \ No newline at end of file diff --git a/src/components/Folder/FolderTitle.tsx b/src/components/Folder/FolderTitle.tsx new file mode 100644 index 0000000000..6cf4a794e6 --- /dev/null +++ b/src/components/Folder/FolderTitle.tsx @@ -0,0 +1,104 @@ +import styled from "styled-components"; +import share from "../../assets/icons/share.svg"; +import pen from "../../assets/icons/pen.svg"; +import trash from "../../assets/icons/trash.svg"; + +const FolderTitle = ({ titleName, setIsModal }) => { + return ( + + {titleName} + {titleName !== "전체" && ( + + + + + + )} + + ); +}; + +const Container = styled.div` + width: 1060px; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + margin: 24px auto; + + @media (max-width: 1124px) { + width: 704px; + } + @media (max-width: 774px) { + width: 325px; + flex-direction: column; + align-items: flex-start; + gap: 12px; + margin: 28px auto 20px; + } +`; + +const Title = styled.span` + color: #000; + font-family: Pretendard; + font-size: 24px; + font-style: normal; + font-weight: 600; + line-height: normal; + letter-spacing: -0.2px; +`; + +const OptionBox = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 12px; +`; + +const Option = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 4px; + + &:hover { + cursor: pointer; + } +`; + +const OptionIcon = styled.img` + width: 18px; + height: 18px; +`; + +const OptionText = styled.span` + color: #9fa6b2; + font-family: Pretendard; + font-size: 14px; + font-style: normal; + font-weight: 600; + line-height: normal; +`; + +export default FolderTitle; diff --git a/src/components/Folder/Menus.tsx b/src/components/Folder/Menus.tsx new file mode 100644 index 0000000000..6588d47db8 --- /dev/null +++ b/src/components/Folder/Menus.tsx @@ -0,0 +1,126 @@ +import { useState } from "react"; +import { getFolderList } from "../../api/api"; +import GlobalStyle from "../common/GlobalStyle"; +import styled from "styled-components"; +import union from "../../assets/icons/Union.svg"; +import { COLORS } from "../../constants/colors"; +import { useGetPromise } from "../../hooks/uesGetPromise"; + +const Menus = ({ changeTitle, changeID, setIsVisible }) => { + const listsData: any = useGetPromise(getFolderList); + const lists = listsData?.data ?? []; + if (lists[0]) { + lists[0].name === "전체" || lists.unshift({ id: 0, name: "전체" }); + } + + const initialButtonColors = lists.reduce((colors, list) => { + colors[list.name] = COLORS.White; + return colors; + }, {}); + + const [buttonColors, setButtonColors] = useState(initialButtonColors); + + const handleClick = async (name, id) => { + changeTitle(name); + changeID(id); + setButtonColors((prevColors) => { + return { + ...initialButtonColors, + [name]: + prevColors[name] === COLORS.White ? COLORS.Primary : COLORS.White, + }; + }); + }; + + return ( + + + + {lists.map((val) => ( + + ))} + + setIsVisible("폴더 추가")}> + 폴더 추가 + unionIcon + + + ); +}; + +const Container = styled.div` + width: 1080px; + margin: 0px auto; + display: flex; + padding: 8px 12px; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + + @media (max-width: 1124px) { + width: 704px; + } + @media (max-width: 774px) { + width: 325px; + padding: 0px; + } +`; + +const ButtonDiv = styled.div` + display: flex; + flex-direction: row; + gap: 8px; + flex-wrap: wrap; + gap: 12px 8px; +`; + +const Button = styled.button` + min-width: max-content; + padding: 8px 12px; + border-radius: 5px; + border: 1px solid ${COLORS.Primary}; + background-color: ${({ color }) => color || COLORS.White}; + color: ${({ color = COLORS.White }) => + color === COLORS.White ? "#000000" : "#FFFFFF"}; + transition: all 0.3s ease-in-out; + + &:hover { + cursor: pointer; + } +`; + +const AddFolderDiv = styled.div` + margin: 8px; + display: flex; + flex-direction: row; + + &:hover { + cursor: pointer; + } + + @media (max-width: 774px) { + display: none; + } +`; + +const AddFolder = styled.span` + color: #6d6afe; + text-align: center; + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: normal; + letter-spacing: -0.3px; + margin-right: 4px; +`; + +export default Menus; \ No newline at end of file diff --git a/src/components/common/BlueButton.tsx b/src/components/common/BlueButton.tsx new file mode 100644 index 0000000000..3f2bdfeb90 --- /dev/null +++ b/src/components/common/BlueButton.tsx @@ -0,0 +1,54 @@ +import { styled } from "styled-components"; +import { COLORS } from "../../constants/colors"; + +export interface ButtonProps { + width?: string; + height?: string; + radius?: string; + margin?: string; + padding?: string; + fontSize?: string; +} + +export const BlueButton = ({ + text, + width, + height, + margin, + padding, + fontSize, + radius, + onBtnHandle, +}) => { + return ( + + ); +}; +const Button = styled.button` + display: block; + width: ${({ width }) => width || "auto"}; + min-width: max-content; + height: ${({ height }) => height || "auto"}; + border: 0px; + border-radius: ${({ radius }) => radius || "0px"}; + margin: ${({ margin }) => margin || "auto"}; + padding: ${({ padding }) => padding || "auto"}; + background: linear-gradient(91deg, #6d6afe 0.12%, #6ae3fe 101.84%); + cursor: pointer; + + color: ${({ color }) => color}; + font-size: ${({ fontSize }) => fontSize || "14px"}; + font-weight: 600; + line-height: 21.6px; +`; \ No newline at end of file diff --git a/src/components/common/FolderItem.tsx b/src/components/common/FolderItem.tsx new file mode 100644 index 0000000000..f4d150dc45 --- /dev/null +++ b/src/components/common/FolderItem.tsx @@ -0,0 +1,163 @@ +import styled from "styled-components"; +import { useState } from "react"; +import { CalcTime } from "../../utils/calculator"; +import { ReactComponent as Star } from "../../assets/icons/card_star.svg"; +import { ReactComponent as Kebab } from "../../assets/icons/kebab.svg"; +import logo from "../../assets/icons/logo.png"; +import { PopOver } from "./modals/PopOver"; +import "../../styles/shared.css"; + +function FolderItem({ item, $isModalVisible, setIsModalVisible }) { + const [isHovering, setIsHovering] = useState(false); + const { imageSource, createdAt, description, url, id } = item; + const { created_at, favorite, image_source } = item; + const [isPopOverVisible, setIsPopOverVisible] = useState(false); + + let time = ""; + let img_src = ""; + + if (created_at) { + time = CalcTime(created_at); + img_src = image_source; + } else { + time = CalcTime(createdAt); + img_src = imageSource; + } + + const handleMouseOver = () => { + setIsHovering(true); + }; + + const handleMouseOut = () => { + setIsHovering(false); + }; + + return ( + + + + {img_src ? ( + {id} + ) : ( + + logo + + )} + + + + + {time} + { + e.preventDefault(); + setIsPopOverVisible(!isPopOverVisible); + }} + > + + + {description} + 2023. 3. 26 + + + + ); +} + +const Folder = styled.div` + display: flex; + flex-direction: column; + justify-content: space-between; + border-radius: 15px; + box-shadow: 0px 5px 25px 0px rgba(0, 0, 0, 0.08); + position: relative; +`; + +const ImageContainer = styled.div` + width: 100%; + height: 230px; + overflow: hidden; + margin: 0px; + border-radius: 15px 15px 0px 0px; + display: flex; + justify-content: center; + align-items: center; +`; + +const DefaultImage = styled.div` + width: 100%; + height: 100%; + background-color: #dddfff; + display: flex; + justify-content: center; + align-items: center; +`; + +const TextBox = styled.div` + width: 100%; + height: 135px; + display: flex; + flex-direction: column; + gap: 10px; + padding: 15px 20px; + border-radius: 0px 0px 15px 15px; +`; + +const Description = styled.div` + width: 100%; + height: 49px; + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 24px; + margin: 0px; + color: #000; + + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; +`; + +const TimeContainer = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + position: relative; +`; + +const TimeText = styled.span` + font-size: 13px; + font-weight: 400; + color: #666; + margin: 0px; +`; + +const DateText = styled.span` + overflow: hidden; + color: #333; + text-overflow: ellipsis; + white-space: nowrap; + font-family: Pretendard; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; + margin: 0px; +`; + +export default FolderItem; diff --git a/src/components/common/FolderList.tsx b/src/components/common/FolderList.tsx new file mode 100644 index 0000000000..d1907728ab --- /dev/null +++ b/src/components/common/FolderList.tsx @@ -0,0 +1,22 @@ +import FolderItem from "./FolderItem"; + +function FolderList({ items, $isModalVisible, setIsModalVisible }) { + return ( +
+
+ {items.map((item) => { + return ( + + ); + })} +
+
+ ); +} + +export default FolderList; diff --git a/src/components/common/FooterElement.tsx b/src/components/common/FooterElement.tsx new file mode 100644 index 0000000000..f8c40eff1f --- /dev/null +++ b/src/components/common/FooterElement.tsx @@ -0,0 +1,58 @@ +import facebookIcon from "../../assets/icons/icon_facebook.png"; +import twitterIcon from "../../assets/icons/icon_twitter.png"; +import youtubeIcon from "../../assets/icons/icon_youtube.png"; +import instagramIcon from "../../assets/icons/icon_instagram.png"; + +import "../../styles/common.css"; + +function FooterElement() { + return ( + + ); +} + +export default FooterElement; diff --git a/src/components/common/GlobalStyle.ts b/src/components/common/GlobalStyle.ts new file mode 100644 index 0000000000..54c95c80c7 --- /dev/null +++ b/src/components/common/GlobalStyle.ts @@ -0,0 +1,11 @@ +import { createGlobalStyle } from "styled-components"; + +const GlobalStyle = createGlobalStyle` + * { + margin: 0; + padding: 0; + box-sizing: border-box; + } +`; + +export default GlobalStyle; diff --git a/src/components/common/HeaderElement.tsx b/src/components/common/HeaderElement.tsx new file mode 100644 index 0000000000..8906f92a68 --- /dev/null +++ b/src/components/common/HeaderElement.tsx @@ -0,0 +1,65 @@ +import styled from "styled-components"; +import logo from "../../assets/Linkbrary.png"; +import profile from "../../assets/icons/icon_myprofile.png"; +import { getUserInfo } from "../../api/api"; +import { useGetPromise } from "../../hooks/uesGetPromise"; +import "../../styles/common.css"; +import { Link } from 'react-router-dom'; + +interface propTypes { + $positionval: string; +} + +function HeaderElement({ $positionval }) { + const user: any = useGetPromise(getUserInfo); + const email = user?.email; + const profileImageSource = user?.profileImageSource; + + return ( +
+ + logo + +
+ {user ? ( +
+
+ myProfile-img +
+ {email} +
+ ) : ( + + + + )} +
+
+ ); +} + +const Header = styled.div` + background-color: var(--Grey_100); + padding: 20px 200px; + position: ${({ $positionval }) => ($positionval ? $positionval : "sticky")}; + top: 0; + z-index: 2; + display: flex; + justify-content: space-between; + align-items: center; + + @media (max-width: 1124px) { + padding: 32px; + } + @media (max-width: 774px) { + padding: 18px 32px; + } +`; + +export default HeaderElement; \ No newline at end of file diff --git a/src/components/common/Input.tsx b/src/components/common/Input.tsx new file mode 100644 index 0000000000..73db321bed --- /dev/null +++ b/src/components/common/Input.tsx @@ -0,0 +1,50 @@ +import styled from "styled-components"; +import searchIcon from "../../assets/icons/icon_search.png"; +import { ReactComponent as Delete } from "../../assets/icons/delete.svg"; + +const Input = ({ setInputValue, inputValue, onEnterButtonHandle }) => { + const onClickDeleteButtonHandle = () => { + setInputValue(""); + }; + + const onKeyPressHandle = (e) => { + if (e.key === "Enter") { + onEnterButtonHandle(); + } + }; + + return ( + + ); +}; + +const DeleteAllButton = styled.button` + width: 25px; + height: 25px; + border-radius: 999px; + border: none; + background-color: #ccd5e3; + display: flex; + justify-content: center; + align-items: center; + color: white; + + @media (max-width: 767px) { + width: 29px; + height: 25px; + } +`; + +export default Input; \ No newline at end of file diff --git a/src/components/common/RedButton.tsx b/src/components/common/RedButton.tsx new file mode 100644 index 0000000000..2aaac9ad56 --- /dev/null +++ b/src/components/common/RedButton.tsx @@ -0,0 +1,44 @@ +import { styled } from "styled-components"; +import { COLORS } from "../../constants/colors"; +import { ButtonProps } from "./BlueButton"; + +export const RedButton = ({ + text, + width, + height, + margin, + padding, + fontSize, + radius, +}) => { + return ( + + ); +}; + +const Button = styled.button` + display: block; + width: ${({ width }) => width || "auto"}; + height: ${({ height }) => height || "auto"}; + border: 0px; + border-radius: ${({ radius }) => radius || "0px"}; + margin: ${({ margin }) => margin || "auto"}; + padding: ${({ padding }) => padding || "auto"}; + background: ${COLORS.Red}; + cursor: pointer; + + color: ${({ color }) => color}; + font-size: ${({ fontSize }) => fontSize || "14px"}; + font-weight: 600; + line-height: 21.6px; +`; \ No newline at end of file diff --git a/src/components/common/modals/AddFolderModal.tsx b/src/components/common/modals/AddFolderModal.tsx new file mode 100644 index 0000000000..d8ebc08b5d --- /dev/null +++ b/src/components/common/modals/AddFolderModal.tsx @@ -0,0 +1,103 @@ +import styled from "styled-components"; +import { COLORS } from "../../../constants/colors"; +import { BlueButton } from "../BlueButton"; +import closeIcon from "../../../assets/icons/closeModal.png"; + +export interface ModalPropsType { + $isVisible: string; +} +export const AddFolderModal = ({ $isModalVisible, setIsModalVisible }) => { + const handleCloseBtn = () => { + setIsModalVisible(null); + }; + + return ( + + + handleCloseBtn()}> + {closeIcon} + + 폴더 추가 + + {}} + > + + + ); +}; + +const Background = styled.div` + display: ${({ $isVisible }) => + $isVisible === "폴더 추가" ? "block" : "none"}; + z-index: 2; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.8); + transition: visibility 0.3s ease; +`; + +const Modal = styled.div` + position: absolute; + top: 20%; + left: 50%; + transform: translate(-50%, 50%); + padding: 32px 40px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 24px; + border-radius: 15px; + background-color: ${COLORS.White}; + transition: visibility 0.3s ease; +`; + +const Close = styled.button` + border: none; + position: absolute; + top: 16px; + right: 16px; + + &:hover { + cursor: pointer; + } +`; + +const Title = styled.div` + color: #373740; + font-family: Pretendard; + font-size: 20px; + font-style: normal; + font-weight: 700; + line-height: normal; +`; + +const Input = styled.input` + width: 280px; + padding: 18px 15px; + border-radius: 8px; + border: 1px solid ${COLORS.Grey_300}; + background: ${COLORS.White}; + color: var(--Linkbrary-gray100, #373740); + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 24px; + margin-bottom: 10px; + + &:focus { + border: 1px solid ${COLORS.Primary}; + } +`; \ No newline at end of file diff --git a/src/components/common/modals/AddToFolder.tsx b/src/components/common/modals/AddToFolder.tsx new file mode 100644 index 0000000000..06cd687ada --- /dev/null +++ b/src/components/common/modals/AddToFolder.tsx @@ -0,0 +1,163 @@ +import { styled } from "styled-components"; +import { COLORS } from "../../../constants/colors"; +import closeIcon from "../../../assets/icons/closeModal.png"; +import { BlueButton } from "../BlueButton"; + +interface PropsType { + $isVisible: string; +} + +export const AddToFolder = ({ $isModalVisible, setIsModalVisible }) => { + const handleCloseBtn = () => { + setIsModalVisible(null); + }; + + return ( + { + e.preventDefault(); + }} + > + + { + handleCloseBtn(); + }} + > + {closeIcon} + + + <h3>폴더에 추가</h3> + <p>링크 주소</p> + + + + 코딩팁

7개 링크

+
+ + 채용 사이트

12개 링크

+
+ + 유용한 글

30개 링크

+
+ + 나만의 장소

3개 링크

+
+
+ {}} + padding="16px 20px" + fontSize="16px" + radius="8px" + > +
+
+ ); +}; + +const Background = styled.div` + display: ${({ $isVisible }) => + $isVisible === "폴더에 추가" ? "block" : "none"}; + z-index: 2; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.8); + transition: visibility 0.3s ease; + + &:hover { + cursor: default; + } +`; + +const Modal = styled.div` + position: absolute; + top: 10%; + left: 50%; + transform: translate(-50%, 50%); + padding: 32px 40px; + background-color: #fff; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 24px; + border-radius: 15px; + background: ${COLORS.White}; + transition: visibility 0.3s ease; +`; + +const Close = styled.button` + border: none; + position: absolute; + top: 16px; + right: 16px; + + &:hover { + cursor: pointer; + } +`; + +const Title = styled.div` + text-align: center; + font-family: Pretendard; + font-style: normal; + + & > h3 { + color: #373740; + font-size: 20px; + font-weight: 700; + line-height: normal; + margin-bottom: 8px; + } + + & > p { + color: ${COLORS.Grey_400}; + font-size: 14px; + font-weight: 400; + line-height: 22px; /* 157.143% */ + } +`; + +const Folders = styled.div` + width: 264px; +`; + +const Folder = styled.div` + width: 100%; + padding: 8px; + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + + color: #373740; + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 24px; + margin-right: 8px; + + &:hover { + cursor: pointer; + background: ${COLORS.Grey_100}; + color: ${COLORS.Primary}; + } + + & > p { + color: ${COLORS.Grey_400}; + font-family: Pretendard; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; + } +`; \ No newline at end of file diff --git a/src/components/common/modals/DeleteModal.tsx b/src/components/common/modals/DeleteModal.tsx new file mode 100644 index 0000000000..d59496b7bc --- /dev/null +++ b/src/components/common/modals/DeleteModal.tsx @@ -0,0 +1,104 @@ +import styled from "styled-components"; +import { COLORS } from "../../../constants/colors"; +import closeIcon from "../../../assets/icons/closeModal.png"; +import { RedButton } from "../../../components/common/RedButton"; + +interface propsType { + $isVisible: string; +} + +export const DeleteModal = ({ $isModalVisible, setIsModalVisible }) => { + const handleCloseBtn = () => { + setIsModalVisible(null); + }; + + return ( + + + { + e.preventDefault(); + handleCloseBtn(); + }} + > + {closeIcon} + + + <h3>폴더 삭제</h3> + <p>폴더명</p> + + + + + ); +}; + +const Background = styled.div` + display: ${({ $isVisible }) => ($isVisible === "삭제" ? "block" : "none")}; + z-index: 2; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.8); + transition: visibility 0.3s ease; +`; + +const Modal = styled.div` + position: absolute; + top: 30%; + left: 50%; + transform: translate(-50%, 50%); + padding: 32px 40px; + background-color: #fff; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 24px; + border-radius: 15px; + background: ${COLORS.White}; + transition: visibility 0.3s ease; +`; + +const Close = styled.button` + border: none; + position: absolute; + top: 16px; + right: 16px; + + &:hover { + cursor: pointer; + } +`; + +const Title = styled.div` + & > h3 { + color: #373740; + font-family: Pretendard; + font-size: 20px; + font-style: normal; + font-weight: 700; + line-height: normal; + margin-bottom: 8px; + } + + & > p { + color: ${COLORS.Grey_400}; + text-align: center; + font-family: Pretendard; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 22px; /* 157.143% */ + } +`; \ No newline at end of file diff --git a/src/components/common/modals/EditNameModal.tsx b/src/components/common/modals/EditNameModal.tsx new file mode 100644 index 0000000000..bd5ebe1f80 --- /dev/null +++ b/src/components/common/modals/EditNameModal.tsx @@ -0,0 +1,105 @@ +import { styled } from "styled-components"; +import { COLORS } from "../../../constants/colors"; +import closeIcon from "../../../assets/icons/closeModal.png"; +import { BlueButton } from "../BlueButton"; + +interface PropsType { + $isVisible: string; +} + +export const EditNameModal = ({ $isModalVisible, setIsModalVisible }) => { + const handleCloseBtn = () => { + setIsModalVisible(null); + }; + + return ( + + + handleCloseBtn()}> + {closeIcon} + + 폴더 이름 변경 + + {}} + > + + + ); +}; + +const Background = styled.div` + display: ${({ $isVisible }) => + $isVisible === "이름 변경" ? "block" : "none"}; + z-index: 2; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.8); + transition: visibility 0.3s ease; +`; + +const Modal = styled.div` + position: absolute; + top: 30%; + left: 50%; + transform: translate(-50%, 50%); + padding: 32px 40px; + background-color: #fff; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 24px; + border-radius: 15px; + background: ${COLORS.White}; + transition: visibility 0.3s ease; +`; + +const Close = styled.button` + border: none; + position: absolute; + top: 16px; + right: 16px; + + &:hover { + cursor: pointer; + } +`; + +const Title = styled.div` + color: #373740; + font-family: Pretendard; + font-size: 20px; + font-style: normal; + font-weight: 700; + line-height: normal; +`; + +const Input = styled.input` + width: 280px; + padding: 18px 15px; + border-radius: 8px; + border: 1px solid ${COLORS.Primary}; + background: ${COLORS.White}; + color: var(--Linkbrary-gray100, #373740); + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 24px; + margin-bottom: 10px; + + &:focus { + border: 1px solid ${COLORS.Primary}; + } +`; \ No newline at end of file diff --git a/src/components/common/modals/PopOver.tsx b/src/components/common/modals/PopOver.tsx new file mode 100644 index 0000000000..d0deb3b38c --- /dev/null +++ b/src/components/common/modals/PopOver.tsx @@ -0,0 +1,76 @@ +import { styled } from "styled-components"; +import { COLORS } from "../../../constants/colors"; +import { DeleteModal } from "../../../components/common/modals/DeleteModal"; +import { AddToFolder } from "../../../components/common/modals/AddToFolder"; + +export const PopOver = ({ + $isPopOverVisible, + setIsPopOverVisible, + $options, + $modalType, + $top, + $right, + $isModalVisible, + setIsModalVisible, +}) => { + return ( + <> + + + + {$options.map((option, index) => ( + + ))} + + + ); +}; + +interface PropsType { + $right: number; + $top: number; + $isVisible: boolean; +} + +const MenuOptions = styled.div` + width: 100px; + position: absolute; + right: ${({ $right }) => $right ?? 0}; + top: ${({ $top }) => $top ?? 0}; + border: 1px; + display: ${({ $isVisible }) => ($isVisible ? "block" : "none")}; + background-color: #fff; + z-index: 1; +`; + +const Option = styled.p` + padding: 7px 12px; + background-color: #fff; + color: #333236; + font-family: Pretendard; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; + text-align: center; + + &:hover { + cursor: pointer; + color: ${COLORS.Primary}; + background-color: ${COLORS.Grey_100}; + } +`; \ No newline at end of file diff --git a/src/components/common/modals/SharedModal.tsx b/src/components/common/modals/SharedModal.tsx new file mode 100644 index 0000000000..2e3e420104 --- /dev/null +++ b/src/components/common/modals/SharedModal.tsx @@ -0,0 +1,160 @@ +import styled from "styled-components"; +import { COLORS } from "../../../constants/colors"; +import closeIcon from "../../../assets/icons/closeModal.png"; +import kakao from "../../../assets/icons/icon_kakao.png"; +import facebook from "../../../assets/icons/icon_facebook.png"; +import link from "../../../assets/icons/link.png"; + +interface PropsType { + $isVisible?: string; + $backgroundColor?: string; +} + +export const SharedModal = ({ $isModalVisible, setIsModalVisible }) => { + const ICONS = [ + { + name: "카카오톡", + backgroundColor: "#F5E14B", + imgUrl: kakao, + }, + { + name: "페이스북", + backgroundColor: "#1877F2", + imgUrl: facebook, + }, + { + name: "링크 복사", + imgUrl: link, + }, + ]; + + const handleCloseBtn = () => { + setIsModalVisible(null); + }; + + return ( + + + handleCloseBtn()}> + {closeIcon} + + + <h3>폴더 공유</h3> + <p>폴더명</p> + + + {ICONS.map((icon) => ( + + + {icon.name} + + {icon.name} + + ))} + + + + ); +}; + +const Background = styled.div` + display: ${({ $isVisible }) => ($isVisible === "공유" ? "block" : "none")}; + z-index: 2; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.8); + transition: visibility 0.3s ease; +`; + +const Modal = styled.div` + position: absolute; + top: 30%; + left: 50%; + transform: translate(-50%, 50%); + padding: 32px 40px; + background-color: #fff; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 24px; + border-radius: 15px; + background: ${COLORS.White}; + transition: visibility 0.3s ease; +`; + +const Close = styled.button` + border: none; + position: absolute; + top: 16px; + right: 16px; + + &:hover { + cursor: pointer; + } +`; + +const Title = styled.div` + & > h3 { + color: #373740; + font-family: Pretendard; + font-size: 20px; + font-style: normal; + font-weight: 700; + line-height: normal; + margin-bottom: 8px; + } + + & > p { + color: ${COLORS.Grey_400}; + text-align: center; + font-family: Pretendard; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: 22px; /* 157.143% */ + } +`; + +const Icons = styled.div` + display: flex; + flex-direction: row; + gap: 32px; + padding: 0px 32px; +`; + +const Icon = styled.div` + width: max-content; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 10px; + + & > span { + color: #373740; + text-align: center; + font-size: 13px; + font-style: normal; + font-weight: 400; + line-height: 15px; + } +`; + +const IconImg = styled.div` + width: 42px; + height: 42px; + background-color: ${({ $backgroundColor }) => $backgroundColor ?? null}; + border-radius: 40px; + display: flex; + justify-content: center; + align-items: center; + + & > img { + width: 18px; + height: 18px; + } +`; \ No newline at end of file diff --git a/src/components/home/CardFrame.tsx b/src/components/home/CardFrame.tsx new file mode 100644 index 0000000000..b0cc0a1fb3 --- /dev/null +++ b/src/components/home/CardFrame.tsx @@ -0,0 +1,217 @@ +import { styled } from "styled-components"; +import card1 from "../../assets/cards/card1.png"; +import card2 from "../../assets/cards/card2.png"; +import card3 from "../../assets/cards/card3.png"; +import card4 from "../../assets/cards/card4.png"; +import { COLORS } from "../../constants/colors"; + +interface propTypes { + height?: number | null; +} +export const CardFrame = ({ num, reversed, height }) => { + const card = CARDS[`card${num}`]; + return ( +
+ + {reversed ? ( + <> + + {card.headline} + + {`card${num}Img`} + + {card.description} + + + {`card${num}Img`} + + + ) : ( + <> + + {`card${num}Img`} + + + {card.headline} + + {`card${num}Img`} + + {card.description} + + + )} + +
+ ); +}; + +const Section = styled.section` + width: 100%; + height: ${({ height }) => height ?? 550}px; + display: flex; + background-color: ${COLORS.White}; + + @media (max-width: 1124px) { + height: 445px; + } + @media (max-width: 774px) { + height: auto; + } +`; + +const SectionFrame = styled.div` + display: flex; + align-items: center; + justify-content: center; + gap: 157px; + + @media (max-width: 1124px) { + height: 315px; + gap: 50px; + } + @media (max-width: 774px) { + flex-direction: column; + height: auto; + } +`; + +const SectionCards = styled.div` + width: 550px; + height: 450px; + display: flex; + justify-content: center; + align-items: center; + + @media (max-width: 1124px) { + width: 384px; + height: 315px; + } + @media (max-width: 774px) { + width: 384px; + height: 315px; + display: none; + } + + & > img { + width: 100%; + } +`; + +const SectionCardsMobile = styled(SectionCards)` + display: none; + + @media (max-width: 774px) { + display: block; + } +`; + +const Description = styled.div` + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + + & > h3 { + font-family: Pretendard; + font-size: 48px; + font-style: normal; + font-weight: 700; + line-height: normal; + letter-spacing: -0.3px; + margin-bottom: 10px; + } + + @media (max-width: 774px) { + width: 100%; + padding: 40px 32px; + gap: 20px; + + & > h3 { + font-size: 24px; + margin-bottom: 0px; + } + & > h3 > br { + display: none; + } + } +`; + +const StrongText = styled.span` + background: linear-gradient(${({ color }) => color}); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +`; + +const CARDS = { + card1: { + headline: ( +

+ + 원하는 링크 + + 를
+ 저장하세요 +

+ ), + imgUrl: card1, + description: ( +

+ 나중에 읽고 싶은 글, 다시 보고 싶은 영상, +
+ 사고 싶은 옷, 기억하고 싶은 모든 것을 +
한 공간에 저장하세요. +

+ ), + }, + card2: { + headline: ( +

+ 링크를 폴더로
+ 관리 + 하세요 +

+ ), + imgUrl: card2, + description: ( +

+ 나만의 폴더를 무제한으로 만들고 +
+ 다양하게 활용할 수 있습니다. +

+ ), + }, + card3: { + headline: ( +

+ 저장한 링크를
+ + 공유 + + 해 보세요. +

+ ), + imgUrl: card3, + description: ( +

+ 여러 링크를 폴더에 담고 공유할 수 있습니다. +
+ 가족,친구,동료들에게 쉽고 빠르게 링크를 +
+ 공유해 보세요. +

+ ), + }, + card4: { + headline: ( +

+ 저장한 링크를
+ + 검색 + + 해 보세요. +

+ ), + imgUrl: card4, + description:

중요한 정보들을 검색으로 쉽게 찾아보세요.

, + }, +}; \ No newline at end of file diff --git a/src/components/home/Headline.tsx b/src/components/home/Headline.tsx new file mode 100644 index 0000000000..7affe2e48d --- /dev/null +++ b/src/components/home/Headline.tsx @@ -0,0 +1,65 @@ +import styled from "styled-components"; +import { BlueButton } from "../common/BlueButton"; +import { Link } from 'react-router-dom'; + +export const Headline = () => { + return ( + + + + 세상의 모든 정보
+ 쉽게 저장하고 + 관리해 보세요. +
+
+ + {}} + /> + +
+ ); +}; + +const Container = styled.div` + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +`; + +const TextBox = styled.p` + width: 100%; + text-align: center; +`; + +const HeadlineText = styled.span` + font-size: 64px; + font-weight: 700; + + @media (max-width: 774px) { + font-size: 32px; + } +`; + +const Strong = styled(HeadlineText)` + background: linear-gradient(90deg, #6d6afe, #ff9f9f); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +`; + +const LineBreak = styled.br` + display: none; + + @media (max-width: 1124px) { + display: block; + } +`; \ No newline at end of file diff --git a/src/components/shared/SharedSection.tsx b/src/components/shared/SharedSection.tsx new file mode 100644 index 0000000000..1018382efc --- /dev/null +++ b/src/components/shared/SharedSection.tsx @@ -0,0 +1,46 @@ +import { useState, useEffect } from "react"; +import { getFolderInfo } from "../../api/api"; +import smileIcon from "../../assets/icons/icon_smile.png"; +import styled from "styled-components"; + +const SharedSection = () => { + const [folderName, setFolderName] = useState([]); + const [owner, setOwner] = useState([]); + const { profileImageSource, name } = owner; + + async function handleLoad() { + const folderInfo = await getFolderInfo(); + const { name, owner } = folderInfo.folder; + setFolderName(name); + setOwner(owner); + } + + useEffect(() => { + handleLoad(); + }, []); + + return ( +
+ + {name} +
+

{folderName}

+
+
+ ); +}; + +const OwnerProfile = styled.img` + display: flex; + justify-content: center; + width: 60px; + height: 60px; + align-items: center; + border-radius: 47px; + margin-bottom: 12px; +`; + +export default SharedSection; \ No newline at end of file diff --git a/src/constants/colors.ts b/src/constants/colors.ts new file mode 100644 index 0000000000..dcbdb90bba --- /dev/null +++ b/src/constants/colors.ts @@ -0,0 +1,11 @@ +export const COLORS = { + Primary: "#6d6afe", + Red: "#ff5b56", + Black: "#111322", + White: "#fff", + Grey_100: "#f0f6ff", + Grey_200: "#e7effb", + Grey_300: "#ccd5e3", + Grey_400: "#9fa6b2", + Grey_500: "#3e3e43", +}; diff --git a/src/constants/errorMsg.ts b/src/constants/errorMsg.ts new file mode 100644 index 0000000000..5555e6f67e --- /dev/null +++ b/src/constants/errorMsg.ts @@ -0,0 +1,16 @@ +const ERROR_MESSAGE = { + email: { + empty: "이메일을 입력해주세요.", + invalid: "올바른 이메일이 아닙니다.", + check: "이메일을 확인해 주세요.", + inUse: "이미 존재하는 이메일입니다.", + }, + password: { + empty: "비밀번호을 입력해 주세요", + invalid: "비밀번호는 영문,숫자 조합 8자 이상 입력해주세요.", + check: "비밀번호을 확인해 주세요", + recheck: "비밀번호가 일치하지 않아요.", + }, +}; + +export { ERROR_MESSAGE }; diff --git a/src/hooks/uesGetPromise.ts b/src/hooks/uesGetPromise.ts new file mode 100644 index 0000000000..439e337e5a --- /dev/null +++ b/src/hooks/uesGetPromise.ts @@ -0,0 +1,23 @@ +import { useState, useEffect, useCallback } from "react"; + +export const useGetPromise = (func) => { + const [values, setValues] = useState([]); + const HandleLoad = useCallback(async () => { + let results; + try { + results = await func(); + // await console.log(results); + } catch (error) { + console.error(error); + } + + if (!results) return; + setValues(results); + }, []); + + useEffect(() => { + HandleLoad(); + }, [HandleLoad]); + + return values; +}; diff --git a/src/index.js b/src/index.js index d563c0fb10..faaccb31ae 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,5 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; -import reportWebVitals from './reportWebVitals'; +import ReactDOM from "react-dom/client"; +import Main from "./Main"; -const root = ReactDOM.createRoot(document.getElementById('root')); -root.render( - - - -); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); +const root = ReactDOM.createRoot(document.getElementById("root")); +root.render(
); diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 9dfc1c058c..0000000000 --- a/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/pages/Folder.tsx b/src/pages/Folder.tsx new file mode 100644 index 0000000000..db53ef0556 --- /dev/null +++ b/src/pages/Folder.tsx @@ -0,0 +1,192 @@ +import { useState, useEffect, useRef } from "react"; +import styled from "styled-components"; +import { getAllLinkData } from "../api/api"; +import HeaderElement from "../components/common/HeaderElement"; +import FooterElement from "../components/common/FooterElement"; +import GlobalStyle from "../components/common/GlobalStyle"; +import FolderInput from "../components/Folder/FolderInput"; +import FolderList from "../components/common/FolderList"; +import Input from "../components/common/Input"; +import Menus from "../components/Folder/Menus"; +import FolderTitle from "../components/Folder/FolderTitle"; +import { SharedModal } from "../components/common/modals/SharedModal"; +import { EditNameModal } from "../components/common/modals/EditNameModal"; +import { DeleteModal } from "../components/common/modals/DeleteModal"; +import { AddFolderModal } from "../components/common/modals/AddFolderModal"; +import { COLORS } from "../constants/colors"; + +const Folder = () => { + const [titleName, setTitleName] = useState("전체"); + const [listId, setListId] = useState(""); + const [data, setData] = useState([]); + const [isModalVisible, setIsModalVisible] = useState(null); + const [searchInputValue, setSearchInputValue] = useState(""); + + useEffect(() => { + const fetchData = async () => { + try { + const response = await getAllLinkData(listId); + const result = await response.data; + setData(result); + } catch (error) { + console.error(error); + } + }; + + fetchData(); + }, [listId]); + + const onSearchEnterClickHandle = () => { + findCardsByKeyword(searchInputValue); + }; + + const addLinkDivRef = useRef(null); + const footerDivRef = useRef(null); + const [isAddLinkVisible, setIsAddLinkVisible] = useState(false); + + const onIntersectionHandle = async (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setIsAddLinkVisible(true); + } else { + setIsAddLinkVisible(false); + } + }); + }; + useEffect(() => { + if (addLinkDivRef.current) { + const observer1 = new IntersectionObserver(onIntersectionHandle, { + threshold: 0.1, + }); + + if (addLinkDivRef.current) { + observer1.observe(addLinkDivRef.current); + } + + const observer2 = new IntersectionObserver(onIntersectionHandle, { + threshold: 0.1, + }); + + if (footerDivRef.current) { + observer2.observe(footerDivRef.current); + } + + return () => { + observer1.disconnect(); + observer2.disconnect(); + }; + } + }, []); + + const findCardsByKeyword = (keyword: string) => { + const results = []; + if (data) { + data.forEach((card) => { + const cardInfo = card.title + card.description + card.url; + if (cardInfo && cardInfo.includes(keyword)) { + results.push(card); + } else { + console.log("없음"); + } + }); + } + setData(results); + }; + + return ( + + + + + + + + + + + + {data[0] ? ( + + ) : ( + 저장된 링크가 없습니다. + )} + + 폴더 추가 + + +
+ +
+
+ ); +}; + +const Container = styled.div` + margin: 0px; +`; + +const NoLinkMsg = styled.p` + color: #000; + text-align: center; + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 400; + line-height: 24px; /* 150% */ + margin-top: 40px; +`; + +const AddFolderBtn = styled.button<{ $isAddLinkVisible: boolean }>` + border: none; + border-radius: 20px; + border: 1px solid ${COLORS.White}; + background: ${COLORS.Primary}; + position: sticky; + left: 50%; + transform: translateX(-50%); + bottom: ${({ $isAddLinkVisible }) => ($isAddLinkVisible ? "50px" : "150px")}; + padding: 8px 24px; + display: none; + + color: ${COLORS.Grey_200}; + text-align: center; + font-family: Pretendard; + font-size: 16px; + font-style: normal; + font-weight: 500; + line-height: normal; + letter-spacing: -0.3px; + + @media (max-width: 375px) { + display: block; + } +`; + +export default Folder; \ No newline at end of file diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx new file mode 100644 index 0000000000..52291ae232 --- /dev/null +++ b/src/pages/Home.tsx @@ -0,0 +1,55 @@ +import styled from "styled-components"; +import HeaderElement from "../components/common/HeaderElement"; +import FooterElement from "../components/common/FooterElement"; +import { Headline } from "../components/home/Headline"; +import { CardFrame } from "../components/home/CardFrame"; +import cards_img from "../assets/cards/cards_img.png"; +import { COLORS } from "../constants/colors"; + +export const Home = () => { + return ( + <> + + + + + {cards_img} + + + + + + + + + ); +}; + +const HeadlineContainer = styled.div` + padding-top: 70px; + display: flex; + flex-direction: column; + align-items: center; + background-color: ${COLORS.Grey_100}; +`; + +const CardsContainer = styled.div` + width: 1118px; + height: 590px; + overflow-y: hidden; + + & > img { + width: 100%; + margin-top: 50px; + border-radius: 25px; + } + + @media (max-width: 1124px) { + width: 698px; + height: 343px; + } + @media (max-width: 774px) { + width: 325px; + height: 160px; + } +`; \ No newline at end of file diff --git a/src/pages/Shared.tsx b/src/pages/Shared.tsx new file mode 100644 index 0000000000..32e4b7b755 --- /dev/null +++ b/src/pages/Shared.tsx @@ -0,0 +1,46 @@ +import { useState } from "react"; +import { getFolderInfo } from "../api/api"; +import HeaderElement from "../components/common/HeaderElement"; +import FooterElement from "../components/common/FooterElement"; +import SharedSection from "../components/shared/SharedSection"; +import Input from "../components/common/Input"; +import FolderList from "../components/common/FolderList"; +import { DeleteModal } from "../components/common/modals/DeleteModal"; +import { AddFolderModal } from "../components/common/modals/AddFolderModal"; +import { useGetPromise } from "../hooks/uesGetPromise"; +import "../styles/shared.css"; + +function Shared() { + const foldersData: any = useGetPromise(getFolderInfo); + const folders = foldersData?.folder?.links || []; + const [searchInputValue, setSearchInputValue] = useState(""); + const [isModalVisible, setIsModalVisible] = useState(null); + + return ( + <> + + + + + {}} + /> + + + + ); +} + +export default Shared; \ No newline at end of file diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts new file mode 100644 index 0000000000..6431bc5fc6 --- /dev/null +++ b/src/react-app-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/reportWebVitals.js b/src/reportWebVitals.ts similarity index 75% rename from src/reportWebVitals.js rename to src/reportWebVitals.ts index 5253d3ad9e..49a2a16e0f 100644 --- a/src/reportWebVitals.js +++ b/src/reportWebVitals.ts @@ -1,4 +1,6 @@ -const reportWebVitals = onPerfEntry => { +import { ReportHandler } from 'web-vitals'; + +const reportWebVitals = (onPerfEntry?: ReportHandler) => { if (onPerfEntry && onPerfEntry instanceof Function) { import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { getCLS(onPerfEntry); diff --git a/src/setupTests.ts b/src/setupTests.ts new file mode 100644 index 0000000000..8f2609b7b3 --- /dev/null +++ b/src/setupTests.ts @@ -0,0 +1,5 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom'; diff --git a/src/styles/common.css b/src/styles/common.css new file mode 100644 index 0000000000..ebb18c8132 --- /dev/null +++ b/src/styles/common.css @@ -0,0 +1,148 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} +:root { + --Primary: #6d6afe; + --Red: #ff5b56; + --Black: #111322; + --White: #ffffff; + --Grey_100: #f0f6ff; + --Grey_200: #e7effb; + --Grey_300: #ccd5e3; + --Grey_400: #9fa6b2; + --Grey_500: #3e3e43; +} + + +#myProfileName { + display: flex; + align-items: center; +} + +.myProfile span { + font-family: Pretendard; + font-size: 14px; + font-style: normal; + font-weight: 400; + line-height: normal; +} + +#myProfile-back_img { + width: 28px; + height: 28px; + background-image: url("../assets/icons/icon_box_primary_color.png"); + margin-right: 6px; + overflow: hidden; + border-radius: 30px; +} + +#myProfile-img { + width: 100%; + height: 100%; +} + +#LoginBtn { + display: block; + width: 128px; + padding: 16px 20px; + border: 0px; + border-radius: 8px; + background: linear-gradient(91deg, #6d6afe 0.12%, #6ae3fe 101.84%); + cursor: pointer; + + font-size: 14px; + font-weight: 600; + line-height: 21.6px; + color: #f5f5f5; +} + +/* footer */ + +footer { + height: 280px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-end; + box-shadow: 0px 0px 16px 0px rgba(0, 0, 0, 0.02); +} + +.footer-frame { + width: 100%; + height: 160px; + display: flex; + flex-direction: row; + padding: 32px 104px 64px 104px; + justify-content: space-between; + background-color: var(--Black); +} + +footer p, +a { + font-size: 16px; + font-weight: 400; + text-decoration-line: none; + color: #cfcfcf; +} + +#footer-notice, +#footer-icons { + display: flex; + gap: 12px; +} + +#footer-icons img { + width: 20px; + height: 20px; + cursor: pointer; +} + +@media (max-width: 1199px) { + header { + padding: 20px 32px; + } + + #myProfileName { + width: 100%; + display: flex; + align-items: center; + } +} + +@media (max-width: 767px) { + header { + padding: 13px 32px; + } + + #myEmail { + display: none; + } + + footer { + height: 200px; + display: flex; + } + + .footer-frame { + width: 100%; + padding: 32px 32px 64px 32px; + position: relative; + gap: auto; + } + + .footer-frame #footer-codeit { + position: absolute; + top: 110px; + left: 32px; + } + + .footer-frame #footer-codeit p { + color: #676767; + } + + #footer-notice { + gap: 30px; + } +} diff --git a/src/styles/shared.css b/src/styles/shared.css new file mode 100644 index 0000000000..21b267f2df --- /dev/null +++ b/src/styles/shared.css @@ -0,0 +1,218 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} +:root { + --Primary: #6d6afe; + --Red: #ff5b56; + --Black: #111322; + --White: #ffffff; + --Grey_100: #f0f6ff; + --Grey_200: #e7effb; + --Grey_300: #ccd5e3; + --Grey_400: #9fa6b2; + --Grey_500: #3e3e43; +} + +/* section */ +section { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + background-color: var(--Grey_100); + width: 100%; + padding-top: 20px; + padding-bottom: 60px; +} + +.codeit-mark-section span { + font-size: 16px; + font-weight: 400; + line-height: 24px; +} + +#favorites h1 { + font-size: 40px; + font-style: normal; + font-weight: 600; +} + +article { + margin: 40px 10px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +/* 검색창 */ +#search-bar { + width: 1060px; + padding: 15px 16px; + display: flex; + flex-direction: row; + background-color: #f5f5f5; + border-radius: 10px; + margin-bottom: 40px; + margin: 40px auto; +} + +#search-bar img { + width: 16px; + height: 16px; + margin-right: 10px; +} + +#search-bar input { + width: 100%; + margin-right: 16px; + font-size: 16px; + font-weight: 400; + color: #666; + border: none; + background-color: transparent; +} + +#search-bar input:focus { + outline: none; +} + +.folders-gridBox { + width: 1060px; + display: grid; + grid-template: 360px 360px 360px / 340px 340px 340px; + gap: 25px 20px; +} + +.folder { + display: flex; + flex-direction: column; + justify-content: space-between; + border-radius: 15px; + box-shadow: 0px 5px 25px 0px rgba(0, 0, 0, 0.08); + position: relative; +} + +.star { + width: 34px; + height: 34px; + position: absolute; + top: 0; + right: 0; + z-index: 1; + margin-top: 15px; + margin-right: 15px; +} + +.imgBox { + width: 100%; + height: 230px; + overflow: hidden; + margin: 0px; + border-radius: 15px 15px 0px 0px; + display: flex; + justify-content: center; + align-items: center; +} + +.folderImage { + width: 340px; + height: 253.746px; + transition: all 0.3s ease-in-out; +} + +.grow { + transform: scale(1.3); + transition: all 0.3s ease-in-out; +} + +.folder-textBox { + width: 100%; + height: 135px; + display: flex; + flex-direction: column; + gap: 10px; + padding: 15px 20px; + border-radius: 0px 0px 15px 15px; +} + +@media (max-width: 1124px) { + header { + padding: 20px 32px; + } + + header div { + margin: 0px auto; + width: 800px; + max-width: 100%; + } + + .folders-gridBox { + width: auto; + grid-template: 360px 360px / 340px 340px; + gap: 25px 24px; + } + + #search-bar { + width: 704px; + } + + article { + margin: 0px; + padding: 40px 35px; + } +} + +@media (max-width: 774px) { + header { + width: 100%; + } + + .folders-gridBox { + width: auto; + grid-template: 360px / 325px; + gap: 25px 24px; + } + + #search-bar { + width: 325px; + margin: 20px auto 32px; + } + + article { + margin: 0px; + padding: 40px 35px; + } + + #myEmail { + border: 1px; + } + + footer { + height: 200px; + display: flex; + } + + .footer-frame { + width: 100%; + padding: 32px 32px 64px 32px; + position: relative; + gap: auto; + } + + .footer-frame #footer-codeit { + position: absolute; + top: 110px; + left: 32px; + } + + .footer-frame #footer-codeit p { + color: #676767; + } + + #footer-notice { + gap: 30px; + } +} diff --git a/src/utils/calculator.js b/src/utils/calculator.js new file mode 100644 index 0000000000..ae2a521ba0 --- /dev/null +++ b/src/utils/calculator.js @@ -0,0 +1,41 @@ +function timeToString(time) { + let diffSec = parseInt(time / 1000); + // 2분 미만 + if (diffSec <= 120) { + return "1 minute ago"; + } + let diffMinute = parseInt(diffSec / 60); + // 59분 이하 + if (diffMinute <= 59) { + return `${diffMinute} minutes ago`; + } + + let diffHour = parseInt(diffMinute / 60); + // 23시간 이하 + if (diffHour <= 23) { + return `${diffHour} hours ago`; + } + // 30일 이하 + let diffDay = parseInt(diffHour / 24); + if (diffDay <= 30) { + return `${diffDay} days ago`; + } + + let diffMonth = parseInt(diffDay / 30); + // 11달 이하 + if (diffMonth <= 11) { + return `${diffMonth} months ago`; + } + // 11달 이상 + let diffYear = parseInt(diffMonth / 12); + return `${diffYear} years ago`; +} + +export function CalcTime(time) { + let nowTime = Date.now(); + let writingTime = Date.parse(time); + let diffTime = nowTime - writingTime; + + let sentence = timeToString(diffTime); + return sentence; +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000..12cda02ef7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "outDir": "dist", + "allowJs": true, + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "moduleResolution": "node", + // "strict": true, + "jsx": "react-jsx" + }, + "ts-node": { + "esm": true + }, + "include": ["src"] +} \ No newline at end of file