diff --git a/src/components/GenderSelectButton/index.tsx b/src/components/GenderSelectButton/index.tsx index 0a388c9..e7e658a 100644 --- a/src/components/GenderSelectButton/index.tsx +++ b/src/components/GenderSelectButton/index.tsx @@ -11,8 +11,8 @@ interface GenderSelectButtonProps { export default function GenderSelectButton({ gender, isActive, onClick }: GenderSelectButtonProps) { return ( - - + + {gender === 'male' ? '남' : '여'} ) diff --git a/src/components/GenderSelectButton/styles.ts b/src/components/GenderSelectButton/styles.ts index 836203f..c550326 100644 --- a/src/components/GenderSelectButton/styles.ts +++ b/src/components/GenderSelectButton/styles.ts @@ -1,10 +1,10 @@ import { styled } from 'styled-components' -export const GenderBtn = styled.button<{ isActive: boolean }>` - border: solid 2px ${({ isActive, theme }) => (isActive ? theme.colors.brand.darken : theme.colors.grayscale.gc_1)}; +export const GenderBtn = styled.button<{ $isActive: boolean }>` + border: solid 2px ${({ $isActive, theme }) => ($isActive ? theme.colors.brand.darken : theme.colors.grayscale.gc_1)}; border-radius: 8px; width: auto; height: 102px; - color: ${({ isActive, theme }) => (isActive ? theme.colors.brand.darken : theme.colors.grayscale.font_3)}; + color: ${({ $isActive, theme }) => ($isActive ? theme.colors.brand.darken : theme.colors.grayscale.font_3)}; display: flex; flex-direction: column; @@ -13,9 +13,9 @@ export const GenderBtn = styled.button<{ isActive: boolean }>` gap: 0.2rem; ` -export const GenderIcon = styled.img<{ isActive: boolean }>` +export const GenderIcon = styled.img<{ $isActive: boolean }>` filter: ${props => - props.isActive + props.$isActive ? 'invert(12%) sepia(70%) saturate(924%) hue-rotate(351deg) brightness(96%) contrast(97%)' : 'invert(31%) sepia(4%) saturate(61%) hue-rotate(332deg) brightness(99%) contrast(97%)'}; ` diff --git a/src/components/Toast/index.tsx b/src/components/Toast/index.tsx index 4496dd4..f94c498 100644 --- a/src/components/Toast/index.tsx +++ b/src/components/Toast/index.tsx @@ -1,4 +1,3 @@ -// components/Toast/index.tsx import * as S from './styles' import { useEffect } from 'react' import { useToastStore } from '~/stores/toastStore' @@ -16,7 +15,7 @@ export default function Toast() { }, [isVisible]) return ( - + {content} ) diff --git a/src/components/Toast/styles.ts b/src/components/Toast/styles.ts index 3a4f825..ba26058 100644 --- a/src/components/Toast/styles.ts +++ b/src/components/Toast/styles.ts @@ -1,6 +1,6 @@ import { styled } from 'styled-components' -export const ToastWrapper = styled.div<{ isVisible: boolean }>` +export const ToastWrapper = styled.div<{ $isVisible: boolean }>` position: absolute; top: -50px; @@ -8,8 +8,8 @@ export const ToastWrapper = styled.div<{ isVisible: boolean }>` display: flex; justify-content: center; - visibility: ${({ isVisible }) => (isVisible ? 'visiblie' : 'hidden')}; - opacity: ${({ isVisible }) => (isVisible ? 1 : 0)}; + visibility: ${({ $isVisible }) => ($isVisible ? 'visiblie' : 'hidden')}; + opacity: ${({ $isVisible }) => ($isVisible ? 1 : 0)}; transition: opacity 0.3s ease, visibility 0.3s ease; diff --git a/src/data/breeds.json b/src/data/breeds.json new file mode 100644 index 0000000..c43ab32 --- /dev/null +++ b/src/data/breeds.json @@ -0,0 +1,119 @@ +{ + "breeds": [ + "래브라도 리트리버", + "골든 리트리버", + "저먼 셰퍼드", + "도베르만", + "그레이트 데인", + "버니즈 마운틴 독", + "뉴펀들랜드", + "로트와일러", + "세인트 버나드", + "아이리시 울프하운드", + "잉글리시 마스티프", + "그레이트 피레니즈", + "시베리안 허스키", + "알래스칸 맬러뮤트", + "보더 콜리", + "비글", + "불독", + "차우차우", + "달마시안", + "사모예드", + "시바견", + "웰시코기", + "진도견", + "아키타", + "바셋하운드", + "브리타니", + "콜리", + "잉글리시 세터", + "잉글리시 스프링거 스패니얼", + "벨기에 말리노이즈", + "포인터", + "에어데일 테리어", + "휘펫", + "불 테리어", + "스탠더드 푸들", + "아메리칸 에스키모", + "보더 테리어", + "웨스트하이랜드 화이트 테리어", + "프렌치 불독", + "포메라니안", + "치와와", + "요크셔테리어", + "미니어처 슈나우저", + "미니어처 푸들", + "토이 푸들", + "말티즈", + "시츄", + "닥스훈트", + "비숑 프리제", + "파피용", + "퍼그", + "페키니즈", + "잭 러셀 테리어", + "미니어처 핀셔", + "캐벌리어 킹 찰스 스패니얼", + "보스턴 테리어", + "이탈리안 그레이하운드", + "스코티시 테리어", + "실키 테리어", + "케언 테리어", + "노리치 테리어", + "아프간 하운드", + "살루키", + "바센지", + "차이니즈 샤페이", + "아메리칸 코카 스패니얼", + "잉글리시 코카 스패니얼", + "클럼버 스패니얼", + "필드 스패니얼", + "저먼 와이어헤어드 포인터", + "체서피크 베이 리트리버", + "컬리코티드 리트리버", + "플랫코티드 리트리버", + "아이리시 세터", + "고든 세터", + "올드 잉글리시 시프도그", + "셔틀랜드 시프도그", + "벨기안 시프도그", + "오스트레일리안 캐틀 독", + "핀란드 스피츠", + "케이스혼드", + "티베탄 마스티프", + "불마스티프", + "네아폴리탄 마스티프", + "블러드하운드", + "그레이하운드", + "노르웨이언 엘크하운드", + "아이리시 워터 스패니얼", + "웨일즈 스프링거 스패니얼", + "스탠더드 슈나우저", + "자이언트 슈나우저", + "스코티시 디어하운드", + "맨체스터 테리어", + "노퍽 테리어", + "래빗 닥스훈트", + "롱헤어드 닥스훈트", + "아메리칸 불리", + "버니두들", + "골든두들", + "래브라두들", + "포르투갈 워터 독", + "오스트레일리안 셰퍼드", + "벨기에 터뷰런", + "블랙 러시안 테리어", + "불 마스티프", + "체서피크 베이 리트리버", + "샤페이", + "클럼버 스패니얼", + "컬리 코티드 리트리버", + "댄디 딘몬트 테리어", + "잉글리시 폭스하운드", + "필드 스패니얼", + "핀란드 라프훈드", + "자이언트 슈나우저", + "아이비전 하운드" + ] +} diff --git a/src/modals/DatePickerModal/index.tsx b/src/modals/DatePickerModal/index.tsx index 56b626f..a0ea97d 100644 --- a/src/modals/DatePickerModal/index.tsx +++ b/src/modals/DatePickerModal/index.tsx @@ -40,7 +40,7 @@ export default function DatePickerModal({ date, setDate }: DatePickerModalProps) return ( - + 확인 ` +export const DatePickerModal = styled.div<{ $isExiting: boolean }>` background-color: white; width: 100%; - animation: ${({ isExiting }) => (isExiting ? slideDown : slideUp)} 0.3s ease-out; + animation: ${({ $isExiting }) => ($isExiting ? slideDown : slideUp)} 0.3s ease-out; > div { padding: 1rem; diff --git a/src/modals/RegisterDogModal/CheckDogProfileSection/index.tsx b/src/modals/RegisterDogModal/CheckDogProfileSection/index.tsx index d060017..5451e0b 100644 --- a/src/modals/RegisterDogModal/CheckDogProfileSection/index.tsx +++ b/src/modals/RegisterDogModal/CheckDogProfileSection/index.tsx @@ -4,14 +4,15 @@ import Header from '~components/Header/index' import { Typo24 } from '~components/Typo/index' import { Profile } from '~components/Profile' import Tag from '~components/Tag' +import { useModalStore } from '~stores/modalStore' export default function CheckDogProfileSection() { - const handleClickPrev = () => {} + const { popModal } = useModalStore() return ( <> -
+
diff --git a/src/modals/RegisterDogModal/CheckDogProfileSection/styles.ts b/src/modals/RegisterDogModal/CheckDogProfileSection/styles.ts index 248cf84..920d7c6 100644 --- a/src/modals/RegisterDogModal/CheckDogProfileSection/styles.ts +++ b/src/modals/RegisterDogModal/CheckDogProfileSection/styles.ts @@ -1,9 +1,14 @@ import { styled } from 'styled-components' export const CheckDogProfileSection = styled.div` + z-index: 200; padding: 180px 20px 24px 20px; background-color: ${({ theme }) => theme.colors.grayscale.gc_4}; - height: 100dvh; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; display: flex; flex-direction: column; diff --git a/src/modals/RegisterDogModal/DogProfileDetailSection/index.tsx b/src/modals/RegisterDogModal/DogProfileDetailSection/index.tsx index dbe2981..473f9b1 100644 --- a/src/modals/RegisterDogModal/DogProfileDetailSection/index.tsx +++ b/src/modals/RegisterDogModal/DogProfileDetailSection/index.tsx @@ -5,23 +5,29 @@ import GenderSelectButton from '~components/GenderSelectButton' import { Typo24 } from '~components/Typo/index' import Check from '~assets/check.svg' import Header from '~components/Header/index' +import SearchModal from '~modals/SearchModal' +import { useModalStore } from '~stores/modalStore' +import { useDogProfileStore } from '~/stores/dogProfileStore' +import { validateDogDetailProfile } from '~utils/validateDogProfile' +import { useToastStore } from '~stores/toastStore' +import Toast from '~components/Toast' export default function DogProfileDetailSection() { - const [isNeutered, setIsNeutered] = useState(false) - const [selectedGender, setSelectedGender] = useState<'male' | 'female' | null>(null) - const [weight, setWeight] = useState('') + const { dogProfile, setDogProfile } = useDogProfileStore() + const { pushModal, popModal } = useModalStore() + const { showToast } = useToastStore() const [displayValue, setDisplayValue] = useState('') const [inputType, setInputType] = useState('text') const handleGenderSelect = (gender: 'male' | 'female') => { - setSelectedGender(gender) + setDogProfile({ gender }) } const onChangeWeightInput = (e: React.ChangeEvent) => { const value = e.target.value if (value === '') { - setWeight('') + setDogProfile({ weight: '' }) setDisplayValue('') return } @@ -29,29 +35,36 @@ export default function DogProfileDetailSection() { if (/^\d*\.?\d*$/.test(value)) { const formatted = value.includes('.') ? value.match(/^\d*\.?\d{0,2}/)![0] : value - setWeight(formatted) + setDogProfile({ weight: formatted }) setDisplayValue(inputType === 'number' ? formatted : `${formatted}kg`) } } const handleFocus = () => { setInputType('number') - setDisplayValue(weight) + setDisplayValue(dogProfile.weight) } const handleBlur = () => { setInputType('text') - if (weight) { - setDisplayValue(`${weight}kg`) + if (dogProfile.weight) { + setDisplayValue(`${dogProfile.weight}kg`) } } - const handleClickPrev = () => {} + const handleComfirmClick = () => { + const alertMessage = validateDogDetailProfile(dogProfile) + if (alertMessage) { + showToast(alertMessage) + return + } + console.log('이제 백엔드로 전송') + } return ( <> -
+
반려견 상세 정보를 @@ -62,34 +75,45 @@ export default function DogProfileDetailSection() { handleGenderSelect('male')} /> handleGenderSelect('female')} /> - setIsNeutered(!isNeutered)}> - - {isNeutered && check} + setDogProfile({ isNeutered: !dogProfile.isNeutered })}> + + {dogProfile.isNeutered && check} - 중성화 했어요 + 중성화 했어요 - 견종 입력 + pushModal()} $hasBreed={!!dogProfile.breed}> + {dogProfile.breed || '견종 입력'} + - 확인 + + + 확인 + + + ) diff --git a/src/modals/RegisterDogModal/DogProfileDetailSection/styles.ts b/src/modals/RegisterDogModal/DogProfileDetailSection/styles.ts index 6d31b00..3f0620a 100644 --- a/src/modals/RegisterDogModal/DogProfileDetailSection/styles.ts +++ b/src/modals/RegisterDogModal/DogProfileDetailSection/styles.ts @@ -1,9 +1,14 @@ import { styled } from 'styled-components' export const DogProfileDetailSection = styled.div` + z-index: 300; padding: 120px 20px 24px 20px; background-color: ${({ theme }) => theme.colors.grayscale.gc_4}; - height: 100dvh; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; display: flex; flex-direction: column; @@ -43,10 +48,10 @@ export const CheckboxWrapper = styled.div` gap: 0.5rem; ` -export const CheckboxCircle = styled.div<{ isChecked: boolean }>` +export const CheckboxCircle = styled.div<{ $isChecked: boolean }>` width: 24px; height: 24px; - border: 2px solid ${({ isChecked }) => (isChecked ? '#000' : '#ccc')}; + border: 2px solid ${({ $isChecked }) => ($isChecked ? '#000' : '#ccc')}; border-radius: 50%; display: flex; @@ -55,8 +60,8 @@ export const CheckboxCircle = styled.div<{ isChecked: boolean }>` cursor: pointer; ` -export const CheckboxLabel = styled.span<{ isChecked: boolean }>` - color: ${({ isChecked }) => (isChecked ? '#000' : '#ccc')}; +export const CheckboxLabel = styled.span<{ $isChecked: boolean }>` + color: ${({ $isChecked }) => ($isChecked ? '#000' : '#ccc')}; ` export const InputArea = styled.div` @@ -69,23 +74,25 @@ export const InputArea = styled.div` } ` -export const PickerBtn = styled.div` +export const PickerBtn = styled.div<{ $hasBreed: boolean }>` width: 100%; border: none; text-align: center; padding: 17px 32px; font-size: ${({ theme }) => theme.typography._20}; - color: ${({ theme }) => theme.colors.grayscale.font_3}; + color: ${({ theme, $hasBreed }) => ($hasBreed ? 'black' : theme.colors.grayscale.font_3)}; + font-weight: ${({ $hasBreed }) => ($hasBreed ? 'bold' : 'default')}; cursor: pointer; ` -export const WeightInput = styled.input` +export const WeightInput = styled.input<{ $hasWeight: boolean }>` width: 100%; border: none; text-align: center; padding: 17px 32px; border-radius: 12px; font-size: ${({ theme }) => theme.typography._20}; + font-weight: ${({ $hasWeight }) => ($hasWeight ? 'bold' : 'default')}; &:focus { box-shadow: ${({ theme }) => `inset 0 0 0 1px ${theme.colors.grayscale.font_1}`}; } @@ -100,3 +107,7 @@ export const WeightInput = styled.input` -moz-appearance: textfield; } ` + +export const ToastWrapper = styled.div` + position: relative; +` diff --git a/src/modals/RegisterDogModal/DogProfileSection/DogImageUploader.tsx b/src/modals/RegisterDogModal/DogProfileSection/DogImageUploader.tsx index 77260f4..d7a8db1 100644 --- a/src/modals/RegisterDogModal/DogProfileSection/DogImageUploader.tsx +++ b/src/modals/RegisterDogModal/DogProfileSection/DogImageUploader.tsx @@ -27,7 +27,7 @@ export default function DogImageUploader({ image, setImage }: DogImageUploaderPr 반려견 사진 추가
반려견 사진 추가
- {image && } + {image && } ) diff --git a/src/modals/RegisterDogModal/DogProfileSection/index.tsx b/src/modals/RegisterDogModal/DogProfileSection/index.tsx index f6a465c..c88a660 100644 --- a/src/modals/RegisterDogModal/DogProfileSection/index.tsx +++ b/src/modals/RegisterDogModal/DogProfileSection/index.tsx @@ -1,5 +1,4 @@ import * as S from './styles' -import { useState } from 'react' import { ActionButton } from '~components/Button/ActionButton' import TwoLineInput from '~components/Input/TwoLineInput' import Header from '~components/Header/index' @@ -11,29 +10,15 @@ import { validateDogProfile } from '~utils/validateDogProfile' import DogProfileDetailSection from '../DogProfileDetailSection' import Toast from '~components/Toast' import { useToastStore } from '~/stores/toastStore' - -interface DogProfileType { - name: string - image: string | undefined - birth: string - intro: string -} +import { useDogProfileStore } from '~/stores/dogProfileStore' export default function DogProfileSection() { - const [dogProfile, setDogProfile] = useState({ - name: '', - image: undefined, - birth: '', - intro: '', - }) - const { popModal, pushModal } = useModalStore() const { showToast } = useToastStore() + const { dogProfile, setDogProfile } = useDogProfileStore() const handleDatePickerOpen = () => { - pushModal( - setDogProfile(prev => ({ ...prev, birth: date }))} /> - ) + pushModal( setDogProfile({ birth: date })} />) } const handleNextClick = () => { @@ -45,30 +30,44 @@ export default function DogProfileSection() { pushModal() } + const handlePrevClick = () => { + setDogProfile({ + name: '', + image: undefined, + birth: '', + intro: '', + gender: null, + isNeutered: false, + breed: '', + weight: '', + }) + popModal() + } + return ( <> -
+
반려견 기본 정보를
알려주세요!
- setDogProfile(prev => ({ ...prev, image }))} /> + setDogProfile({ image })} /> setDogProfile(prev => ({ ...prev, name: e.target.value }))} + onChange={e => setDogProfile({ name: e.target.value })} /> - + {dogProfile.birth || '생년월일 선택'} setDogProfile(prev => ({ ...prev, intro: e.target.value }))} + onChange={e => setDogProfile({ intro: e.target.value })} > 한줄 소개 입력 diff --git a/src/modals/RegisterDogModal/DogProfileSection/styles.ts b/src/modals/RegisterDogModal/DogProfileSection/styles.ts index 34b5424..92983e3 100644 --- a/src/modals/RegisterDogModal/DogProfileSection/styles.ts +++ b/src/modals/RegisterDogModal/DogProfileSection/styles.ts @@ -1,9 +1,14 @@ import { styled } from 'styled-components' export const DogProfileSection = styled.div` + z-index: 100; padding: 120px 20px 24px 20px; background-color: ${({ theme }) => theme.colors.grayscale.gc_4}; - height: 100dvh; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; display: flex; flex-direction: column; @@ -54,9 +59,9 @@ export const HiddenFileInput = styled.input` display: none; ` -export const DogImage = styled.img<{ hasImage: boolean }>` +export const DogImage = styled.img<{ $hasImage: boolean }>` position: absolute; - z-index: ${({ hasImage }) => (hasImage ? '100' : '-100')}; + z-index: ${({ $hasImage }) => ($hasImage ? '100' : '-100')}; top: 0; left: 0; width: 100%; @@ -91,14 +96,14 @@ export const NameInput = styled.input` } ` -export const DatePickerBtn = styled.div<{ hasBirth: boolean }>` +export const DatePickerBtn = styled.div<{ $hasBirth: boolean }>` width: 100%; border: none; text-align: center; padding: 17px 32px; font-size: ${({ theme }) => theme.typography._20}; - color: ${({ theme, hasBirth }) => (hasBirth ? 'black' : theme.colors.grayscale.font_3)}; - font-weight: ${({ hasBirth }) => (hasBirth ? '700' : '400')}; + color: ${({ theme, $hasBirth }) => ($hasBirth ? 'black' : theme.colors.grayscale.font_3)}; + font-weight: ${({ $hasBirth }) => ($hasBirth ? '700' : '400')}; cursor: pointer; ` export const ActionButtonArea = styled.div` diff --git a/src/modals/RegisterDogModal/FamilyCodeSection/index.tsx b/src/modals/RegisterDogModal/FamilyCodeSection/index.tsx index 14ad870..0646c8e 100644 --- a/src/modals/RegisterDogModal/FamilyCodeSection/index.tsx +++ b/src/modals/RegisterDogModal/FamilyCodeSection/index.tsx @@ -1,15 +1,32 @@ import * as S from './styles' +import { useState } from 'react' import { ActionButton } from '~components/Button/ActionButton' import PrevButton from '~components/Button/PrevButton' import { Typo24 } from '~components/Typo/index' import Header from '~components/Header/index' +import { useModalStore } from '~stores/modalStore' +import CheckDogProfileSection from '../CheckDogProfileSection' +import { useToastStore } from '~stores/toastStore' +import { validateFamilyCode } from '~utils/validateDogProfile' +import Toast from '~components/Toast' export default function FamilyCodeSection() { - const handleClickPrev = () => {} + const { pushModal, popModal } = useModalStore() + const [familyCode, setFamilyCode] = useState('') + const { showToast } = useToastStore() + + const handleClickNext = () => { + const alertMessage = validateFamilyCode(familyCode) + if (alertMessage) { + showToast(alertMessage) + return + } + pushModal() + } return ( <> -
+
@@ -21,9 +38,14 @@ export default function FamilyCodeSection() { 코드를 입력해 주세요. - + setFamilyCode(e.target.value)} /> - 다음 + + + 다음 + + + ) diff --git a/src/modals/RegisterDogModal/FamilyCodeSection/styles.ts b/src/modals/RegisterDogModal/FamilyCodeSection/styles.ts index e56d421..c064e44 100644 --- a/src/modals/RegisterDogModal/FamilyCodeSection/styles.ts +++ b/src/modals/RegisterDogModal/FamilyCodeSection/styles.ts @@ -1,9 +1,14 @@ import { styled } from 'styled-components' export const FamilyCodeSection = styled.div` + z-index: 100; padding: 0 20px 24px 20px; background-color: ${({ theme }) => theme.colors.grayscale.gc_4}; - height: 100dvh; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; display: flex; flex-direction: column; @@ -46,3 +51,7 @@ export const FamilyCodeInput = styled.input` font-weight: 400; } ` + +export const ToastWrapper = styled.div` + position: relative; +` diff --git a/src/modals/SearchModal/index.tsx b/src/modals/SearchModal/index.tsx new file mode 100644 index 0000000..e5fbe5f --- /dev/null +++ b/src/modals/SearchModal/index.tsx @@ -0,0 +1,68 @@ +import * as S from './styles' +import { useState, useEffect } from 'react' +import breeds from '~/data/breeds.json' +import { useModalStore } from '~/stores/modalStore' +import { useDogProfileStore } from '~/stores/dogProfileStore' + +export default function SearchModal() { + const { setDogProfile } = useDogProfileStore() + const { popModal } = useModalStore() + const [searchTerm, setSearchTerm] = useState('') + const [searchResults, setSearchResults] = useState([]) + + useEffect(() => { + if (searchTerm === '') { + setSearchResults(breeds.breeds.sort((a, b) => a.localeCompare(b))) + return + } + + const filteredResults = breeds.breeds + .filter(breed => breed.includes(searchTerm)) + .sort((a, b) => (!a.startsWith(searchTerm) && b.startsWith(searchTerm) ? 1 : -1)) + setSearchResults(filteredResults) + }, [searchTerm]) + + const handleClickSearchArea = (e: React.MouseEvent) => { + e.stopPropagation() + } + + const highlightText = (text: string) => { + if (!searchTerm) return text + + const parts = text.split(new RegExp(`(${searchTerm})`, 'gi')) + return parts.map((part, index) => + part.toLowerCase() === searchTerm.toLowerCase() ? {part} : part + ) + } + + const handleBreedClick = (e: React.MouseEvent) => { + const target = e.target as HTMLElement + if (target.closest('[data-breed]')) { + const breed = target.closest('[data-breed]')?.getAttribute('data-breed') + if (breed) setDogProfile({ breed }) + popModal() + } + } + + return ( + + + setSearchTerm(e.target.value)} + autoFocus + /> + + {searchResults.map((result, index) => ( + + {highlightText(result)} + + ))} + 믹스견 + 기타 + + + + ) +} diff --git a/src/modals/SearchModal/styles.ts b/src/modals/SearchModal/styles.ts new file mode 100644 index 0000000..052d0c1 --- /dev/null +++ b/src/modals/SearchModal/styles.ts @@ -0,0 +1,88 @@ +import styled from 'styled-components' + +export const SearchModalOverlay = styled.div` + background-color: rgba(0, 0, 0, 0.4); + z-index: 400; + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + padding-top: 140px; + + @media (max-height: 750px) { + padding-top: 120px; + } + + @media (max-height: 700px) { + padding-top: 100px; + } +` + +export const SearchArea = styled.div` + position: relative; + height: 45%; + margin: 0 20px; + display: flex; + flex-direction: column; + gap: 4px; +` + +export const SearchInput = styled.input` + width: 100%; + background-color: ${({ theme }) => theme.colors.grayscale.gc_4}; + border: none; + text-align: center; + padding: 17px 32px; + border-radius: 12px; + font-size: ${({ theme }) => theme.typography._20}; + font-weight: 700; + + &:focus { + box-shadow: ${({ theme }) => `inset 0 0 0 1px ${theme.colors.grayscale.font_1}`}; + } + &::placeholder { + font-weight: 400; + } +` + +export const SearchResultsWrapper = styled.div` + border-radius: 12px; + background-color: white; + flex: 1; + overflow-y: auto; + padding: 4px; + + &::-webkit-scrollbar { + width: 6px; + } + + &::-webkit-scrollbar-track { + background-color: ${({ theme }) => theme.colors.grayscale.gc_2}; + border-radius: 12px; + } + + &::-webkit-scrollbar-thumb { + background-color: ${({ theme }) => theme.colors.grayscale.font_1}; + border-radius: 10px; + } + + &::-webkit-scrollbar-thumb:hover { + background: #555; + } +` + +export const SearchResult = styled.div` + border-radius: 12px; + height: 55px; + padding: 16px 20px; + cursor: pointer; + + &:hover { + background-color: ${({ theme }) => theme.colors.brand.lighten_3}; + } +` +export const Highlight = styled.span` + color: ${({ theme }) => theme.colors.brand.darken}; + font-weight: bold; +` diff --git a/src/pages/RegisterPage/Dog/styles.ts b/src/pages/RegisterPage/Dog/styles.ts index c77bbfa..7c79e9d 100644 --- a/src/pages/RegisterPage/Dog/styles.ts +++ b/src/pages/RegisterPage/Dog/styles.ts @@ -3,7 +3,11 @@ import { styled } from 'styled-components' export const RegisterDogPage = styled.div` padding: 120px 20px 24px 20px; background-color: ${({ theme }) => theme.colors.grayscale.gc_4}; - height: 100dvh; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; @media (max-height: 700px) { padding: 100px 20px 24px 20px; diff --git a/src/stores/dogProfileStore.ts b/src/stores/dogProfileStore.ts new file mode 100644 index 0000000..257774f --- /dev/null +++ b/src/stores/dogProfileStore.ts @@ -0,0 +1,24 @@ +import { create } from 'zustand' +import { DogProfileType } from '~types/dogProfile' + +interface DogProfileStore { + dogProfile: DogProfileType + setDogProfile: (profile: Partial) => void +} + +export const useDogProfileStore = create(set => ({ + dogProfile: { + name: '', + image: undefined, + birth: '', + intro: '', + gender: null, + isNeutered: false, + breed: '', + weight: '', + }, + setDogProfile: profile => + set(state => ({ + dogProfile: { ...state.dogProfile, ...profile }, + })), +})) diff --git a/src/types/dogProfile.ts b/src/types/dogProfile.ts new file mode 100644 index 0000000..e5c677d --- /dev/null +++ b/src/types/dogProfile.ts @@ -0,0 +1,10 @@ +export interface DogProfileType { + name: string + image: string | undefined + birth: string + intro: string + gender: 'male' | 'female' | null + isNeutered: boolean + breed: string + weight: string +} diff --git a/src/utils/validateDogProfile.ts b/src/utils/validateDogProfile.ts index ced4bed..a61ee83 100644 --- a/src/utils/validateDogProfile.ts +++ b/src/utils/validateDogProfile.ts @@ -1,11 +1,7 @@ -interface DogProfileType { - name: string - image: string | undefined - birth: string - intro: string -} +import { DogProfileType } from '~types/dogProfile' const regex = /^[가-힣]+$/ +const familyCodeDummyData = ['TYK14TK2', '1234'] export const validateDogProfile = (dogProfile: DogProfileType): string | null => { if (!dogProfile.name) return '반려견의 이름을 입력해주세요' @@ -16,3 +12,16 @@ export const validateDogProfile = (dogProfile: DogProfileType): string | null => if (dogProfile.intro.length > 40) '한줄 소개는 최대 40자까지만 적어주세요' return null } + +export const validateDogDetailProfile = (dogProfile: DogProfileType): string | null => { + if (!dogProfile.gender) return '성별을 선택해주세요' + if (!dogProfile.breed) return '견종을 선택해주세요' + if (!dogProfile.weight) return '몸무게를 입력해주세요' + return null +} + +export const validateFamilyCode = (familyCode: string): string | null => { + if (!familyCode) return '가족 코드를 입력해주세요' + if (!familyCodeDummyData.includes(familyCode)) return '가족 코드가 유효하지 않습니다' + return null +}