From f419b2dd8bcf837bd258c44f1ef05fcd3374fadf Mon Sep 17 00:00:00 2001 From: klmhyeonwoo Date: Tue, 22 Apr 2025 22:52:43 +0900 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20#102=20=EA=B5=AC=EB=8F=85=20?= =?UTF-8?q?=EB=AA=A8=EB=8B=AC=20=ED=8D=BC=EB=B8=94=EB=A6=AC=EC=8B=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + pnpm-lock.yaml | 199 ++++++++- src/App.tsx | 3 +- src/app/home/index.tsx | 8 +- src/assets/chip/delete.svg | 4 + src/components/app/signup/certificate-box.tsx | 24 +- src/components/common/Button.tsx | 31 +- src/components/common/Input.tsx | 14 +- src/components/common/chip.tsx | 38 ++ src/components/common/modal/Modal.tsx | 5 +- .../common/modal/SubscriptionModalContent.tsx | 393 +++++++----------- src/style/variable.scss | 2 +- src/types/modal.ts | 2 +- 13 files changed, 448 insertions(+), 276 deletions(-) create mode 100644 src/assets/chip/delete.svg create mode 100644 src/components/common/chip.tsx diff --git a/package.json b/package.json index 00f3c70..9f183c0 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5", "prettier": "^3.2.5", + "sass": "^1.87.0", "typescript": "^5.2.2", "vite": "^5.0.8", "vite-plugin-radar": "^0.9.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f831d50..fe168f5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -94,12 +94,15 @@ devDependencies: prettier: specifier: ^3.2.5 version: 3.2.5 + sass: + specifier: ^1.87.0 + version: 1.87.0 typescript: specifier: ^5.2.2 version: 5.3.3 vite: specifier: ^5.0.8 - version: 5.0.12(@types/node@20.12.12) + version: 5.0.12(@types/node@20.12.12)(sass@1.87.0) vite-plugin-radar: specifier: ^0.9.6 version: 0.9.6(vite@5.0.12) @@ -753,6 +756,149 @@ packages: fastq: 1.17.0 dev: true + /@parcel/watcher-android-arm64@2.5.1: + resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@parcel/watcher-darwin-arm64@2.5.1: + resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@parcel/watcher-darwin-x64@2.5.1: + resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@parcel/watcher-freebsd-x64@2.5.1: + resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@parcel/watcher-linux-arm-glibc@2.5.1: + resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@parcel/watcher-linux-arm-musl@2.5.1: + resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@parcel/watcher-linux-arm64-glibc@2.5.1: + resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@parcel/watcher-linux-arm64-musl@2.5.1: + resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@parcel/watcher-linux-x64-glibc@2.5.1: + resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@parcel/watcher-linux-x64-musl@2.5.1: + resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@parcel/watcher-win32-arm64@2.5.1: + resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@parcel/watcher-win32-ia32@2.5.1: + resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@parcel/watcher-win32-x64@2.5.1: + resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@parcel/watcher@2.5.1: + resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} + engines: {node: '>= 10.0.0'} + requiresBuild: true + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.5 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.1 + '@parcel/watcher-darwin-arm64': 2.5.1 + '@parcel/watcher-darwin-x64': 2.5.1 + '@parcel/watcher-freebsd-x64': 2.5.1 + '@parcel/watcher-linux-arm-glibc': 2.5.1 + '@parcel/watcher-linux-arm-musl': 2.5.1 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 + '@parcel/watcher-linux-arm64-musl': 2.5.1 + '@parcel/watcher-linux-x64-glibc': 2.5.1 + '@parcel/watcher-linux-x64-musl': 2.5.1 + '@parcel/watcher-win32-arm64': 2.5.1 + '@parcel/watcher-win32-ia32': 2.5.1 + '@parcel/watcher-win32-x64': 2.5.1 + dev: true + optional: true + /@remix-run/router@1.15.2: resolution: {integrity: sha512-+Rnav+CaoTE5QJc4Jcwh5toUpnVLKYbpU6Ys0zqbakqbaLQHeglLVHPfxOiQqdNmUy5C2lXz5dwC6tQNX2JW2Q==} engines: {node: '>=14.0.0'} @@ -1240,7 +1386,7 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.23.3(@babel/core@7.23.9) '@types/babel__core': 7.20.5 react-refresh: 0.14.0 - vite: 5.0.12(@types/node@20.12.12) + vite: 5.0.12(@types/node@20.12.12)(sass@1.87.0) transitivePeerDependencies: - supports-color dev: true @@ -1386,6 +1532,13 @@ packages: supports-color: 7.2.0 dev: true + /chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + dependencies: + readdirp: 4.1.2 + dev: true + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -1489,6 +1642,14 @@ packages: engines: {node: '>=0.4.0'} dev: false + /detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + requiresBuild: true + dev: true + optional: true + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -1879,6 +2040,10 @@ packages: engines: {node: '>= 4'} dev: true + /immutable@5.1.1: + resolution: {integrity: sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==} + dev: true + /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -2134,6 +2299,12 @@ packages: tslib: 2.6.2 dev: true + /node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + requiresBuild: true + dev: true + optional: true + /node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} dev: true @@ -2327,6 +2498,11 @@ packages: loose-envify: 1.4.0 dev: false + /readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + dev: true + /regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} dev: false @@ -2385,6 +2561,18 @@ packages: queue-microtask: 1.2.3 dev: true + /sass@1.87.0: + resolution: {integrity: sha512-d0NoFH4v6SjEK7BoX810Jsrhj7IQSYHAHLi/iSpgqKc7LaIDshFRlSg5LOymf9FqQhxEHs2W5ZQXlvy0KD45Uw==} + engines: {node: '>=14.0.0'} + hasBin: true + dependencies: + chokidar: 4.0.3 + immutable: 5.1.1 + source-map-js: 1.0.2 + optionalDependencies: + '@parcel/watcher': 2.5.1 + dev: true + /scheduler@0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: @@ -2562,7 +2750,7 @@ packages: peerDependencies: vite: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 dependencies: - vite: 5.0.12(@types/node@20.12.12) + vite: 5.0.12(@types/node@20.12.12)(sass@1.87.0) dev: true /vite-plugin-svgr@4.2.0(typescript@5.3.3)(vite@5.0.12): @@ -2573,14 +2761,14 @@ packages: '@rollup/pluginutils': 5.1.0 '@svgr/core': 8.1.0(typescript@5.3.3) '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0) - vite: 5.0.12(@types/node@20.12.12) + vite: 5.0.12(@types/node@20.12.12)(sass@1.87.0) transitivePeerDependencies: - rollup - supports-color - typescript dev: true - /vite@5.0.12(@types/node@20.12.12): + /vite@5.0.12(@types/node@20.12.12)(sass@1.87.0): resolution: {integrity: sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -2612,6 +2800,7 @@ packages: esbuild: 0.19.12 postcss: 8.4.33 rollup: 4.9.6 + sass: 1.87.0 optionalDependencies: fsevents: 2.3.3 dev: true diff --git a/src/App.tsx b/src/App.tsx index 8659e32..1d42992 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,8 +2,9 @@ import React from "react"; import { createRoot } from "react-dom/client"; import { Routers } from "./routes"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import "./style/global.css"; import { Modal } from "@/components/common/modal/Modal.tsx"; +import "./style/global.css"; +import "./style/variable.scss"; const queryClient = new QueryClient(); diff --git a/src/app/home/index.tsx b/src/app/home/index.tsx index 9c7aa4f..c6d82f5 100644 --- a/src/app/home/index.tsx +++ b/src/app/home/index.tsx @@ -28,7 +28,6 @@ import { useAtom } from "jotai"; import { loginState } from "@/store/user"; import { SubscriptionModalContent } from "@/components/common/modal/SubscriptionModalContent"; - type CoinInfoType = { [coin in "BTC" | "ETH" | "XRP"]: { price: number; // 가격 @@ -112,14 +111,13 @@ export default function HomePage() { } }; - /** + /** * @description 구독버튼 클릭시 flowbit서비스에 대해 주기적으로 구독할 수 있는 함수입니다. */ - const openSubscriptionModal = () => { + const openSubscriptionModal = () => { open({ - title: "구독 설정", content: , - isVisibleBtn: false + isVisibleBtn: false, }); }; diff --git a/src/assets/chip/delete.svg b/src/assets/chip/delete.svg new file mode 100644 index 0000000..b33f8ea --- /dev/null +++ b/src/assets/chip/delete.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/app/signup/certificate-box.tsx b/src/components/app/signup/certificate-box.tsx index 039d788..84a7a67 100644 --- a/src/components/app/signup/certificate-box.tsx +++ b/src/components/app/signup/certificate-box.tsx @@ -1,17 +1,17 @@ import { css } from "@emotion/react"; import { PropsWithChildren } from "react"; -import { DESIGN_SYSTEM_COLOR, DESIGN_SYSTEM_TEXT } from "@/style/variable.ts"; +import { DESIGN_SYSTEM_TEXT } from "@/style/variable.ts"; interface certificateProps extends Omit, "type"> { icon?: string; - check?: boolean; + disabled?: boolean; } export default function CertificateBox({ icon, children, - check = false, + disabled = false, ...props }: PropsWithChildren) { return ( @@ -19,20 +19,26 @@ export default function CertificateBox({ id="certificateb-box" css={css` ${DESIGN_SYSTEM_TEXT.B2} - border-radius: 0.2rem; - padding: 0.5rem 1.1rem; - background: ${icon ? `transparent` : DESIGN_SYSTEM_COLOR.BRAND_BLUE}; - color: ${DESIGN_SYSTEM_COLOR.GRAY_50}; white-space: nowrap; width: auto; + border-radius: 6px; + padding: 11px 16px; + background: ${icon ? `transparent` : `var(--blue-5)`}; + color: var(--blue-50); transition: 0.4s all; + font-weight: 400; + font-size: 15px; cursor: pointer; + display: flex; + align-items: center; + ${icon && `padding-right: 0`} - ${!check && + ${disabled && css` pointer-events: none; - filter: grayscale(100%); + background-color: var(--gray-10); + color: var(--text-disabled); `} `} {...props} diff --git a/src/components/common/Button.tsx b/src/components/common/Button.tsx index f474c12..5449156 100644 --- a/src/components/common/Button.tsx +++ b/src/components/common/Button.tsx @@ -1,19 +1,16 @@ import { css } from "@emotion/react"; +// TODO: 버튼 컴포넌트 인라인 스타일 지정의 분리가 필요해요 + type buttonProps = { icon?: string; - state?: boolean; } & Omit, "types">; -export default function Button({ - icon, - state = true, - children, - ...props -}: buttonProps) { +export default function Button({ icon, children, ...props }: buttonProps) { return (
diff --git a/src/components/common/Input.tsx b/src/components/common/Input.tsx index eadac76..40efca3 100644 --- a/src/components/common/Input.tsx +++ b/src/components/common/Input.tsx @@ -13,6 +13,7 @@ export default function Input({ return (
)} @@ -35,8 +36,10 @@ export default function Input({ border: none; box-shadow: inset 0 0 0 1px #eeeeee; width: 100%; - height: 5.6rem; - border-radius: 0.5rem; + height: 45px; + border-radius: 8px; + font-size: 14px; + color: var(--text-body1); padding-left: ${icon ? `6rem` : `2rem`}; &:focus { @@ -46,6 +49,11 @@ export default function Input({ &::placeholder { color: #bdbdbd; } + + &:disabled { + color: var(--text-disabled); + background: var(--gray-10); + } `} placeholder={placeholder} {...props} diff --git a/src/components/common/chip.tsx b/src/components/common/chip.tsx new file mode 100644 index 0000000..2644b3b --- /dev/null +++ b/src/components/common/chip.tsx @@ -0,0 +1,38 @@ +import { css } from "@emotion/react"; +import delete_icon from "@/assets/chip/delete.svg"; + +type ChipType = { + children: string; + closable?: boolean; +}; + +export default function Chip({ children, closable = false }: ChipType) { + return ( +
+ {children} + {closable ? 삭제 아이콘 : null} +
+ ); +} + +const chipCss = css` + display: flex; + align-items: center; + column-gap: 2px; + background-color: #fff; + border: 1px solid var(--gray-10); + border-radius: 4px; + padding: 4px 8px; + width: 100%; + height: 100%; + max-width: fit-content; + max-height: fit-content; + + & > img { + cursor: pointer; + } + + & > span { + color: var(--text-body2); + } +`; diff --git a/src/components/common/modal/Modal.tsx b/src/components/common/modal/Modal.tsx index 950a43f..695a21e 100644 --- a/src/components/common/modal/Modal.tsx +++ b/src/components/common/modal/Modal.tsx @@ -57,7 +57,7 @@ export function Modal() { background-color: #ffffff; border: 1px solid #cbcbcb; border-radius: 1rem; - padding: 2.9rem 2.4rem; + padding: 3.2rem; display: flex; flex-direction: column; text-align: center; @@ -68,7 +68,6 @@ export function Modal() { css={css` display: flex; flex-direction: column; - row-gap: 3rem; color: ${DESIGN_SYSTEM_COLOR.GRAY_700}; `} > @@ -92,7 +91,7 @@ export function Modal() {
{ - const [email, setEmail] = useState(""); - const [emailValid, setEmailValid] = useState(false); const [verifySendCheck, setVerifySendCheck] = useState(false); const [verifyEmailNum, setVerifyEmailNum] = useState(""); const [verifyEmailNumCheck, setVerifyEmailNumCheck] = useState(false); - const [categories, setCategories] = useState({ - bitcoinPrediction: false, - latestNews: false, - }); + const [keywordList, setKeywordList] = useState([]); const { close } = useModal(); + const { email, isValidEmail, handleEmailChange } = useCheckEmail(); - - const handleEmailChange = (e: React.ChangeEvent) => { - const value = e.target.value; - setEmail(value); - setEmailValid(EMAIL_REGEX.test(value)); - }; - - const handleSendVerificationEmail = async () => { - if (!emailValid) return; + const handleSendVerificationEmail = () => { + if (!isValidEmail) return; try { - sendVerifyEmail({ email, emailPurpose: EMAIL_PURPOSE.SUBSCRIBE }); - alert("인증번호가 전송되었습니다. 확인 후 입력해주세요."); + const res = sendVerifyEmail({ + email, + emailPurpose: EMAIL_PURPOSE.SUBSCRIBE, + }); + console.log(res); setVerifySendCheck(true); } catch (error) { alert("이메일 전송에 실패했습니다. 다시 시도해주세요."); } }; - const handleVerifyEmailCode = async () => { + const handleVerifyEmailCode = () => { if (!verifyEmailNum) return; try { - await verifyEmail({ email, randomNumber: verifyEmailNum, emailPurpose: EMAIL_PURPOSE.SUBSCRIBE }); + const res = verifyEmail({ + email, + randomNumber: verifyEmailNum, + emailPurpose: EMAIL_PURPOSE.SUBSCRIBE, + }); setVerifyEmailNumCheck(true); - alert("이메일 인증이 완료되었습니다."); } catch (error) { alert("인증번호가 올바르지 않습니다. 다시 확인해주세요."); } @@ -58,7 +54,7 @@ export const SubscriptionModalContent = () => { } const selectedCategories = Object.keys(categories).filter( - (key) => categories[key as keyof typeof categories] + (key) => categories[key as keyof typeof categories], ); if (selectedCategories.length === 0) { @@ -75,222 +71,155 @@ export const SubscriptionModalContent = () => { } }; - return (
-

- 구독할 카테고리를 선택하세요 -

- - {/* 구독 카테고리 토글 */} -
-
- setVerifyEmailNum(e.target.value)} - disabled={true} - /> +
+
+ setVerifyEmailNum(e.target.value)} + disabled={!verifySendCheck} + /> + +
+
+ emailWrapper.messageWrapper({ + verifyDescription: verifyDescription.type, + }) + } + > + {verifyDescription.message} + {isTimerRunning ? ( + 유효시간 {dateUtil.formatTime(remaingTime)} + ) : null} +
+
+ {/* 이메일 동의 및 구독 영역 */}
- +
+ {verifyCheckBox.map((item) => { + return ( +
handleChecked(item)} + > + {item.checked ? ( + unchecked icon + ) : ( + checked icon + )} + {item.label} +
+ ); + })} +
+
); @@ -223,8 +404,10 @@ const keywordWrapper = { keywordSelectContainer: css` display: inline-flex; column-gap: 8px; + row-gap: 8px; align-items: center; justify-content: flex-start; + flex-wrap: wrap; `, }; @@ -260,6 +443,47 @@ const emailWrapper = { flex-direction: row; column-gap: 12px; `, + certificationContainer: css` + display: flex; + flex-direction: column; + width: 100%; + row-gap: 8px; + + span { + font-size: 12px; + } + `, + certificationWrapper: css` + position: relative; + width: 100%; + display: flex; + align-items: center; + + > button { + position: absolute; + right: 0; + font-size: 15px; + color: var(--blue-50); + background-color: transparent; + border: none; + margin-right: 16px; + + &:disabled { + color: var(--text-disabled); + } + + &:focus { + outline: none; + } + } + `, + messageWrapper: ({ verifyDescription }: { verifyDescription: string }) => css` + display: flex; + flex-direction: column; + text-align: left; + ${verifyDescription === "error" && "color: var(--red-50);"} + ${verifyDescription === "success" && "color: var(--blue-50);"} + `, }; const footerWrapper = { @@ -271,4 +495,14 @@ const footerWrapper = { margin-top: 32px; row-gap: 12px; `, + checkbox__container: css` + display: flex; + column-gap: 24px; + cursor: pointer; + `, + checkbox__wrapper: css` + display: flex; + align-items: center; + color: var(--text-body1); + `, }; diff --git a/src/hooks/api/subscribe/useApiSendSubscribe.ts b/src/hooks/api/subscribe/useApiSendSubscribe.ts index 1e9de47..296fc63 100644 --- a/src/hooks/api/subscribe/useApiSendSubscribe.ts +++ b/src/hooks/api/subscribe/useApiSendSubscribe.ts @@ -2,12 +2,15 @@ import { api } from "@/api"; interface sendSubscribeEmail { email: string; + keywords: string[]; } -export const sendSubscribeEmail = ({ email }: sendSubscribeEmail) => { + +// TODO: 이메일 구독 요청 API 훅 구조로 변경하기 +export const sendSubscribeEmail = ({ email, keywords }: sendSubscribeEmail) => { const res = api.post(`/user-service/api/v1/subscribers/subscribe`, { - email: email, + email, + keywords, }); return res; }; - diff --git a/src/utils/dateUtil.ts b/src/utils/dateUtil.ts new file mode 100644 index 0000000..ff78a27 --- /dev/null +++ b/src/utils/dateUtil.ts @@ -0,0 +1,7 @@ +export default { + formatTime: (seconds: number): string => { + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + return `${minutes.toString().padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")}`; + }, +}; From d63a123c2e1976600828d97c3a06ef33072b0775 Mon Sep 17 00:00:00 2001 From: dori Date: Wed, 28 May 2025 23:15:11 +0900 Subject: [PATCH 4/5] Update src/app/signin/index.tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/app/signin/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/signin/index.tsx b/src/app/signin/index.tsx index 652f2d5..66578bc 100644 --- a/src/app/signin/index.tsx +++ b/src/app/signin/index.tsx @@ -85,7 +85,7 @@ export default function SignIn() { css={css` margin-top: 5.6rem; `} - disabled={isValidEmail && password.length > 5} + disabled={!(isValidEmail && password.length > 5)} onClick={handleSignIn} > 로그인 From b8cd748fb6d1ed433f99e225c915024deacb409a Mon Sep 17 00:00:00 2001 From: dori Date: Wed, 28 May 2025 23:15:31 +0900 Subject: [PATCH 5/5] Update src/app/signup/consent/index.tsx Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/app/signup/consent/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/signup/consent/index.tsx b/src/app/signup/consent/index.tsx index 04a9a1b..ed1d6d9 100644 --- a/src/app/signup/consent/index.tsx +++ b/src/app/signup/consent/index.tsx @@ -118,7 +118,7 @@ export default function Consent() { css={css` margin-top: 1.6rem; `} - disabled={privacy && useService} + disabled={!(privacy && useService)} onClick={() => handleNextClick()} > 다음