Skip to content

JaydenLee1116/quarter-fe

 
 

Repository files navigation

🔨 프로젝트 소개

🍦 베스트 라빈스

선택하기 어려운 수많은 아이스크림 조합들, 우리가 추천해줄게!

배스킨 라빈스 아이스크림을 모르거나 조합을 결정하기 힘든 사람들을 위한 재료 기반 아이스크림 추천 서비스

🔨 프로젝트 배포 혹은 데모

배포: 링크

🔨 프로젝트 주요 기능

  • 메인 페이지의 캐로셀 슬라이더를 통해 독특한 아이스크림 조합을 추천받을 수 있습니다.
    • 데스크탑 환경에서는 화살표 버튼을, 모바일 환경에서는 스와이프를 통해 슬라이더를 이동할 수 있습니다.
  • 라이트 모드와 다크 모드를 지원합니다.
  • 원하는 사이즈와 재료들을 선택하여 아이스크림 조합을 추천받을 수 있습니다.

🔨 프로젝트 구조 및 기술 스택

1. 프로젝트 구조

📦
├─ .eslintignore
├─ .eslintrc.json
├─ .gitignore
├─ .husky
├─ .prettierignore
├─ .prettierrc.json
├─ .storybook
├─ README.md
├─ package-lock.json
├─ package.json
├─ public
│  ├─ icons
│  └─ index.html
├─ src
│  ├─ App.tsx
│  ├─ api
│  ├─ assets
│  │  └─ icons
│  │     ├─ cardColor
│  │     ├─ character
│  │     ├─ common
│  │     ├─ loading
│  │     └─ logo
│  ├─ components
│  │  ├─ Carousel
│  │  ├─ FootButton
│  │  ├─ FootNavBar
│  │  ├─ MainGlobalNavBar
│  │  ├─ RecipeCard
│  │  ├─ SubGlobalNavBar
│  │  └─ Text
│  ├─ constants
│  ├─ contexts
│  │  ├─ ModeContext
│  │  └─ UserItemContext
│  ├─ index.tsx
│  ├─ pages
│  │  ├─ AuthPage
│  │  ├─ ErrorPage
│  │  │  └─ ErrorBoundary
│  │  ├─ IngredientSelectPage
│  │  ├─ LoadingPage
│  │  ├─ RecipePage
│  │  ├─ ResultRecommendPage
│  │  ├─ SizePickPage
│  │  └─ UserPage
│  ├─ router
│  ├─ service-worker.ts
│  ├─ serviceWorkerRegistration.ts
│  ├─ services
│  │  ├─ useGetIngredients
│  │  ├─ useGetRecipe
│  │  ├─ useGetRecommendations
│  │  ├─ useGetSizes
│  │  └─ usePostRecipe
│  ├─ styles
│  │  ├─ GlobalStyles.styled.ts
│  │  └─ theme.styled.ts
│  └─ utils
│     └─ isMobile
└─ tsconfig.json

©generated by Project Tree Generator

2. 주요 기술 스택

목적 이름 버전
언어 TypeScript ^4.9.5
UI React ^18.2.0
스타일 styled-components ^6.0.8
상태관리 Tanstack Query ^4.35.3
테스트 Storybook ^7.4.2
환경 설정 prettier ^3.0.3
환경 설정 eslint ^8.49.0
환경 설정 husky ^8.0.0
환경 설정 lint-staged ^14.0.1

🔨 프로젝트 특이사항

1. Carousel 컴포넌트 구현

기획과 디자인에서 요구하는 캐러셀 슬라이드 라이브러리가 존재하지 않아 서비스에 맞는 컴포넌트를 직접 구현하였습니다.

아래는 컴포넌트 코드 일부입니다.

