Skip to content

Commit

Permalink
[Feature] - 랜딩 페이지 구현 (#539)
Browse files Browse the repository at this point in the history
* feat(Text): isInline props 추가

* feat(webp): 랜딩 페이지에 사용되는 이미지들 추가

* feat(router): 랜딩 페이지 route 추가

* feat(Animation): Animation style 추가

* feat(useAnimationObserver): 애니메이션을 위한 Observer 훅 구현

* feat(FirstPage): 랜딩 페이지의 첫번째 페이지 구현

* feat(SecondPage): 랜딩 페이지의 두번째 페이지 구현

* feat(ThirdPage): 랜딩 페이지의 세번째 페이지 구현

* feat(Box): 랜딩 페이지 내에서 사용되는 Box 컴포넌트 구현

* feat(FourthPage): 랜딩 페이지의 네번째 페이지 추가

* feat(LandingPage): 랜딩 페이지에 페이지들 추가

* refactor(useScrollAnimation): hook으로 분리

* fix(SecondPage): 띄어쓰기 없어지는 이슈 수정

* chore(useAnimationObserver): 필요없는 console.log 삭제

* fix(SecondPage): 글자 개행 깨지는 이슈 수정

* fix(FourthPage): 말풍선 속 글씨 깨짐 수정

* style(Text): 조건문 css 값 수정

* fix: 라우터가 달라짐에따라 수정
  • Loading branch information
0jenn0 authored Oct 23, 2024
1 parent 74c9dfb commit 8cab7f8
Show file tree
Hide file tree
Showing 40 changed files with 1,061 additions and 13 deletions.
2 changes: 1 addition & 1 deletion frontend/cypress/e2e/mainPage.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe("메인 페이지 테스트", () => {
});
}).as("getTravelogues");

cy.visit(ROUTE_PATHS_MAP.root);
cy.visit(ROUTE_PATHS_MAP.main);
});

