Skip to content

Latest commit

 

History

History
387 lines (263 loc) · 13.6 KB

README.md

File metadata and controls

387 lines (263 loc) · 13.6 KB

👨‍👩‍👧‍👦 9팀

✨ 배포링크


📝 Description

차량 대여를 위해 차량 목록을 불러오는 사이트입니다.
전역 상태 관리는 React에 내장되어있는 Context API를 사용했고, 스타일 라이브러리는 Styled-components를 사용했습니다.


🛠️ Dev Tools


📝 API 보러가기

과제에 사용된 API 상세 설명입니다.


💅 Figma 보러가기

과제에서 요구사항으로 주어진 Figma입니다.


📝 목차


🖥 프로젝트 실행 방법

git clone [email protected]:crystal993/pre-onboarding-7th-2-2-0.git

yarn install

yarn start

open http://localhost:3000

📝 디렉토리 구조

- 디렉토리 구조
📂src
|   📄App.js
|   📄index.js
|
+---📂api
|       📄 apis.js
|       📄 axiosInstance.js
|       📄 carService.js
|
+---📂assets
|   \---📂icons
|           📄icon_back.svg
|
+---📂components
|   +---📂detail
|   |       📄DetailContainer.jsx
|   |       📄DetailInfoBar.jsx
|   |       📄DetailSectionBar.jsx
|   |
|   +---📂elements
|   |       📄Badge.jsx
|   |       📄Button.jsx
|   |       📄MsgBox.jsx
|   |
|   +---📂layout
|   |       📄Header.jsx
|   |       📄Layout.jsx
|   |
|   +---📂main
|   |       📄CarItem.jsx
|   |       📄CarList.jsx
|   |       📄FilteringBar.jsx
|   |       📄SwiperTags.jsx
|   |
|   \---📂metaTag
|           📄SEOMetaTag.jsx
|
+---📂context
|       📄actionTypes.js
|       📄CarListProvider.js
|       📄CarListReducer.js
|
+---📂pages
|       📄Detail.jsx
|       📄Main.jsx
|       📄NotFound.jsx
|
+---📂router
|       📄Routers.jsx
|
+---📂styles
|       📄GlobalStyle.jsx
|       📄theme.js
|
\---📂utils
    +---📂constant
    |       📄fuelTypeOption.js
    |       📄segmentOption.js
    |
    \---📂function
            📄convertAmount.js
            📄convertOption.js
            📄convertUsableDate.js
            📄IsCreatedWithinDay.js
  • api : CarService 에서 컴포넌트 단에서 함수 호출만 가능하도록 최대한 관심사를 분리했습니다.
  • context : context에서 사용하는 actionTypes, CarListProvider, CarListReducer 로 분리해서 썼습니다.
  • utils : 상수와 자주 쓰이는 함수들을 따로 분리해놨습니다.

🔒 팀 규칙

커밋 규칙

commit message 규칙

⭐ feat : 새로운 기능에 대한 커밋

🛠 fix : 버그 수정에 대한 커밋

🧱 build : 빌드 관련 파일 수정에 대한 커밋

👏 chore : 그 외 자잘한 수정에 대한 커밋

⚒ refactor :  코드 리팩토링에 대한 커밋

🎨 style : 코드 스타일 혹은 포맷 등에 관한 커밋

✏ docs : 문서 수정에 대한 커밋

💡 ci : CI관련 설정 수정에 대한 커밋

🚫 제목 끝에 마침표 금지 ⚠ 무엇을 했는지 명확하게 작성

코딩 컨벤션 규칙

코딩 컨벤션

  • 컴포넌트의 ID사용은 지양한다.
  • react의 state는 여러개 사용시 최소 집합을 찾아 사용한다.
  • 컴포넌트의 이벤트에서 불필요한 익명함수를 사용하지 않는다. (예시: 함수의 인자가 event 하나인 경우)
  • 코드를 설명하는 주석은 가급적 사용하지 않는다.
  • 상수는 영문 대문자 스네이크 표기법(Snake case)를 사용한다.(예시: SYMBOLIC_CONSTANTS)
  • 반환 값이 불린인 함수는 'is'로 시작한다
  • const와 let은 사용 시점에 선언 및 할당한다.
  • 함수는 사용 전에 선언해야 하며, 함수 선언문은 변수 선언문 다음에 오도록 한다.
  • 이벤트 핸들러는 'on'으로 시작한다.
  • 한 줄짜리 블록일 경우라도 {}를 생략하지 않으며 명확히 줄 바꿈 하여 사용한다.
Lint, Formatter 규칙

