diff --git a/index.html b/index.html index 6a7807f4..918f4e90 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,5 @@ - + diff --git a/package.json b/package.json index 8439277e..6b231726 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "react-router-dom": "^6.21.1", "recoil": "^0.7.7", "styled-components": "^6.1.3", + "tailwind-scrollbar-hide": "^1.1.7", "uuid": "^9.0.1" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3163368e..1168c9f1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ dependencies: styled-components: specifier: ^6.1.3 version: 6.1.4(react-dom@18.2.0)(react@18.2.0) + tailwind-scrollbar-hide: + specifier: ^1.1.7 + version: 1.1.7 uuid: specifier: ^9.0.1 version: 9.0.1 @@ -4644,6 +4647,10 @@ packages: picocolors: 1.0.0 dev: false + /tailwind-scrollbar-hide@1.1.7: + resolution: {integrity: sha512-X324n9OtpTmOMqEgDUEA/RgLrNfBF/jwJdctaPZDzB3mppxJk7TLIDmOreEDm1Bq4R9LSPu4Epf8VSdovNU+iA==} + dev: false + /tailwindcss@3.4.0: resolution: {integrity: sha512-VigzymniH77knD1dryXbyxR+ePHihHociZbXnLZHUyzf2MMs2ZVqlUrZ3FvpXP8pno9JzmILt1sZPD19M3IxtA==} engines: {node: '>=14.0.0'} diff --git a/src/App.tsx b/src/App.tsx index bfa01682..26dc2596 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -16,7 +16,7 @@ const App = () => { - + diff --git a/src/components/Review/ReviewButton.tsx b/src/components/Review/ReviewButton.tsx index eaf9c5a4..ed11657a 100644 --- a/src/components/Review/ReviewButton.tsx +++ b/src/components/Review/ReviewButton.tsx @@ -1,5 +1,5 @@ -import { ButtonPrimary } from '@components/common/button/Button'; +import { Button } from '@components/common'; export default function ReviewButton() { - return {}}>완료; + return ; } diff --git a/src/components/common/back/Back.tsx b/src/components/common/back/Back.tsx new file mode 100644 index 00000000..65b1a6e0 --- /dev/null +++ b/src/components/common/back/Back.tsx @@ -0,0 +1,14 @@ +import { useNavigate, useNavigation } from 'react-router-dom'; +import { LeftIcon } from '../icons/Icons'; + +const Back = () => { + const navigate = useNavigate(); + + return ( +
navigate(-1)}> + +
+ ); +}; + +export default Back; diff --git a/src/components/common/button/Button.tsx b/src/components/common/button/Button.tsx index b19d31df..feb8a1ec 100644 --- a/src/components/common/button/Button.tsx +++ b/src/components/common/button/Button.tsx @@ -1,26 +1,33 @@ import { ReactNode } from 'react'; + interface ButtonProps { - onClick: () => void; + isActive?: boolean; + type?: 'submit' | 'button' | 'reset' | undefined; + onClick?: VoidFunction; children: ReactNode; - className?: string; + outline?: boolean; } -export const ButtonWhite: React.FC = ({ onClick, children }) => { +const Button = ({ + isActive = true, + type = 'submit', + onClick, + children, + outline = false, +}: ButtonProps) => { return ( ); }; -export const ButtonPrimary: React.FC = ({ onClick, children }) => { - return ( - - ); -}; +export default Button; diff --git a/src/components/common/icons/Icons.tsx b/src/components/common/icons/Icons.tsx index adf48bb2..c70960f0 100644 --- a/src/components/common/icons/Icons.tsx +++ b/src/components/common/icons/Icons.tsx @@ -711,3 +711,58 @@ export const CameraIcon: React.FC = ({ ); }; + +interface LogoProps { + width?: string; + height?: string; + fill?: string; +} + +export const LogoIcon: React.FC = ({ + width = '71', + height = '22', + fill = '#29DDF6', +}) => { + return ( + + + + + + + + ); +}; + +export const KakaoIcon = () => { + return ( + + + + ); +}; diff --git a/src/components/common/index.ts b/src/components/common/index.ts new file mode 100644 index 00000000..21990fa9 --- /dev/null +++ b/src/components/common/index.ts @@ -0,0 +1,6 @@ +import Back from './back/Back'; +import Button from './button/Button'; +import { Nav } from './nav'; +import { Header } from './header'; + +export { Back, Button, Nav, Header }; diff --git a/src/components/user/index.ts b/src/components/user/index.ts new file mode 100644 index 00000000..7744986a --- /dev/null +++ b/src/components/user/index.ts @@ -0,0 +1,5 @@ +import UserInputBox from './useInputBox/UserInputBox'; +import UserEmailInputBox from './useInputBox/UserEmailInputBox'; +import UserPwInputBox from './useInputBox/UserPwInputBox'; + +export { UserInputBox, UserEmailInputBox, UserPwInputBox }; diff --git a/src/components/user/useInputBox/UserEmailInputBox.tsx b/src/components/user/useInputBox/UserEmailInputBox.tsx new file mode 100644 index 00000000..bf2eb366 --- /dev/null +++ b/src/components/user/useInputBox/UserEmailInputBox.tsx @@ -0,0 +1,66 @@ +import { CloseIcon } from '@components/common/icons/Icons'; +import { useState } from 'react'; + +const UserEmailInputBox = () => { + const [inputValue, setInputValue] = useState(''); + + const [isEmailValidated, setIsEmailValidated] = useState(true); + const [isEmailDuplicate, setIsEmailDuplicate] = useState(false); + + const onInputChange = (e: React.ChangeEvent) => { + setInputValue(e.target.value); + + if (e.target.value !== '') { + setIsEmailValidated( + /^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-za-z0-9\-]+/.test(inputValue), + ); + } else { + setIsEmailDuplicate(false); + } + }; + + const onInputBlur = () => { + if (isEmailValidated) { + console.log('이메일 중복 확인 요청'); + // setIsEmailDuplicate(true); + } + }; + + return ( +
+
+ +
+ + {inputValue && ( + + )} +
+
+ + {!isEmailValidated + ? '이메일 형식이 올바르지 않습니다.' + : isEmailDuplicate && '사용 중인 이메일입니다.'} + +
+ ); +}; + +export default UserEmailInputBox; diff --git a/src/components/user/useInputBox/UserInputBox.tsx b/src/components/user/useInputBox/UserInputBox.tsx new file mode 100644 index 00000000..318f41bd --- /dev/null +++ b/src/components/user/useInputBox/UserInputBox.tsx @@ -0,0 +1,72 @@ +import { useState } from 'react'; +import { CloseIcon } from '@components/common/icons/Icons'; + +interface Props { + label: string; + type?: string; + placeholder: string; + // children: React.ReactNode; + validifyCheckList?: string[]; + // onInputBlur: VoidFunction; + marginB?: string; +} + +const UserInputBox = ({ + label, + type = 'password', + placeholder, + // validifyCheckList, + marginB = 'mb-6', +}: Props) => { + const [inputValue, setInputValue] = useState(''); + + const onInputChange = (e: React.ChangeEvent) => { + setInputValue(e.target.value); + }; + + return ( +
+
+ +
+ + {inputValue && ( + + )} +
+
+ + {/* {type === 'email' && } + + {validifyCheckList && ( +
+ {validifyCheckList.map((validifyCheckItem) => ( + + ))} +
+ )} */} +
+ ); +}; + +export default UserInputBox; diff --git a/src/components/user/useInputBox/UserPwInputBox.tsx b/src/components/user/useInputBox/UserPwInputBox.tsx new file mode 100644 index 00000000..1480266b --- /dev/null +++ b/src/components/user/useInputBox/UserPwInputBox.tsx @@ -0,0 +1,100 @@ +import { useState } from 'react'; +import ValidifyCheck from './ValidifyCheck'; +import { CloseIcon } from '@components/common/icons/Icons'; + +const UserPwInputBox = () => { + const [inputPwValue, setInputPwValue] = useState(''); + + const onPwInputChange = (e: React.ChangeEvent) => { + setInputPwValue(e.target.value); + }; + + const [inputPwCheckValue, setInputPwCheckValue] = useState(''); + + const onPwCheckInputChange = (e: React.ChangeEvent) => { + setInputPwCheckValue(e.target.value); + }; + + return ( + <> +
+
+ +
+ + {inputPwValue && ( + + )} +
+
+
+ {['checkPwEng', 'checkPwNum', 'checkPwLength'].map( + (validifyCheckItem) => ( + + ), + )} +
+
+ +
+
+ +
+ + {inputPwCheckValue && ( + + )} +
+
+
+ {['checkPwMatch'].map((validifyCheckItem) => ( + + ))} +
+
+ + ); +}; + +export default UserPwInputBox; diff --git a/src/components/user/useInputBox/ValidifyCheck.tsx b/src/components/user/useInputBox/ValidifyCheck.tsx new file mode 100644 index 00000000..08957916 --- /dev/null +++ b/src/components/user/useInputBox/ValidifyCheck.tsx @@ -0,0 +1,39 @@ +import { CheckIcon } from '@components/common/icons/Icons'; +import validationList from '@utils/user/validationList'; +import { useEffect, useState } from 'react'; + +interface Props { + checkId: string; + inputValue: string; + inputValueCheck?: string; +} + +const ValidifyCheck = ({ checkId, inputValue, inputValueCheck }: Props) => { + const [isValidated, setIsValidated] = useState(false); + + const matchedValidation = validationList.find( + (validationItem) => validationItem.id === checkId, + ); + + useEffect(() => { + matchedValidation + ? checkId !== 'checkPwMatch' + ? setIsValidated(matchedValidation.func(inputValue, '')) + : inputValue !== '' && + setIsValidated( + matchedValidation.func(inputValue, inputValueCheck || ''), + ) + : setIsValidated(false); + }, [inputValue, inputValueCheck]); + + return ( +
+ + {matchedValidation?.value} + + +
+ ); +}; + +export default ValidifyCheck; diff --git a/src/index.css b/src/index.css index 22095935..b480640d 100644 --- a/src/index.css +++ b/src/index.css @@ -39,7 +39,7 @@ code { @layer components { .btn-base { - @apply w-full rounded-lg py-3.5 transition; + @apply w-full rounded-lg transition; } .title1, diff --git a/src/pages/index.ts b/src/pages/index.ts new file mode 100644 index 00000000..cb5c5caf --- /dev/null +++ b/src/pages/index.ts @@ -0,0 +1,4 @@ +import Main from './main/main.page'; +import Signup from './signup/signup.page'; + +export { Main, Signup }; diff --git a/src/pages/main/main.page.tsx b/src/pages/main/main.page.tsx index 55ea0926..605b8e9b 100644 --- a/src/pages/main/main.page.tsx +++ b/src/pages/main/main.page.tsx @@ -1,12 +1,5 @@ -import ToursSectionTop from '@components/Tours/ToursSectionTop'; - const Main = () => { - return ( - <> -
dev 브랜치 test
- - - ); + return <>; }; export default Main; diff --git a/src/pages/signin/signin.page.tsx b/src/pages/signin/signin.page.tsx new file mode 100644 index 00000000..1ee7cb75 --- /dev/null +++ b/src/pages/signin/signin.page.tsx @@ -0,0 +1,48 @@ +import { Button } from '@components/common'; +import Back from '@components/common/back/Back'; +import { KakaoIcon, LogoIcon } from '@components/common/icons/Icons'; +import { UserInputBox } from '@components/user'; + +const Signin = () => { + return ( +
+ +
+
+
+ +
+

+ 위플랜플랜즈에 오신 것을 환영합니다. +

+
+
+ + + + +
+ +
+
+
+ 또는 +
+
+ + + +
+
+ ); +}; + +export default Signin; diff --git a/src/pages/signup/SignupSurvey.tsx b/src/pages/signup/SignupSurvey.tsx new file mode 100644 index 00000000..21549d72 --- /dev/null +++ b/src/pages/signup/SignupSurvey.tsx @@ -0,0 +1,66 @@ +import { useState } from 'react'; +import { Button } from '../../components/common'; +import { SignupSurveyList } from './SignupSurveyList'; +import { SignupSurveyOption } from './SignupSurveyOption'; + +const SignupSurvey = () => { + const [selectedOptions, setSelectedOptions] = useState<(number | null)[]>( + SignupSurveyList.map(() => null), + ); + + console.log(selectedOptions); + + const handleOptionClick = (listIndex: number, optionIndex: number) => { + setSelectedOptions( + selectedOptions.map((selected, index) => { + if (index === listIndex) { + return optionIndex; + } + return selected; + }), + ); + }; + + return ( +
+
+

+ 어떤 여행을 좋아하세요? +

+

+ 여행 취향을 골라주세요. +

+
+
+ {SignupSurveyList.map((list, listIndex) => ( +
+
+

+ {list.title} +

+
+
+ {list.options.map((option, optionIndex) => ( + + ))} +
+
+ ))} +
+ +
+ ); +}; + +export default SignupSurvey; diff --git a/src/pages/signup/SignupSurveyList.ts b/src/pages/signup/SignupSurveyList.ts new file mode 100644 index 00000000..be829403 --- /dev/null +++ b/src/pages/signup/SignupSurveyList.ts @@ -0,0 +1,22 @@ +export const SignupSurveyList = [ + { + title: '계획성', + options: [{ text: '철저하게' }, { text: '여유롭게' }], + }, + { + title: '활동성', + options: [{ text: '아침형' }, { text: '저녁형' }], + }, + { + title: '숙소', + options: [{ text: '분위기' }, { text: '가격' }], + }, + { + title: '음식', + options: [{ text: '노포 맛집' }, { text: '인테리어' }], + }, + { + title: '관광지', + options: [{ text: '액티비티' }, { text: '휴양' }], + }, +]; diff --git a/src/pages/signup/SignupSurveyOption.tsx b/src/pages/signup/SignupSurveyOption.tsx new file mode 100644 index 00000000..b11f8aba --- /dev/null +++ b/src/pages/signup/SignupSurveyOption.tsx @@ -0,0 +1,17 @@ +import { memo } from 'react'; + +export const SignupSurveyOption = memo<{ text: string; active: boolean }>( + ({ text, active }) => ( +
+

+ {text} +

+
+ ), +); diff --git a/src/pages/signup/signup.page.tsx b/src/pages/signup/signup.page.tsx new file mode 100644 index 00000000..c32e1eba --- /dev/null +++ b/src/pages/signup/signup.page.tsx @@ -0,0 +1,32 @@ +import { UserEmailInputBox, UserPwInputBox } from '@components/user'; +import { useState } from 'react'; + +const Signup = () => { + const [isActive, setIsActive] = useState(false); + + return ( +
+

+ 위플플 이용을 위해 +
+ 회원가입을 해주세요 +

+
+ + + + {/* TODO 서지수 | 모든 조건이 만족되어야지만 활성화되도록 수정 */} +
+ +
+ +
+ ); +}; + +export default Signup; diff --git a/src/router/mainRouter.tsx b/src/router/mainRouter.tsx index 714836f3..489c51c8 100644 --- a/src/router/mainRouter.tsx +++ b/src/router/mainRouter.tsx @@ -3,16 +3,26 @@ import { Header } from '@components/common/header'; import { Nav } from '@components/common/nav'; import Main from '@pages/main/main.page'; import Detail from '@pages/detail/detail.page'; +import { Signup } from '@pages/index'; +import SignupSurvey from '@pages/signup/SignupSurvey'; import PostingReview from '@pages/postingReview/postingReview.page'; +import { useLocation } from 'react-router-dom'; +import Signin from '@pages/signin/signin.page'; export function MainLayout() { + const location = useLocation(); + const hideNavPaths = ['/signup', '/signin']; + const showNav = !hideNavPaths.some((path) => + location.pathname.includes(path), + ); + return (
-
+
-
); } @@ -25,6 +35,9 @@ const MainRouter = () => { } /> } /> } /> + } /> + } /> + } /> diff --git a/src/utils/user/validationList.ts b/src/utils/user/validationList.ts new file mode 100644 index 00000000..a796b206 --- /dev/null +++ b/src/utils/user/validationList.ts @@ -0,0 +1,38 @@ +const validationList = [ + // { + // id: 'checkEmailValidity', + // value: '이메일 유효성 검사', + // func: (value: string) => + // /^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-za-z0-9\-]+/.test(value), + // }, + // { + // id: 'checkEmailDuplicate', + // value: '이메일 중복 확인', + // func: () => { + // validationList[0].func; + // return false; + // }, + // }, + { + id: 'checkPwEng', + value: '영문포함', + func: (value: string) => /[a-zA-Z]/.test(value), + }, + { + id: 'checkPwNum', + value: '숫자포함', + func: (value: string) => /[0-9]/.test(value), + }, + { + id: 'checkPwLength', + value: '8~20자 이내', + func: (value: string) => value.length >= 8 && value.length <= 20, + }, + { + id: 'checkPwMatch', + value: '비밀번호 일치', + func: (value: string, checkValue: string) => value === checkValue, + }, +]; + +export default validationList; diff --git a/tailwind.config.js b/tailwind.config.js index 52c00582..abec5259 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -19,5 +19,5 @@ export default { }, }, }, - plugins: [], + plugins: [require('tailwind-scrollbar-hide')], }; diff --git a/tsconfig.json b/tsconfig.json index a1751e9c..644efc3d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -33,6 +33,11 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src", "custom.d.ts", "svg.d.ts"], + "include": [ + "src", + "custom.d.ts", + "svg.d.ts", + "src/pages/signup/SignupSurvey.tsx" + ], "references": [{ "path": "./tsconfig.node.json" }] }