describe("여행기 무한 스크롤 테스트", () => {
Expand Down
2 changes: 1 addition & 1 deletion frontend/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Cypress.Commands.add("simulateKakaoLogin", () => {

cy.wait("@loginOauthRequest");

cy.url().should("eq", `${Cypress.config().baseUrl}${ROUTE_PATHS_MAP.root}`);
cy.url().should("eq", `${Cypress.config().baseUrl}${ROUTE_PATHS_MAP.main}`);
});

Cypress.Commands.add("fillTravelPlanBasicInfo", (title, date) => {
Expand Down
Binary file added frontend/src/assets/webp/bigTturi.webp
Binary file not shown.
6 changes: 6 additions & 0 deletions frontend/src/assets/webp/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
export { default as ExcitedTturi } from "./excitedTturi.webp";
export { default as BigTturi } from "./bigTturi.webp";
export { default as MainPageImage } from "./mainPage.webp";
export { default as SpeechBubbleLeft } from "./speechBubbleLeft.webp";
export { default as SpeechBubbleRight } from "./speechBubbleRight.webp";
export { default as TravelogueDetailPageImage } from "./travelogueDetailPage.webp";
export { default as travelPlanDetailPageImage } from "./travelPlanDetailPage.webp";
Binary file added frontend/src/assets/webp/mainPage.webp
Binary file not shown.
Binary file added frontend/src/assets/webp/speechBubbleLeft.webp
Binary file not shown.
Binary file added frontend/src/assets/webp/speechBubbleRight.webp
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const DefaultHeader = () => {
<IconButton
iconType="home-icon"
size="20"
onClick={() => navigation(ROUTE_PATHS_MAP.root)}
onClick={() => navigation(ROUTE_PATHS_MAP.main)}
aria-label="홈 이동"
/>
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/common/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const Header = ({
aria-label="홈 이동"
color={isLogoUsed ? theme.colors.primary : PRIMITIVE_COLORS.black}
iconType={isLogoUsed ? "korean-logo" : "back-icon"}
onClick={isLogoUsed ? () => navigate(ROUTE_PATHS_MAP.root) : () => goBack()}
onClick={isLogoUsed ? () => navigate(ROUTE_PATHS_MAP.main) : () => goBack()}
/>
</S.LeftWrapper>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const SearchHeader = () => {
<IconButton
iconType="home-icon"
size="20"
onClick={() => navigate(ROUTE_PATHS_MAP.root)}
onClick={() => navigate(ROUTE_PATHS_MAP.main)}
aria-label="홈 이동"
/>
</>
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/components/common/Text/Text.styled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import styled from "@emotion/styled";

import type { TextProps } from "./Text";

export const Text = styled.p<{ $textType: TextProps["textType"] }>`
export const Text = styled.p<{
$textType: TextProps["textType"];
$isInline: TextProps["isInline"];
}>`
${({ theme, $textType = "body" }) => theme.typography.mobile[$textType]}
${({ $isInline }) => ($isInline === true ? "display: inline" : "block")}
`;
5 changes: 3 additions & 2 deletions frontend/src/components/common/Text/Text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as S from "./Text.styled";

export interface TextProps extends React.HTMLAttributes<HTMLElement> {
textType?: TextVariants;
isInline?: boolean;
}

const TAG_MAP = {
Expand All @@ -16,9 +17,9 @@ const TAG_MAP = {
detailBold: "span",
} as const;

const Text = ({ children, textType = "body", ...attributes }: TextProps) => {
const Text = ({ children, textType = "body", isInline, ...attributes }: TextProps) => {
return (
<S.Text as={TAG_MAP[textType]} $textType={textType} {...attributes}>
<S.Text as={TAG_MAP[textType]} $textType={textType} $isInline={isInline} {...attributes}>
{children}
</S.Text>
);
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/layout/AppLayout/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const AppLayout = () => {
const receivedKeyword = encodedKeyword ? decodeURIComponent(encodedKeyword) : "";

const getHeader = (pathName: string) => {
if (pathName === ROUTE_PATHS_MAP.root) return <HomePageHeader />;
if (pathName === ROUTE_PATHS_MAP.main) return <HomePageHeader />;
if (pathName === ROUTE_PATHS_MAP.searchMain) return <SearchHeader />;
if (receivedKeyword && pathName.includes(ROUTE_PATHS_MAP.searchMain)) return <SearchHeader />;
return <DefaultHeader />;
Expand Down
31 changes: 31 additions & 0 deletions frontend/src/components/pages/landing/Animation.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { css, keyframes } from "@emotion/react";

export const fadeIn = keyframes`
0% {
opacity: 0;
transform: translateY(1rem);
}
100% {
opacity: 1;
transform: translateY(0);
}
`;

const ANIMATION_MAP = {
fadeIn: fadeIn,
};

export const createAnimation = ({
shouldAnimate,
animation,
delay = 300,
}: {
shouldAnimate: boolean;
animation: keyof typeof ANIMATION_MAP;
delay?: number;
}) => css`
opacity: 0;
animation: ${shouldAnimate ? ANIMATION_MAP[animation] : "none"} 0.5s ease-in-out ${delay}ms
forwards;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
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;
justify-content: space-between;
align-items: center;
position: relative;
width: 100%;
height: 100vh;
max-width: 48rem;
padding: ${theme.spacing.l} ${theme.spacing.l} 0 ${theme.spacing.l};
background-color: ${PRIMITIVE_COLORS.blue[50]};
`;
export const Image = styled.img`
position: absolute;
bottom: 0;
z-index: 0;
width: 100%;
`;
export const titleStyle = css`
margin-top: ${theme.spacing.xxl};
color: ${theme.colors.primary};
`;
export const Container = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing.l};
`;
export const buttonStyle = css`
background-color: ${PRIMITIVE_COLORS.white};
color: ${theme.colors.primary};
box-shadow: 0 4px 10px rgb(0 0 0 / 5%);
transition: all 0.2s ease-in-out;
&:hover {
background-color: ${theme.colors.primary};
color: ${PRIMITIVE_COLORS.white};
}
`;
export const Gradient = styled.div`
position: absolute;
bottom: 0;
left: 0;
z-index: 10;
width: 100%;
height: 20rem;
background: linear-gradient(to bottom, rgb(255 255 255 / 0%) 0%, rgb(255 255 255 / 50%) 100%);
`;
53 changes: 53 additions & 0 deletions frontend/src/components/pages/landing/FirstPage/FirstPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useNavigate } from "react-router-dom";

import { Button, Text } from "@components/common";

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

import { BigTturi } from "@assets/webp";

import { createAnimation } from "../Animation.styled";
import useAnimationObserver from "../hook/useAnimationObserver";
import * as S from "./FirstPage.styled";

const FirstPage = () => {
const navigate = useNavigate();
const handleClick = () => {
navigate(ROUTE_PATHS_MAP.main);
};
const { registerElement, getAnimationState } = useAnimationObserver();

return (
<S.Layout>
<S.Container>
<div
ref={registerElement("Title1")}
css={createAnimation({
animation: "fadeIn",
shouldAnimate: getAnimationState("Title1").shouldAnimate,
})}
>
<Text textType="heading" css={S.titleStyle}>
To your route, 투룻!
</Text>
</div>

<div
ref={registerElement("Button1")}
css={createAnimation({
animation: "fadeIn",
shouldAnimate: getAnimationState("Button1").shouldAnimate,
delay: 600,
})}
>
<Button onClick={handleClick} css={S.buttonStyle}>
투룻 사용하기
</Button>
</div>
</S.Container>
<S.Gradient />
<S.Image src={BigTturi} alt="" />
</S.Layout>
);
};
export default FirstPage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { css } from "@emotion/react";
import styled from "@emotion/styled";

import theme from "@styles/theme";

export const Layout = styled.div`
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 48rem;
padding: ${theme.spacing.m};
gap: ${theme.spacing.xxl};
`;

export const largeText = css`
font-size: 1.4rem;
`;

export const titleStyle = css`
text-align: center;
`;

export const subtitleStyle = css`
margin-bottom: ${theme.spacing.xxl};
text-align: center;
`;
80 changes: 80 additions & 0 deletions frontend/src/components/pages/landing/FourthPage/FourthPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Text } from "@components/common";

import { createAnimation } from "../Animation.styled";
import useAnimationObserver from "../hook/useAnimationObserver";
import * as S from "./FourthPage.styled";
import SpeechBubble from "./SpeechBubble/SpeechBubble";

const FourthPage = () => {
const { registerElement, getAnimationState } = useAnimationObserver();

return (
<S.Layout>
<Text textType="title" css={S.titleStyle}>
투룻을 써본 사람들은 이런걸 좋아했어요! 💙
</Text>

<div
ref={registerElement("SpeechBubble1")}
css={createAnimation({
shouldAnimate: getAnimationState("SpeechBubble1").shouldAnimate,
animation: "fadeIn",
})}
>
<SpeechBubble variants="left" name="🦹🏻‍️ 이OO">
<Text textType="detailBold" css={S.largeText}>
여자 친구랑 가는 여행 계획 세우는 게 너무 힘들었는데~ 다른 사람의 여행을 쉽게 여행
계획으로 가져올 수 있어서 좋아요!
</Text>
</SpeechBubble>
</div>

<div
ref={registerElement("SpeechBubble2")}
css={createAnimation({
shouldAnimate: getAnimationState("SpeechBubble2").shouldAnimate,
animation: "fadeIn",
delay: 300,
})}
>
<SpeechBubble variants="right" name="🙋🏻‍♀️ 최OO">
<Text textType="detailBold" css={S.largeText}>
친구가 여행 정보 좀 알려달라고 할 때 일일이 알려주기 귀찮았는데 투룻을 사용하니 잘
정리된 정보로 전달할 수 있어서 좋았어요
</Text>
</SpeechBubble>
</div>

<div
ref={registerElement("SpeechBubble3")}
css={createAnimation({
shouldAnimate: getAnimationState("SpeechBubble3").shouldAnimate,
animation: "fadeIn",
delay: 600,
})}
>
<SpeechBubble variants="left" name="👨🏼‍🏭 심OO">
<Text textType="detailBold" css={S.largeText}>
나의 여행을 기록한 것이 <br />
다른 사람에게도 도움이 되었다는 점이 뿌듯해요
</Text>
</SpeechBubble>
</div>

<div
ref={registerElement("Text1")}
css={[
S.subtitleStyle,
createAnimation({
shouldAnimate: getAnimationState("Text1").shouldAnimate,
animation: "fadeIn",
}),
]}
>
<Text textType="subTitle">투룻 서비스를 이용해보세요! 😘</Text>
</div>
</S.Layout>
);
};

export default FourthPage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { css } from "@emotion/react";
import styled from "@emotion/styled";

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

export const Layout = styled.section`
display: flex;
flex-direction: column;
align-items: center;
position: relative;
width: 100%;
`;
export const Image = styled.img`
width: 100%;
height: 220px;
margin: -4rem 0;
`;
export const TextWrapper = styled.div<{ $variants: "left" | "right" }>`
position: absolute;
top: 50%;
transform: translateY(-40%);
left: 10%;
max-width: 80%;
word-break: keep-all;
word-wrap: break-word;
color: ${({ theme, $variants }) =>
$variants === "left" ? PRIMITIVE_COLORS.white : theme.colors.text.primary};
`;
export const NameWrapper = styled.div<{ $variants: "left" | "right" }>`
display: flex;
justify-content: ${({ $variants }) => ($variants === "left" ? "flex-start" : "flex-end")};
width: 100%;
${({ $variants }) => ($variants === "left" ? "padding-left: 10%;" : "padding-right: 10%;")}
`;
export const fontWeightMediumStyle = css`
font-weight: 500;
`;
Loading

0 comments on commit 8cab7f8

Please sign in to comment.