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] - 랜딩 페이지 구현 #539

Merged
merged 19 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
96cb45d
feat(Text): isInline props 추가
0jenn0 Oct 17, 2024
cd9ecbd
feat(webp): 랜딩 페이지에 사용되는 이미지들 추가
0jenn0 Oct 19, 2024
ab1eb3e
feat(router): 랜딩 페이지 route 추가
0jenn0 Oct 19, 2024
4d4e915
feat(Animation): Animation style 추가
0jenn0 Oct 19, 2024
651a67d
feat(useAnimationObserver): 애니메이션을 위한 Observer 훅 구현
0jenn0 Oct 19, 2024
1bdc713
feat(FirstPage): 랜딩 페이지의 첫번째 페이지 구현
0jenn0 Oct 19, 2024
4b6114b
feat(SecondPage): 랜딩 페이지의 두번째 페이지 구현
0jenn0 Oct 19, 2024
295ad29
feat(ThirdPage): 랜딩 페이지의 세번째 페이지 구현
0jenn0 Oct 19, 2024
3b33142
feat(Box): 랜딩 페이지 내에서 사용되는 Box 컴포넌트 구현
0jenn0 Oct 19, 2024
f7c93da
feat(FourthPage): 랜딩 페이지의 네번째 페이지 추가
0jenn0 Oct 19, 2024
9df1e6c
feat(LandingPage): 랜딩 페이지에 페이지들 추가
0jenn0 Oct 19, 2024
b4065e0
refactor(useScrollAnimation): hook으로 분리
0jenn0 Oct 19, 2024
a53276f
fix(SecondPage): 띄어쓰기 없어지는 이슈 수정
0jenn0 Oct 21, 2024
f3854b2
chore(useAnimationObserver): 필요없는 console.log 삭제
0jenn0 Oct 21, 2024
ba1b69c
fix(SecondPage): 글자 개행 깨지는 이슈 수정
0jenn0 Oct 21, 2024
600bd57
fix(FourthPage): 말풍선 속 글씨 깨짐 수정
0jenn0 Oct 21, 2024
3b229c7
style(Text): 조건문 css 값 수정
0jenn0 Oct 23, 2024
39d5a9a
fix: 라우터가 달라짐에따라 수정
0jenn0 Oct 23, 2024
ae1dde5
Merge branch 'develop/fe' into feature/fe/#534
0jenn0 Oct 23, 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
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";

Choose a reason for hiding this comment

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

빅뚜리

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.
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" : "")}

Choose a reason for hiding this comment

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

inline 여부에 따라 inline 과 block 식으로 해도 괜찮을 거 같은데 이 부분은 참고만 해요!

Copy link
Author

Choose a reason for hiding this comment

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

그게 더 깔끔하겠네요 수정햇습니다

`;
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
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="" />

Choose a reason for hiding this comment

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

alt 값을 일부러 비우신 거 같은데 의도하신 바가 무엇인가요?

Copy link
Author

Choose a reason for hiding this comment

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

단순히 장식용 이미지라 빈 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;
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Text } from "@components/common";

import { SpeechBubbleLeft, SpeechBubbleRight } from "@assets/webp";

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

export interface SpeechBubbleProps {
variants: "left" | "right";
name: string;
}

const SpeechBubble = ({ variants, name, children }: React.PropsWithChildren<SpeechBubbleProps>) => {
return (
<S.Layout>
<S.NameWrapper $variants={variants}>
<Text css={S.fontWeightMediumStyle}>{name}</Text>
</S.NameWrapper>
<S.TextWrapper $variants={variants}>{children}</S.TextWrapper>
<S.Image src={variants === "left" ? SpeechBubbleLeft : SpeechBubbleRight} />
</S.Layout>
);
};

export default SpeechBubble;
29 changes: 29 additions & 0 deletions frontend/src/components/pages/landing/LandingPage.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { css } from "@emotion/react";
import styled from "@emotion/styled";

export const Layout = styled.div`
display: flex;
flex-direction: column;
width: 100vw;
overflow-y: auto;
`;

export const PageWrapper = styled.div`
position: relative;
width: 100%;
height: 260vh;
`;

export const firstPageStyle = css`
position: sticky;
top: 0;
z-index: 1;
`;

export const secondPageStyle = css`
position: absolute;
top: 100vh;
z-index: 2;
width: 100%;
transition: transform 0.1s ease-out;
`;
Loading
Loading