**Prettier, ESLint 규칙 **

prettier
  printWidth: 100, // printWidth default 80 => 100 으로 변경
  singleQuote: true, // "" => ''
  arrowParens: 'avoid', // arrow function parameter가 하나일 경우 괄호 생략
ESLint
  printWidth: 100, // printWidth default 80 => 100 으로 변경
  singleQuote: true, // "" => ''
  arrowParens: 'avoid', // arrow function parameter가 하나일 경우 괄호 생략

공통 Lib

  • eslint
  • eslint-config-prettier
  • husky
  • prettier

production

  • react-router-dom
  • axios

dev

  • styled-components

Assignment 1

  • Figma 상의 디자인 및 기능 구현
    • 모바일 웹 기준으로 제작
    • 450px ~ 360px까지 고려해서 제작
  • 필수 요구 사항
    • Figma 상의 디자인 및 기능 구현
Chrome Whale
크롬반응형 웨일반응형

Assignment 2

  • 차량 리스트
차량이 없을 때 차량을 불러올 때
image image

차량이 없을 때, 차량을 불러올 때

  • CarList.jsx

const CarList = () => {
const state = useCarState();
const getAllCarList = useAllCarList();
const { carList, isLoading } = state;
useEffect(() => {
getAllCarList();
}, []);
if (isLoading) {
return (
<Wrapper>
<MsgBox txt={'불러오는 중'} />
</Wrapper>
);
} else if (carList.length === 0) {
return (
<Wrapper>
<MsgBox txt={'차량이 없습니다.'} />
</Wrapper>
);
}
return (
<>
{carList &&
carList.map(car => {
return (
<>
<CarItem car={car} key={car.id} />
</>
);
})}
</>
);
};

차량을 불러올 때와 차량이 없을 때 텍스트를 다르게 해서 MsgBox 컴포넌트를 재사용했습니다. Context API에서 전역으로 관리하는 isloading 변수가 true일 때 불러오는 중을 표시하고, Context API에서 전역으로 관리하는 carList 변수의 배열의 길이가 0일 때 차량이 없습니다를 표시합니다.


Assignment 3

  • 차량 상세 image

export default function CarItem({ car }) {
const navigate = useNavigate();
const isLatest = IsCreatedWithinDay(car.createdAt);
return (
<Wrapper onClick={() => navigate(`/car/${car.id}`, { state: car })}>
<TextWrapper>
<Title>{car.attribute.brand}</Title>
<Title>{car.attribute.name}</Title>
<ContentsWrapper>
<Contents>
{car.attribute.segment} / {car.attribute.fuelType}
</Contents>
<Contents>{convertAmount(car.amount)}원 부터</Contents>
</ContentsWrapper>
</TextWrapper>
<ImgWrapper>
{isLatest && <Badge content={'신규'} />}
<Img src={car.attribute.imageUrl} />
</ImgWrapper>
</Wrapper>
);
}


Assignment 4 (추가 구현 사항) - SEO

import React from 'react';
import { Helmet } from 'react-helmet-async';
import { useLocation } from 'react-router-dom';
import { convertAmount } from '../../utils/function/convertAmount';
const SEOMetaTag = () => {
const { state } = useLocation();
if (state)
return (
<Helmet>
<title>{`${state.attribute.brand} ${state.attribute.name}`}</title>
<meta property="og:type" content="website" />
<meta property="og:title" content={`${state.attribute.brand} ${state.attribute.name}`} />
<meta property="og:description" content={`월 ${convertAmount(state.amount)} 원`} />
<meta property="og:image" content={state.attribute.imageUrl} />
<link rel="canonical" href={state.url} />
</Helmet>
);
};
export default SEOMetaTag;

image

  • react-helmet을 적용하면 메타태그들과 title이 동적으로 바뀌는 것을 볼 수 있었습니다.
  • SNS 공유 할 때는 SNS 크롤러 봇이 CSR의 메타태그들을 인식하지 못했습니다.
  • nextjs나 cloudfront + Lambda로 SEO를 적용해볼 예정입니다.

Assignment 5 (추가 구현 사항)

5-1. 태그 스와이퍼 기능 구현

스와이퍼기능구현