const Carousel = ({ children, currentSlideIndex, changeSlide }: CarouselProps) => {
  const touchStartPositionX = useRef(0);
  const touchEndPositionX = useRef(0);
  const handleSlideClick = (index: number) => {
    changeSlide(index);
  };

  useEffect(() => {
    const timer = setInterval(() => {
      if (currentSlideIndex >= React.Children.count(children) - 1) {
        changeSlide(0);
        return;
      }
      changeSlide(currentSlideIndex + 1);
    }, CAROUSEL_SLIDE_AUTOPLAY_INTERVAL);
    return () => {
      clearInterval(timer);
    };
  }, [currentSlideIndex, children]);

  // ... 중략

const Slide = ({ children, isCurrentSlide }: SlideProps) => {
  return <S.SlideWrapper $isCurrentSlide={isCurrentSlide}>{children}</S.SlideWrapper>;
};

Carousel.Slide = Slide;

export default Carousel;
  • Carousel 컴포넌트는 Slide 컴포넌트를 자식으로 받아서 렌더링합니다.
    • 이 때, Carousel 컴포넌트를 사용하는 곳에서 Carousel과 Slide 컴포넌트 간의 props 전달을 제거하고 좀더 직관적인 Carousel Slide 컴포넌트를 사용하기 위해 Compound Pattern을 적용하였습니다.
  • Carousel 컴포넌트는 currentSlideIndexchangeSlide를 props로 받아서 현재 슬라이드 인덱스를 관리하고 슬라이드를 변경할 수 있습니다.
    • currentSlideIndex를 통해 유저가 중앙에 놓고 보는 슬라이드 외의 양 옆의 슬라이드에는 크기가 작거나 투명도가 다르게 하는 등의 UI를 제공할 수 있게 하였습니다.
    • changeSlide라는 Slide를 변경하는 방법을 props로 전달함으로써 Slide를 변경하는 방법에 확장성을 부여하고자 했습니다.

2. 상태 관리

아이스크림에 대한 조건을 선택하는 과정에서 선택한 조건을 다른 페이지에서도 사용해야 하는 상황이 발생하였습니다. 이를 해결하기 위해 useState와 Context API를 사용하여 상태를 관리하였습니다.

// ... 중략

export const UserItemProvider = ({ children }: Props) => {
  const [userItem, setUserItem] = useState<UserItem>({
    size: { id: -1, value: -1 },
    ingredientIds: [],
  });

  const changeSize = ({ id, value }: Size) => {
    setUserItem({
      ...userItem,
      size: { id, value },
    });
  };

  const changeIngredientIds = (ingredientIds: number[]) => {
    setUserItem({
      ...userItem,
      ingredientIds,
    });
  };

  const initUserItem = () => {
    setUserItem({
      size: { id: -1, value: -1 },
      ingredientIds: [],
    });
  };
  return (
    <userItemContext.Provider value={userItem}>
      <changeSizeContext.Provider value={changeSize}>
        <changeIngredientIdsContext.Provider value={changeIngredientIds}>
          <initUserItemContext.Provider value={initUserItem}>
            {children}
          </initUserItemContext.Provider>
        </changeIngredientIdsContext.Provider>
      </changeSizeContext.Provider>
    </userItemContext.Provider>
  );
};

3. 횡단 관심사 분리

api 통신을 위한 axios 인스턴스를 따로 만들어 횡단 관심사를 분리하고 직접 데이터를 가져오는 비즈니스 로직의 경우 react query를 사용하여 분리하여 관리하고자 하였습니다.

apiClient.tsx

import axios from 'axios';

import { API_BASE_URL } from './apiConfig';

export const axiosFetch = axios.create({
  baseURL: API_BASE_URL,
  headers: {
    'Content-Type': 'application/json',
  },
});

리액트 쿼리 훅 사용 예시(useGetIngredients.tsx)

import { useQuery } from '@tanstack/react-query';
import { axiosFetch } from '../../api/apiClient';

type Ingredient = {
  id: number;
  name: string;
  imageUrl: string;
  flavorIdList: number[];
};

type Response = {
  ingredients: Ingredient[];
  message: string;
};

export const useGetIngredients = () => {
  return useQuery<Response>(['ingredients'], async () => {
    const { data } = await axiosFetch('/ingredients');
    const { body: ingredients, message } = data;
    return { ingredients, message };
  });
};

4. 스토리북 배포

component에 대한 스토리를 작성하고 chromatic을 사용하여 배포하여 디자이너와 실시간 양방향 소통을 시도해보았습니다.

5. 초기 기획 및 디자인 참여

기획자와 디자이너와의 직접적인 협업은 처음 경험하여, 적극적으로 프로젝트 초기 기획과 디자인에 참여하고자 하였습니다.

6. PWA(Progressive Web App) 적용

사용자로 하여금 앱과 같은 경험을 제공하기 위해 PWA를 적용하였습니다.

🔨 프로젝트 회고

10일이라는 기간동안 사람들에게 보여줄 수 있는 서비스를 만들어보자는 목표를 가지고 프로젝트를 진행하였습니다. 이를 위해 최대한 병렬적으로 기획, 디자인 그리고 개발을 진행하고자 했습니다. 대부분의 프로젝트 경험들이 잘 짜여진 기획과 디자인을 바탕으로 개발을 했던 것들뿐이라 기획과 디자인과 함께 진행되는 과정이 많이 초조하기도 하고 마음이 급해지기도 했습니다. 한편으로는 이번 프로젝트를 통해 기획과 디자인에 대한 이해도를 높이고, 개발자로서 기획과 디자인에 대한 적극적인 참여가 얼마나 중요한지를 배울 수 있었습니다.

1. 좋았던 점

  • 기획자, 디자이너와의 직접적인 협업을 통해 조금은 현업에서의 프로세스를 경험해볼 수 있었다고 생각합니다.
  • MVP를 우선으로 개발하고 지속적으로 기능을 추가하는 방식으로 개발을 진행하여 빠르게 서비스를 만들어볼 수 있던 점이 좋았습니다.

2. 아쉬운 점

  • 짧은 시간이라 기술적으로 많은 고민을 하지 못했습니다.
  • 적용해보고 싶었던 기술들이 많았지만 숙련도 및 기간 내 완성에 초점을 맞추어 적용하지 못한 점이 아쉬웠습니다.

3. 앞으로의 방향

  • 사실 처음부터 Next.js로 프로젝트를 진행하고 싶었습니다. 왜냐하면 실제 많은 사용자를 두고 운영해보고 싶은 서비스인만큼 SEO에 강하게 만들고 싶었기 때문입니다. 해서 현재는 기능 추가보다 Next.js로 마이그레이션을 진행 중에 있습니다.
  • 더 많은 기능을 추가하려합니다. 예를 들어, 아이스크림 조합을 공유할 수 있는 기능, 아이스크림 조합을 저장할 수 있는 기능, 가까운 아이스크림 매장을 보여주는 지도 등을 추가하려고 합니다. 나아가 사람들끼리 아이스크림 조합을 공유하고, 평가할 수 있는 게시판을 만들어보고 싶습니다.

🔨 만든이

Profile Contact
이메일: vv55adss@gmail.com
이력서: Jayden's Resume
블로그: Jayden {do: smite}

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 94.1%
  • HTML 5.7%
  • Shell 0.2%