-
Notifications
You must be signed in to change notification settings - Fork 0
[OD-179] 회원가입 시 유저 태그 설정 #132
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4160439
6520ad0
94b1b2b
d3cfd24
ced84ab
4074464
b4df44b
94a8da9
a7f0797
fa5d5ab
30965eb
7fc829b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| import { useEffect, useState } from 'react'; | ||
| import { useNavigate } from 'react-router-dom'; | ||
|
|
||
| import { getUserInfoApi, patchUserInfoApi } from '@apis/user'; | ||
| import { PatchUserInfoRequest } from '@apis/user/dto'; | ||
| import { getCurrentUserId } from '@utils/getCurrentUserId'; | ||
| import { styleImages } from '@utils/styleImages'; | ||
|
|
||
| import Back from '@assets/arrow/left.svg'; | ||
|
|
||
| import BottomButton from '@components/BottomButton'; | ||
| import { OODDFrame } from '@components/Frame/Frame'; | ||
| import Modal from '@components/Modal'; | ||
| import TopBar from '@components/TopBar'; | ||
|
|
||
| import { PickMyStyleLayout, StyledSubTitle, StyledTitle, CategoryList, PlaceholderImage } from './style'; | ||
|
|
||
| const PickMyStyle: React.FC = () => { | ||
| const [nickname, setNickname] = useState(''); | ||
| const [clickedImages, setClickedImages] = useState<{ [key: number]: boolean }>({}); | ||
|
|
||
| const [isModalOpen, setIsModalOpen] = useState(false); | ||
| const [modalMessage, setModalMessage] = useState(''); | ||
|
|
||
| const navigate = useNavigate(); | ||
| const currentUserId = getCurrentUserId(); | ||
|
|
||
| // 유저 정보 가져오기 | ||
| useEffect(() => { | ||
| const getUserInfo = async () => { | ||
| try { | ||
| const userInfo = await getUserInfoApi(currentUserId); | ||
| setNickname(userInfo.data.nickname); | ||
| } catch (error) { | ||
| console.error('유저 정보 불러오기 실패:', error); | ||
| } | ||
| }; | ||
| getUserInfo(); | ||
| }, [currentUserId]); | ||
|
|
||
| // 이미지 클릭 시 상태 변경 | ||
| const handleImageClick = (id: number) => { | ||
| setClickedImages((prev) => ({ | ||
| ...prev, | ||
| [id]: !prev[id], // 클릭할 때마다 토글 | ||
| })); | ||
| }; | ||
|
|
||
| const handleSubmitBtnClick = async () => { | ||
| const selectedCategories = Object.keys(clickedImages) | ||
| .filter((id) => clickedImages[Number(id)]) // 클릭된 이미지만 필터링 | ||
| .map((id) => styleImages.find((img) => img.id === Number(id))?.category) // category 값 가져오기 | ||
| .filter((category): category is string => !!category); // undefined 제거 | ||
|
|
||
| const requestData: Partial<PatchUserInfoRequest> = { | ||
| userStyletags: selectedCategories, | ||
| }; | ||
| console.log(requestData); | ||
|
|
||
| try { | ||
| const data = await patchUserInfoApi(requestData, currentUserId); | ||
| console.log(data); | ||
| navigate('/'); | ||
| } catch (error) { | ||
| console.error('API 요청 실패:', error); | ||
| setModalMessage('스타일 선택 중 오류가 발생했습니다.'); | ||
| console.log(requestData); | ||
| setIsModalOpen(true); | ||
| } | ||
| }; | ||
|
|
||
| const handleModalClose = () => { | ||
| setIsModalOpen(false); | ||
| }; | ||
|
|
||
| return ( | ||
| <OODDFrame> | ||
| <TopBar | ||
| LeftButtonSrc={Back} | ||
| onClickLeftButton={() => { | ||
| window.history.back(); | ||
| }} | ||
| /> | ||
| <PickMyStyleLayout> | ||
| <StyledTitle | ||
| $textTheme={{ | ||
| style: { mobile: 'heading1-bold', tablet: 'title2-bold', desktop: 'title2-bold' }, | ||
| }} | ||
| > | ||
| {nickname}님의 취향을 알려주세요! | ||
| </StyledTitle> | ||
| <StyledSubTitle | ||
| $textTheme={{ | ||
| style: { mobile: 'caption1-medium', tablet: 'body2-medium', desktop: 'body2-medium' }, | ||
| }} | ||
| > | ||
| OODD가 당신의 취향을 분석하여 맞춤 스타일을 추천해 드릴게요. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 자신이 고른 옷 스타일과 유사한 이성을 추천해주는건가요?!
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네넹 스트릿, 페미닌 등을 골랐으면 해당 스타일의 게시글을 가진 이성을 추천해주는 거라 이해했습니당
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋은 의견입니다. 다만 스타일 태그를 회원가입 때 선택할 때 그 스타일이 어떤 느낌인지 모르는 상황이 있을 수 있어 지금과 같은 기획을 한것이니 해당 리스트 카테고리를 보여주는 기준에 대한 정책을 개선시키는 방향이 좋을 듯합니다.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그럼 이건 다음 스프린트나 목요일 회의에서 정하나요?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이건 백로그로 등록 시켜두고 스프린트 회의때 얘기해보죠 |
||
| </StyledSubTitle> | ||
| <CategoryList> | ||
mimizae marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| {styleImages.map((image) => ( | ||
| <PlaceholderImage | ||
| key={image.id} | ||
| $isClicked={!!clickedImages[image.id]} | ||
| onClick={() => handleImageClick(image.id)} | ||
| data-category={image.category} | ||
| > | ||
| <img src={image.src} alt={`${image.category} 스타일`} /> | ||
| </PlaceholderImage> | ||
| ))} | ||
| </CategoryList> | ||
| <BottomButton | ||
| content="OODD 시작하기" | ||
| onClick={handleSubmitBtnClick} | ||
| disabled={!Object.values(clickedImages).some(Boolean)} | ||
| /> | ||
| {isModalOpen && <Modal content={modalMessage} onClose={handleModalClose} />} | ||
| </PickMyStyleLayout> | ||
| </OODDFrame> | ||
| ); | ||
| }; | ||
|
|
||
| export default PickMyStyle; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| import { styled } from 'styled-components'; | ||
|
|
||
| import { StyledText } from '@components/Text/StyledText'; | ||
|
|
||
| export const OODDFrame = styled.div` | ||
| width: 100%; | ||
| height: 100vh; // 화면 전체 높이 차지 | ||
| overflow: hidden; // 전체 화면 스크롤 방지 | ||
| display: flex; | ||
| flex-direction: column; | ||
| `; | ||
|
|
||
| export const PickMyStyleLayout = styled.div` | ||
| display: flex; | ||
| flex-direction: column; | ||
| padding: 0 1.875rem; | ||
| flex: 1; // 남은 공간을 다 차지하도록 설정 | ||
| width: 100%; | ||
| height: 100%; | ||
| overflow: hidden; // 상위 요소의 스크롤 방지 | ||
| `; | ||
|
|
||
| export const StyledTitle = styled(StyledText)` | ||
| margin: 0.625rem 0; | ||
| `; | ||
|
|
||
| export const StyledSubTitle = styled(StyledText)` | ||
| margin-bottom: 1.25rem; | ||
| `; | ||
|
|
||
| export const CategoryList = styled.div` | ||
| width: 100%; | ||
| max-width: 31.25rem; | ||
| margin: auto; | ||
| flex-grow: 1; | ||
| overflow-y: auto; | ||
| padding-bottom: 6.25rem; | ||
| display: grid; | ||
| grid-template-columns: repeat(2, 1fr); | ||
| gap: 0.625rem; | ||
|
|
||
| // 스크롤바 숨기기 | ||
| &::-webkit-scrollbar { | ||
| display: none; | ||
| } | ||
| scrollbar-width: none; | ||
| `; | ||
|
|
||
| export const PlaceholderImage = styled.div<{ $isClicked: boolean }>` | ||
| width: 100%; | ||
| aspect-ratio: 1; | ||
| background-color: lightgray; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| font-size: 0.875rem; | ||
| border-radius: 0.5rem; | ||
| transition: | ||
| transform 0.2s ease, | ||
| box-shadow 0.2s ease; | ||
|
|
||
| transform: ${({ $isClicked }) => ($isClicked ? 'scale(0.9)' : 'scale(1)')}; | ||
| box-shadow: ${({ $isClicked }) => ($isClicked ? '0 0.125rem 0.25rem rgba(0, 0, 0, 0.2)' : 'none')}; | ||
|
|
||
| &:hover { | ||
| transform: ${({ $isClicked }) => ($isClicked ? 'scale(0.9)' : 'scale(0.95)')}; | ||
| } | ||
| cursor: pointer; | ||
|
|
||
| img { | ||
| width: 100%; | ||
| height: 100%; | ||
| object-fit: cover; | ||
| border-radius: 0.5rem; | ||
| } | ||
| `; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| export const styleImages = [ | ||
| { category: 'luxury', id: 0, src: '/styles/classic.jpg' }, | ||
| { category: 'casual', id: 1, src: '/styles/casual.jpg' }, | ||
| { category: 'street', id: 2, src: '/styles/hip.jpg' }, | ||
| { category: 'feminine', id: 3, src: '/styles/feminine.jpg' }, | ||
| { category: 'hip', id: 4, src: '/styles/luxury.jpg' }, | ||
| { category: 'outdoor', id: 5, src: '/styles/street.jpg' }, | ||
| { category: 'casual', id: 6, src: '/styles/feminine.jpg' }, | ||
| { category: 'classic', id: 7, src: '/styles/hip.jpg' }, | ||
| { category: 'sporty', id: 8, src: '/styles/casual.jpg' }, | ||
| { category: 'formal', id: 9, src: '/styles/formal.jpg' }, | ||
| { category: 'feminine', id: 10, src: '/styles/luxury.jpg' }, | ||
| { category: 'street', id: 11, src: '/styles/street.jpg' }, | ||
| { category: 'minimal', id: 12, src: '/styles/casual.jpg' }, | ||
| { category: 'outdoor', id: 13, src: '/styles/classic.jpg' }, | ||
| { category: 'formal', id: 14, src: '/styles/formal.jpg' }, | ||
| { category: 'sporty', id: 15, src: '/styles/outdoor.jpg' }, | ||
| { category: 'hip', id: 16, src: '/styles/hip.jpg' }, | ||
| { category: 'minimal', id: 17, src: '/styles/minimal.jpg' }, | ||
| { category: 'classic', id: 18, src: '/styles/classic.jpg' }, | ||
| { category: 'luxury', id: 19, src: '/styles/luxury.jpg' }, | ||
| ]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
requestData에서 userStyletags 외에 다른 데이터가 포함될 여지가 없다면 Pick을 사용하는 편이 타입 안정성 측면에서 더 좋을 것 같기는 합니다!