Skip to content
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

[Feature] - 프로필 이미지 수정 기능 및 여행기 등록 시 여행 장소마다 국가 코드 주도록 구현 #535

Merged
merged 24 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
004644c
refactor(Drawer): 기존 헤드와 높이 달라 선 위치가 다른 문제 개선
simorimi Oct 10, 2024
cef7429
feat: 장소 필터링을 위하여 장소 등록시 countryCode를 보내도록 기능 구현
simorimi Oct 13, 2024
7912136
refactor(AvatarCircle): props $네이밍 수정
simorimi Oct 14, 2024
0aa2cfc
Merge branch 'develop/fe' of https://github.com/woowacourse-teams/202…
simorimi Oct 14, 2024
dde854e
refactor(MyTravelogue):$ 제거에 따른 수정
simorimi Oct 15, 2024
932e318
refactor(usePostUploadImages): resize 와 convert 처리 내부에서 하도록 수정
simorimi Oct 15, 2024
aa16247
refactor(MainPage): div semantic 태그인 button으로 수정
simorimi Oct 15, 2024
2eb4464
feat(ProfileImageEditModalBottomSheet): 기능 구현
simorimi Oct 15, 2024
537c3e8
feat(usePutProfile): api 명세 변경에 따라 patch를 put으로, imageUrl body 값에 부여
simorimi Oct 15, 2024
98076f6
refactor(AvatarCircle): props 유연하게 수정
simorimi Oct 16, 2024
fbc78f4
feat(useMyPage): 훅 구현
simorimi Oct 16, 2024
9321263
feat(MyPage): 프로필 이미지 수정 기능 구현
simorimi Oct 16, 2024
3f702c4
refactor(MyPage): 기능 단위로 pr 분리하기 위한 수정
simorimi Oct 16, 2024
ae9be47
refactor(SearchPage): 기능 단위로 pr 분리하기 위한 수정
simorimi Oct 16, 2024
01b6786
refactor(useMyPage): useToggle 사용하도록 수정
simorimi Oct 16, 2024
c7561fd
refactor(common): 반복되는 타입PlaceInfo 타입으로 선언 및 수정
simorimi Oct 20, 2024
b889315
refactor(constants): 상수들 파일로 분리
simorimi Oct 20, 2024
cac5077
refactor(useMyPage): 책임에 따라 각각 커스텀 훅으로 분리
simorimi Oct 20, 2024
7e6ca5e
refactor(constants): 상수 파일로 분리
simorimi Oct 20, 2024
f318d32
refactor(usePostUploadImages): max width, height 값 받을 수 있도록 수정
simorimi Oct 20, 2024
c810327
refactor(usePostUploadImages): max width, height 값 받을 수 있도록 수정
simorimi Oct 20, 2024
0ec73d3
Merge branch 'feature/fe/#519' of https://github.com/woowacourse-team…
simorimi Oct 20, 2024
3f18f98
refactor(useProfileInitialization): 의존성 배열 추가
simorimi Oct 21, 2024
211c7d6
refactor: useCallback으로 update 함수 감싸주도록 수정
simorimi Oct 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions frontend/__tests__/travelPlanRegisterPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const REGISTER_TRAVEL_PLAN = {
lng: 126.977,
},
description: "조선 시대에 지어진 다섯 개의 궁궐 중 가장 큰 궁궐입니다.",
countryCode: "kr",
},
],
},
Expand Down Expand Up @@ -113,13 +114,14 @@ describe("여행 계획 등록 페이지 테스트", () => {
test("사용자는 1일차에 '경복궁'이라는 장소를 추가할 수 있다.", () => {
// given
const { result } = renderHook(() => useTravelPlanDays([]));
const newPlace: Pick<TravelPlanPlace, "placeName" | "position" | "todos"> = {
const newPlace: Pick<TravelPlanPlace, "placeName" | "position" | "todos" | "countryCode"> = {
placeName: "경복궁",
position: {
lat: 37.5796,
lng: 126.977,
},
todos: [],
countryCode: "kr",
};

// when
Expand All @@ -139,13 +141,14 @@ describe("여행 계획 등록 페이지 테스트", () => {
test("사용자는 추가한 경복궁을 삭제할 수 있다.", () => {
// given
const { result } = renderHook(() => useTravelPlanDays([]));
const newPlace: Pick<TravelPlanPlace, "placeName" | "position" | "todos"> = {
const newPlace: Pick<TravelPlanPlace, "placeName" | "position" | "todos" | "countryCode"> = {
placeName: "경복궁",
position: {
lat: 37.5796,
lng: 126.977,
},
todos: [],
countryCode: "kr",
};

// when
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const meta = {
title: "Components/AvatarCircle",
component: AvatarCircle,
argTypes: {
$size: {
size: {
control: {
type: "select",
options: ["small", "medium", "large"],
Expand All @@ -31,42 +31,42 @@ type Story = StoryObj<typeof meta>;

export const Small: Story = {
args: {
$size: "small",
size: "small",
profileImageUrl: "https://i.pinimg.com/564x/c0/d6/5e/c0d65ef2ff5b3e752b70fe54d94d6206.jpg",
},
};

export const Medium: Story = {
args: {
$size: "medium",
size: "medium",
profileImageUrl: "https://i.pinimg.com/564x/c0/d6/5e/c0d65ef2ff5b3e752b70fe54d94d6206.jpg",
},
};

export const Large: Story = {
args: {
$size: "large",
size: "large",
profileImageUrl: "https://i.pinimg.com/564x/4c/a5/a1/4ca5a1de62690b5615925ce3def4636d.jpg",
},
};

export const WithDefaultAvatar: Story = {
args: {
$size: "small",
size: "small",
profileImageUrl: "https://invalid-image-url.jpg",
},
};

export const MediumWithDefaultAvatar: Story = {
args: {
$size: "medium",
size: "medium",
profileImageUrl: "https://invalid-image-url.jpg",
},
};

export const LargeWithDefaultAvatar: Story = {
args: {
$size: "large",
size: "large",
profileImageUrl: "https://invalid-image-url.jpg",
},
};
13 changes: 6 additions & 7 deletions frontend/src/components/common/AvatarCircle/AvatarCircle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,20 @@ import useImageError from "@hooks/useImageError";
import * as S from "./AvatarCircle.styled";
import type { AvatarCircleSize } from "./AvatarCircle.type";

interface AvatarCircleProps {
$size?: AvatarCircleSize;
interface AvatarCircleProps extends React.ImgHTMLAttributes<HTMLImageElement> {
size?: AvatarCircleSize;
profileImageUrl?: string;
imageAlt?: string;
}

const AvatarCircle = ({ $size = "small", profileImageUrl, imageAlt }: AvatarCircleProps) => {
const AvatarCircle = ({ size = "small", profileImageUrl, ...props }: AvatarCircleProps) => {
const { imageError, handleImageError } = useImageError({ imageUrl: profileImageUrl });

return (
<S.AvatarCircleContainer $size={$size}>
<S.AvatarCircleContainer $size={size}>
{!imageError ? (
<img src={profileImageUrl} alt={imageAlt} onError={handleImageError} />
<img src={profileImageUrl} onError={handleImageError} {...props} />
) : (
<S.FallbackIcon $size={$size}>
<S.FallbackIcon $size={size}>
<svg
width="11"
height="11"
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/common/Drawer/Drawer.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,15 @@ export const Overlay = styled.div<{ isOpen: boolean }>`
export const DrawerHeader = styled.div`
display: flex;
align-items: center;
height: calc(5.6rem + 1px);
padding: 1rem;
height: 6rem;
padding: ${({ theme }) => theme.spacing.m};
border-bottom: 1px solid #e0e0e0;
`;

export const DrawerContent = styled.div`
flex: 1;
overflow-y: auto;
padding: 1rem;
padding: ${({ theme }) => theme.spacing.m};
`;

export const TriggerButton = styled.button`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import * as S from "./GoogleSearchPopup.styled";

interface GoogleSearchPopupProps {
onClosePopup: () => void;
onSearchPlaceInfo: (placeInfo: Pick<TravelTransformPlace, "placeName" | "position">) => void;
onSearchPlaceInfo: (
placeInfo: Pick<TravelTransformPlace, "placeName" | "position" | "countryCode">,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pick<TravelTransformPlace, "placeName" | "position" | "countryCode"> 해당 타입에 대해 중복된 부분이 꽤나 발견되는거 같은데 대한 타입을 따로 분리하면 어떨까 싶긴 하네요~!
(countryCode 뿐만 아니라 다른 타입이 추가된다면 또 다 추가해야하니까 번거로워질 수 있을거 같아요)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은 피드백입니다! 해당 부분 PlaceInfo라는 타입으로 만들어 사용해줬습니다 고마워요

) => void;
}

const GoogleSearchPopup = ({ onClosePopup, onSearchPlaceInfo }: GoogleSearchPopupProps) => {
Expand All @@ -31,15 +33,21 @@ const GoogleSearchPopup = ({ onClosePopup, onSearchPlaceInfo }: GoogleSearchPopu
const onPlaceChanged = useCallback(() => {
if (autocomplete !== null) {
const place = autocomplete.getPlace();
if (place.geometry && place.geometry.location) {

if (place.geometry && place.geometry.location && place.address_components) {
const newCenter = {
lat: place.geometry.location.lat(),
lng: place.geometry.location.lng(),
};

const placeInfo: Pick<TravelTransformPlace, "placeName" | "position"> = {
const countryCode = place.address_components.find((component) =>
component.types.includes("country"),
)?.short_name;

const placeInfo: Pick<TravelTransformPlace, "placeName" | "position" | "countryCode"> = {
placeName: place.name || "",
position: newCenter,
countryCode: countryCode || "",
};

onSearchPlaceInfo(placeInfo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ const SearchHeader = () => {
)
}
autoFocus
maxLength={20}
placeholder="제목 또는 작성자명으로 검색해 주세요."
maxLength={FORM_VALIDATIONS_MAP.title.maxLength}
placeholder="여행기 검색"
css={css`
height: 4rem;
padding-right: 7.8rem;
Expand Down
4 changes: 1 addition & 3 deletions frontend/src/components/pages/main/MainPage.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,11 @@ export const MainPageTraveloguesList = styled.ul`
gap: ${({ theme }) => theme.spacing.m};
`;

export const OptionContainer = styled.div`
export const OptionContainer = styled.button`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[단순 궁금증]

button 태그로 변경한 이유가 그냥 궁금하긴 합니다~! (접근성 때문인건가 싶긴 했어용)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

드래그가 가능하다는 것을 표현하기 위해 cursor css가 들어간거라서
이 경우 수정 전 코드가 더 시맨틱적으로 맞는거 같습니다!
(메인 페이지 웹 접근성 개선하면서 대공사를 하게되서 따로 다시 수정 커밋은 안해주셔도 될거같아용)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

click 메서드이기에 이 부분은 button 이 맞다고 생각했어요! 이름이 Container라 전체 option들에 대한 컨테이너라고 착각할 수 있을 거 같은데 이 부분은 한 옵션을 감싸는 wrapper라고 받아들이는 것이 좋습니다! 그래서 클릭하는 옵션이기에 당연히 button이 맞다고 생각하였습니다

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 감사합니다 ㅎㅎ

display: flex;
justify-content: space-between;

width: 100%;

cursor: pointer;
`;

export const LastElement = styled.div`
Expand Down
88 changes: 61 additions & 27 deletions frontend/src/components/pages/my/MyPage.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,79 +2,111 @@ import { css } from "@emotion/react";
import styled from "@emotion/styled";

import theme from "@styles/theme";
import { PRIMITIVE_COLORS } from "@styles/tokens";

export const Layout = styled.div`
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
gap: ${(props) => props.theme.spacing.xl};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생한게 느껴지는 CSS.. 👍👍👍

width: 100%;
padding: ${(props) => props.theme.spacing.l};
`;

export const TabContentContainer = styled.div`
display: flex;
flex-direction: column;
width: 100%;
gap: ${(props) => props.theme.spacing.m};
`;

export const ColorButtonStyle = css`
display: flex;
justify-content: flex-start;
align-items: center;
width: 100%;
height: 6.8rem;
padding: ${theme.spacing.m};
border-radius: 10px;

background-color: ${PRIMITIVE_COLORS.blue[50]};
gap: ${theme.spacing.m};
`;

export const ListStyle = css`
export const listStyle = css`
li {
${theme.typography.mobile.body};
font-weight: 700;
${theme.typography.mobile.bodyBold};
}
`;

export const FormWrapper = styled.form`
export const ProfileContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
gap: ${({ theme }) => theme.spacing.m};

width: 100%;
`;

export const ButtonWrapper = styled.div`
export const ProfileEditButtonContainer = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing.xs};
`;

export const EditButtonContainer = styled.div`
display: flex;
justify-content: flex-end;

width: 100%;
`;

export const Button = styled.button`
export const EditButton = styled.button`
padding: ${({ theme }) => theme.spacing.s} ${({ theme }) => theme.spacing.m};
border: solid 1px ${({ theme }) => theme.colors.border};
${({ theme }) => theme.typography.mobile.detail};
border-radius: 10px;
`;

export const NicknameWrapper = styled.div`
export const ProfileImageContainer = styled.div`
display: flex;
position: relative;
`;

export const ProfileImageWrapper = styled.div<{ $isProfileImageLoading: boolean }>`
display: ${({ $isProfileImageLoading }) => ($isProfileImageLoading ? "none" : "block")};
`;

export const ProfileImageLoadingWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;

width: 12.9rem;
height: 12.9rem;
`;

export const ProfileImageHiddenInput = styled.input`
display: none;
`;

export const NicknameWrapper = styled.div`
display: flex;
justify-content: center;
align-items: flex-start;

width: 100%;
height: 3rem;
height: 6.5rem;
`;

export const InputContainer = styled.div`
export const NickNameEditContainer = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing.s};

width: 100%;
height: 6.5rem;
`;

export const Button = styled.button`
width: 100%;
`;

export const profileImageEditButtonStyle = css`
justify-content: flex-end;
align-items: flex-end;
position: absolute;

width: 100%;
height: 100%;
border-radius: 50%;
`;

export const inputStyle = css`
Expand Down Expand Up @@ -103,8 +135,10 @@ export const inputStyle = css`
}
`;

export const NicknameStyle = css`
margin-bottom: calc(1.2rem + 2px);
export const nicknameStyle = css`
padding: 1.2rem 1.6rem;
`;

font-weight: 700;
export const deleteTextColor = css`
color: ${theme.colors.text.required};
`;
Loading
Loading