Skip to content

Commit

Permalink
[Fix] - 4차 스프린트 QA 반영 2차 (리버) (#401)
Browse files Browse the repository at this point in the history
* refactor(Box): Box 컴포넌트 props 수정

* feat(TravelPlansTabContent): 상세지도 보기 추가

* refactor(TravelogueCard): title을 Text 컴포넌트 사용으로 수정하여 텍스트 위 짤림 현상 수정

* fix(useInfiniteSearchTravelogues): api 올바르게 수정

* refactor(ThumbnailUpload): 이미지가 등록 중일 때 스피너가 나오도록 구현

* feat(TravelogueRegisterPage): 썸네일 업로드 후 보이는 삭제버튼 구현

* test(ThumbnailUpload): 로딩 중인 ThumbnailUpload storybook 추가

* feat(TravelogueRegisterPage): 썸네일 필수값으로 수정

* refactor(ThumbnailUpload): ThumbnailUpload에서 이미지가 change될때 isLoading 변하게 수정, border보이는 조건 수정

* fix: 썸네일 TextField 컴포넌트로 변경

* fix(ThumbnailUpload): 중복된 z-index 제거

* fix: 검색 결과 text가 이상하게 보이는 오류 해결

* fix: 썸네일 require 아닌 걸로 수정

---------

Co-authored-by: 손진영 <[email protected]>
  • Loading branch information
0jenn0 and jinyoung234 authored Aug 22, 2024
1 parent 5729b7b commit c4129da
Show file tree
Hide file tree
Showing 14 changed files with 202 additions and 33 deletions.
7 changes: 7 additions & 0 deletions frontend/src/components/common/Box/Box.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,10 @@ export const Tag = styled.li`
${({ theme }) => theme.typography.mobile.detail}
color: ${({ theme }) => theme.colors.text.secondary}
`;

export const Header = styled.div`
display: flex;
justify-content: flex-start;
align-items: center;
width: 100%;
`;
11 changes: 8 additions & 3 deletions frontend/src/components/common/Box/Box.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import React from "react";
import React, { ReactNode } from "react";

import * as S from "./Box.styled";

interface BoxProps {
placeName: string;
icon?: ReactNode;
}

const Box = ({
children,
placeName,
icon,
...props
}: React.ComponentPropsWithoutRef<"div"> & React.PropsWithChildren<BoxProps>) => {
return (
<S.Box {...props}>
<S.PlaceName>{placeName}</S.PlaceName>
{children}
<S.Header>
<S.PlaceName>{placeName}</S.PlaceName>
{icon ? icon : null}
</S.Header>
<div>{children}</div>
</S.Box>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Icon from "@components/common/Icon/Icon";
import IconButton from "@components/common/IconButton/IconButton";
import { Input } from "@components/common/Input/Input.styled";

import { FORM_VALIDATIONS_MAP } from "@constants/formValidation";
import { ROUTE_PATHS_MAP } from "@constants/route";

import { extractLastPath } from "@utils/extractId";
Expand Down Expand Up @@ -61,11 +62,20 @@ const SearchHeader = () => {
<Input
ref={inputRef}
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
onChange={(e) =>
setKeyword(
e.target.value.slice(
FORM_VALIDATIONS_MAP.title.minLength,
FORM_VALIDATIONS_MAP.title.maxLength,
),
)
}
autoFocus
maxLength={20}
placeholder="검색해주세요"
css={css`
height: 4rem;
padding-right: 7.8rem;
`}
variant="round"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,10 @@ const meta = {
),
],
} satisfies Meta<typeof ThumbnailUpload>;

export default meta;
type Story = StoryObj<typeof meta>;

const mockOnChangeImage = () => {};
const mockOnClickButton = () => {};

export const Default: Story = {
args: {
id: "1",
Expand All @@ -34,7 +31,6 @@ export const Default: Story = {
onClickButton: mockOnClickButton,
},
};

export const WithImage: Story = {
args: {
...Default.args,
Expand All @@ -49,16 +45,32 @@ export const WithImage: Story = {
const file = new File([""], "example.png", { type: "image/png" });
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);

Object.defineProperty(inputElement, "files", {
value: dataTransfer.files,
});

const event = new Event("change", { bubbles: true });
inputElement.dispatchEvent(event);
}
}, []);

return <Story />;
},
],
};
export const Loading: Story = {
args: {
...Default.args,
id: "thumbnail-upload-3",
previewUrls: ["https://example.com/mock-image.jpg"],
},
decorators: [
(Story) => {
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
React.useEffect(() => {
const timer = setTimeout(() => {
forceUpdate();
}, 100);
return () => clearTimeout(timer);
}, []);
return <Story />;
},
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { css } from "@emotion/react";
import styled from "@emotion/styled";

export const ThumbnailUploadContainer = styled.div`
import theme from "@styles/theme";
import { PRIMITIVE_COLORS } from "@styles/tokens";

export const ThumbnailUploadContainer = styled.div<{ $hasBorder: boolean }>`
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
position: relative;
width: 100%;
height: 20rem;
border: 1px solid ${(props) => props.theme.colors.border};
border: ${({ $hasBorder }) => ($hasBorder ? `1px solid ${theme.colors.border}` : "none")};
border-radius: 0.8rem;
`;

Expand Down Expand Up @@ -47,3 +52,28 @@ export const ThumbnailUploadEditButtonContainer = styled.div`
width: 100%;
height: 100%;
`;

export const ThumbnailUploadLoadingContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background-color: ${PRIMITIVE_COLORS.white};
`;

export const uploadDeleteButtonStyle = css`
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 1.6rem;
right: 1.6rem;
z-index: ${theme.zIndex.default + 1};
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
background-color: rgb(0 0 0 / 20%);
`;
57 changes: 52 additions & 5 deletions frontend/src/components/common/ThumbnailUpload/ThumbnailUpload.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { InputHTMLAttributes, useState } from "react";

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

import { PictureIcon } from "@assets/svg";

import IconButton from "../IconButton/IconButton";
import Spinner from "../Spinner/Spinner";
import * as S from "./ThumbnailUpload.styled";

interface ThumbnailUploadProps extends InputHTMLAttributes<HTMLInputElement> {
Expand All @@ -10,6 +14,7 @@ interface ThumbnailUploadProps extends InputHTMLAttributes<HTMLInputElement> {
fileInputRef: React.RefObject<HTMLInputElement>;
onChangeImage: (e: React.ChangeEvent<HTMLInputElement>) => void;
onClickButton: () => void;
onDeleteButton?: () => void;
}

const ThumbnailUpload = ({
Expand All @@ -18,28 +23,60 @@ const ThumbnailUpload = ({
fileInputRef,
onChangeImage,
onClickButton,
onDeleteButton,
}: ThumbnailUploadProps) => {
const [isShowEditButton, setIsShowEditButton] = useState<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(true);

const handleMouseOver = () => setIsShowEditButton(true);
const handleMouseLeave = () => setIsShowEditButton(false);
const handleMouseOver = () => {
if (!isLoading) setIsShowEditButton(true);
};
const handleMouseLeave = () => {
if (!isLoading) setIsShowEditButton(false);
};

const image = previewUrls[0];

const hasBorder = !!image === false || isLoading;

const handleLoadImage = () => {
setIsLoading(false);
};

const handleChangeImage = (e: React.ChangeEvent<HTMLInputElement>) => {
setIsLoading(true);
onChangeImage(e);
};

const handleDeleteImage = () => {
setIsLoading(false);
onDeleteButton && onDeleteButton();
};

const HiddenInput = (
<S.ThumbnailUploadHiddenInput
id={id}
ref={fileInputRef}
type="file"
accept="image/*"
onChange={onChangeImage}
onChange={handleChangeImage}
aria-label="썸네일 이미지 선택"
title="이미지 파일을 선택하세요"
/>
);

return (
<S.ThumbnailUploadContainer>
<S.ThumbnailUploadContainer $hasBorder={hasBorder}>
{image && (
<IconButton
onClick={handleDeleteImage}
iconType="x-icon"
size="10"
color={PRIMITIVE_COLORS.white}
css={S.uploadDeleteButtonStyle}
/>
)}

{!image ? (
<>
<S.ThumbnailUploadButton type="button" onClick={onClickButton} aria-label="이미지 업로드">
Expand All @@ -58,7 +95,17 @@ const ThumbnailUpload = ({
썸네일 수정하기
</S.ThumbnailUploadEditButton>
)}
<S.ThumbnailUploadImage src={image} alt="썸네일 이미지" />
{isLoading && (
<S.ThumbnailUploadLoadingContainer>
<Spinner variants="circle" size={60} />
</S.ThumbnailUploadLoadingContainer>
)}
<S.ThumbnailUploadImage
src={image}
onLoad={handleLoadImage}
alt="썸네일 이미지"
style={{ display: isLoading ? "none" : "block" }}
/>
{HiddenInput}
</S.ThumbnailUploadEditButtonContainer>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const TravelogueCard = ({
return (
<S.TravelogueCardLayout onClick={handleCardClick}>
<S.TravelogueCardHeader>
<S.TravelogueCardTitle>{title}</S.TravelogueCardTitle>
<Text textType="bodyBold">{title}</Text>
</S.TravelogueCardHeader>

<S.TravelogueCardThumbnailContainer>
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/components/pages/search/SearchPage.styled.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { css } from "@emotion/react";
import styled from "@emotion/styled";

import { SPACING } from "@styles/tokens";
Expand All @@ -23,3 +24,16 @@ export const MainPageTraveloguesList = styled.ul`
gap: ${SPACING.m};
`;

export const searchResultTextStyle = css`
display: -webkit-box;
overflow: hidden;
width: 100%;
max-width: 100%;
line-height: 1.5;
white-space: normal;
word-break: break-word;
overflow-wrap: break-word;
text-overflow: ellipsis;
`;
8 changes: 6 additions & 2 deletions frontend/src/components/pages/search/SearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ const SearchPage = () => {
if (travelogues.length === 0 && status === "success") {
return (
<S.Layout>
{keyword && <Text textType="title">{`"${keyword}" 검색 결과`}</Text>}
{keyword && (
<Text css={S.searchResultTextStyle} textType="title">{`"${keyword}" 검색 결과`}</Text>
)}
<S.SearchFallbackWrapper>
<SearchFallback title="휑" text="검색 결과가 없어요." />
</S.SearchFallbackWrapper>
Expand All @@ -64,7 +66,9 @@ const SearchPage = () => {
return (
<S.Layout>
<FloatingButton />
{keyword && <Text textType="title">{`"${keyword}" 검색 결과`}</Text>}
{keyword && (
<Text css={S.searchResultTextStyle} textType="title">{`"${keyword}" 검색 결과`}</Text>
)}
{status === "pending" && (
<S.MainPageTraveloguesList>
{Array.from({ length: SKELETON_COUNT }, (_, index) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,16 @@ export const boxStyle = css`
export const textStyle = css`
color: ${theme.colors.text.secondary};
`;

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

export const IconButtonStyle = css`
padding: ${theme.spacing.s};
border: 1px solid ${theme.colors.border};
border-radius: 10px;
`;
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { TravelPlanPlace } from "@type/domain/travelPlan";

import { Box, GoogleMapLoadScript, GoogleMapView } from "@components/common";
import { Box, GoogleMapLoadScript, GoogleMapView, IconButton } from "@components/common";
import Skeleton from "@components/common/Skeleton/Skeleton";
import TravelPlanTodoItem from "@components/pages/travelPlanDetail/TravelPlanTodoItem/TravelPlanTodoItem";

import theme from "@styles/theme";

import * as S from "./TravelPlansTabContent.styled";

const TravelPlansTabContent = ({ places }: { places: TravelPlanPlace[] }) => {
Expand All @@ -23,7 +25,28 @@ const TravelPlansTabContent = ({ places }: { places: TravelPlanPlace[] }) => {
</GoogleMapLoadScript>
<S.BoxContainer>
{places.map((place, index) => (
<Box css={S.boxStyle} key={place.id} placeName={`${index + 1}. ${place.placeName}`}>
<Box
key={place.id}
css={S.boxStyle}
placeName={`${index + 1}. ${place.placeName}`}
icon={
<S.IconButtonWrapper>
<a
href={`https://www.google.com/maps/search/${place.placeName}/@${place.position.lat},${place.position.lng}`}
target="_blank"
rel="noopener noreferrer"
title={`${place.placeName} 지도 상세보기 링크 버튼`}
>
<IconButton
iconType="map-icon"
color={theme.colors.primary}
size="16"
css={S.IconButtonStyle}
/>
</a>
</S.IconButtonWrapper>
}
>
<S.TodoListContainer>
{place.todos?.map((todo) => <TravelPlanTodoItem key={todo.id} todo={todo} />)}
</S.TodoListContainer>
Expand Down
Loading

0 comments on commit c4129da

Please sign in to comment.