const SwiperTags = props => {
const { segment, fuelType } = useCarState();
return (
<Wrapper>
<Swiper slidesPerView={5} spaceBetween={-1} initialSlide={2} centeredSlides={true}>
{segmentOption &&
segmentOption.map((option, idx) => (
<SwiperSlide key={idx}>
<Button
key={idx}
value={option.value}
content={option.name}
active={option.value === segment ? 'active' : ''}
onClick={props.onSegClickHandler}
/>
</SwiperSlide>
))}
{fuelTypeOption &&
fuelTypeOption.map((option, idx) => (
<SwiperSlide key={idx}>
<Button
key={idx}
value={option.value}
content={option.name}
active={option.value === fuelType ? 'active' : ''}
onClick={props.onFuelClickHandler}
/>
</SwiperSlide>
))}
</Swiper>
</Wrapper>
);
};

  • Swiper.js 리액트 버전을 사용하여 구현하였습니다.
  • 한 슬라이드에 5개가 보이도록 구현하고, 초기값을 3번째 인덱스에 존재하는 태그로 지정해주었습니다.

5-2. 각종 util함수와 상수들

차종, 연료 변환

image

실제로 서버 API로 응답받는 데이터와 요구사항으로 주어지는 화면상의 데이터 형태가 달랐습니다.
사용자가 보기 쉽게 구현해야만 했습니다.


segmentOption, fuelTypeOption 함수

const segmentOption = [
{ value: '', name: '전체' },
{ value: 'C', name: '소형' },
{ value: 'D', name: '중형' },
{ value: 'E', name: '대형' },
{ value: 'SUV', name: 'SUV' },
];
export default segmentOption;

  • option을 value, name으로 나눠서 API로 받아오는 값을 value, 화면상에 보여지는 값을 name으로 분류하여 파일을 만들어서 따로 관리하였습니다.

import fuelTypeOption from '../constant/fuelTypeOption';
import segmentOption from '../constant/segmentOption';
export const segConvertName = segVal => {
const result = segmentOption.filter(seg => {
if (seg.value === segVal) {
return seg.name;
}
return null;
});
return result[0].name;
};
export const fuelConvertName = fuelVal => {
const result = fuelTypeOption.filter(fuel => {
if (fuel.value === fuelVal) {
return fuel.name;
}
return null;
});
return result[0].name;
};

  • segment와 fuelType option의 value를 이름으로 변환해주는 함수이다.
  • 서버에서 받은 값을 화면 상에 UI로 보기 쉽게 변환해주는 함수입니다.
  • segVal은 서버에서 받아오는 segment값이므로 segmentOption 배열에 filter로 value와 일치하면 name을 반환하게끔 코드를 짰습니다.

돈 단위 변환

image

export const convertAmount = amount => {
return amount.toLocaleString('ko-KR');
};


이용 가능 날짜 변환

const days = ['일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일'];
export const convertUsableDate = dates => {
const startDate = new Date(dates);
const month = startDate.getMonth() + 1;
const date = startDate.getDate();
const dayName = days[startDate.getDay()];
return month + '월 ' + date + '일' + ` (${dayName}) 부터`;
};


5-3. GlobalStyle과 Theme으로 스타일 관리

GlobalStyle

import { createGlobalStyle } from 'styled-components';
import reset from 'styled-reset';
const GlobalStyle = createGlobalStyle`
${reset}
* {
box-sizing: border-box;
}
body {
background-color: #ffffff;
font-family: "Inter";
}
body * {
background-color: transparent;
letter-spacing: -0.5px;
}
h1, h2, h3, h4, h5 {
font-family: 'Roboto', sans-serif;
}
:root {
font-size: 10px;
}
a {
text-decoration: underline;
cursor: pointer;
}
/* Layout */
html {
max-width: 450px;
margin: 0 auto;
}
`;
export default GlobalStyle;

  • styled-reset을 이용해서 전체 style을 초기화 시켰습니다.
  • :root로 전체 font-size를 10px로 주고, 10px이 1rem이 되도록 만들었습니다.
  • 과제 요구사항으로 주어진 Inter font를 적용했습니다.
:root {
    font-size: 10px;
  }

Theme

const theme = {
mobile_s: '360px',
mobile_l: '450px',
/* color Theme */
mainColor: '#0094FF',
gray: '#D9D9D9',
black: '#000000',
white: '#FFFFFF',
};
export default theme;

  • 과제 요구사항으로 주어진 색상과 반응형 크기를 Theme에서 관리하여 styled-components에서 props로 받아서 사용할 수 있도록 했습니다.

💡 logic

1️⃣ logic one

https://github.com/Wanted-07-team-9/pre-onboarding-7th-2-1-9/blob/420610c33258cf72482fe9e3f5c77ccc89ce2249/src/App.js#L1-L3

프리온보딩 9팀

👑 권준 김경훈 김수정
@jun-05 @tirhande @crystal993
송슬기 오나래 이창훈 전이진
@songseul @NR0617 @anotheranotherhoon @pongdang