🔗 배포 링크
ID: catlendar@admin.com
PW: qwer1234!
캣린더(Catlendar)는 고양이를 테마로 한 투두 리스트 및 일정 관리 앱으로,
사용자들에게 운세를 통한 영감과 긍정적인 목표 설정을 돕는 데 중점을 둔 서비스입니다.
| 🔗 팀장 최지완 | 🔗 김소리 | 🔗 류경민 | 🔗 장성우 | 🔗 한동수 |
|---|---|---|---|---|
| 이름 | 내용 |
|---|---|
| 공통 | - 주제 선정, 기술 스택 및 협업툴 선정 |
| 최지완 | - AWS EC2서버 증설 및 운영체제 설정 - 데이터베이스 설계 및 구축 - RESTful API 설계 및 개발 - spring security 인증 및 보안 기능 구현 - Axios 라이브러리 설치 및 설정, API 호출을 통한 데이터 통신 구현 - Recoil을 활용하여 회원정보의 전역 상태관리 구현 - Landing, 로그인, 회원가입 페이지 등의 반응형 구현 리팩토링 - API 엔드포인트 및 요청/응답 API문서 작성 - 회의 내용, 결정 사항, 업무 분담 등을 기록한 회의록 작성 |
| 김소리 | - react-calendar 라이브러리와 연동하여 일정 관리 컴포넌트 및 기능 구현 - Recoil atom을 통해 할 일 완료 여부 데이터 전역 관리 - Recoil selector를 통해 할 일 목록 정렬 로직 구현 - Chart.js를 통한 일정 완료 시각화로 사용자 경험 개선 - 반응형 레이아웃(모바일, 데스크탑) 구현으로 서비스 리팩토링 - 웹 접근성 및 SEO 개선 |
| 류경민 | - Figma를 사용한 UI 디자인 및 필요한 이미지 작업 - 컴포넌트 디자인 모듈화를 통한 일관된 디자인과 높은 재사용성 제공 - 운세 API를 연동하여 사용자의 생년월일에 맞는 운세 정보 제공 - Recoil을 사용하여 운세 타이틀 및 내용 전역관리 - 헤더 및 운세 페이지 반응형으로 개선 |
| 장성우 | - eslint, tsconfig, prettier 설정 - UI 개발 효율성을 위해 자주 사용되는 색상, 폰트 크기를 CSS 변수로 정의 - profile 페이지 반응형 작업 - 전역 상태 관리: Recoil을 사용해 탭, 할 일 목록, 모달 상태를 전역으로 관리 - 서버 상태 관리: Reqct-Query를 사용하여 서버의 즐겨찾기 데이터 관리 |
| 한동수 | - 프로필, 설정, 회원정보 수정 페이지 화면 구현 및 기능 작업 - Chart.js를 활용하여 주별로 할 일 달성률 보여주는 기능 구현 - Tanstack-Query의 useQuery, useMutate를 사용하여 서버 데이터를 캐싱하여 속도를 높임 - 성능 개선 리팩토링 및 반응형 구현 - Recoil를 이용해 Todo Data를 전역 데이터 관리 - 커스텀 훅을 통해 중복되는 코드 줄이고, 컴포넌트의 재사용성 높임 |
| 주차 | FRONT | BACK |
|---|---|---|
| 1-2주차 | - 주제 선정, 기술 스택 및 협업툴 선정, 컨벤션 정리 - Figma UI 디자인 작업, 기능 요구사항 명세 |
- AWS EC2서버 증설 및 운영체제 설정 - 데이터베이스 설계 및 구축 - RESTful API 설계 및 개발 |
| 3-4주차 | - 페이지별 UI, 컴포넌트 기능 구현 - API 연동 구현 |
- 인증 및 보안 기능 구현 - API 문서화 작성 - 외부 서비스와의 통신을 위한 API 연동 |
| 5-6주차 | - 기능별 테스트 후 테스트 케이스 작성 및 보완 - 에러 공유 및 해결 |
- 에러 핸들링 및 디버깅 - 성능 최적화 |
| 7-8주차 | - 성능 개선 리팩토링 및 반응형 구현 - 웹접근성 문제 해결 |
👉🏻 로고 클릭 시 해당 링크로 이동합니다!
기술 스택 상세 정보
- Cloud
서비스: AWS
운영체제: Ubuntu
인스턴스 유형: EC2 t2.micro
스토리지: 30GB
- BackEnd
언어: Java
DB: MariaDB 11.2.2
JDK: 11
프레임워크:
Spring 5.3.23
Spring Boot 2.7.5
Mybatis 2.2.0
빌드 도구: Gradle 8.5
IDE: IntelliJ
프로젝트 구성 도구: Lombok
- FrontEnd
언어: HTML, CSS, TypeScript
라이브러리: React.js, Styled-component, Tanstack-Query(React-Query),chart.js, react-calendar, react-router-dom, axios
상태관리: Recoil
IDE: VScode
| 구분 | 내용 |
|---|---|
| BACK | - 데이터베이스 테이블과 컬럼명은 스네이크 케이스를 사용 - 클래스명, 메소드명은 카멜 케이스를 따르며, 변수명은 소문자로 시작하여 카멜 케이스를 사용 - 스네이크 케이스 작성의 번거로움을 방지하기 위해 mybatis mapUnderscoreToCamelCase 세팅 필요 - 구현한 기능에 대해서는 주석 작성 |
| FRONT | - 브랜치 네이밍 규칙 준수 및 Pull Request 커밋 목적 설명을 위한 이모지 사용 - Styled-components 컨벤션 준수, 컴포넌트 및 스타일 네이밍 규칙 지정 - 구현한 기능에 대해서는 주석 작성 |
| 제목 | 설명 |
|---|---|
| ✨ feat | 기능 추가, 삭제, 변경 |
| 🐛 fix | 버그, 오류 수정 |
| 📝 docs | readme.md, json 파일 등 수정, 라이브러리 설치 (문서 관련, 코드 수정 없음) |
| 🎨 style | CSS 등 사용자 UI 디자인 변경 (제품 코드 수정 발생, 코드 형식, 정렬 등의 변경) |
| ♻️ refactor | 코드 리팩토링 |
| ⚙️ Config | npm 모듈 설치 등 |
| 🚚 rename | 파일 또는 폴더 명을 수정하거나 옮기는 작업만인 경우 |
| 🗑 remove | 파일을 삭제하는 작업만 수행한 경우 |
| ✅ Check | 작업이 완료된 dev브랜치의 코드를 main브랜치로 머지 |
| 🔧 perform | main브랜치로 머지 중, 에러 발생하여 재시도 해야 하는 pull request |
| 🚑 Closed | 닫힌 pull request |
extends:
- 'airbnb': Airbnb의 JavaScript 스타일 가이드 적용
- 'airbnb/hooks': React Hooks에 대한 Airbnb 규칙을 적용
- 'plugin:@typescript-eslint/recommended': TypeScript ESLint 플러그인 권장 규칙
- 'plugin:prettier/recommended': Prettier와 ESLint를 함께 사용할 때 충돌을 방지하고 통합을 용이하게 하는 권장 설정
rules:
- 'react/jsx-filename-extension': JSX 문법을 사용하는 파일의 확장자로 .jsx 또는 .tsx만을 허용
- 'react/react-in-jsx-scope': React17 이상에서 JSX를 사용할 때 React를 스코프에 포함할 필요가 없으므로 이 규칙을 비활성화
- 'react/require-default-props': 컴포넌트의 defaultProps 정의를 강제하지 않습니다.
- 'import/extensions': import 문에서 파일 확장자를 명시하지 않아도 되도록 설정합니다. .js, .jsx, .ts, .tsx 확장자는 무시
- '@typescript-eslint/no-inferrable-types': 타입 추론이 가능한 경우 명시적인 타입 선언을 금지
- '@typescript-eslint/no-explicit-any': any 타입의 사용을 금지하지 않습니다.
- '@typescript-eslint/no-unused-vars': 사용되지 않는 변수가 있을 경우 경고 발생
- 'prefer-const': 재할당되지 않는 변수에 대해 const 사용을 권장
- 'import/prefer-default-export': 단일 내보내기를 할 때 기본 내보내기를 사용하도록 강제하는 규칙 비활성화
- 'import/no-unresolved': 해결되지 않는 import에 대한 경고를 비활성화
settings:
- 'import/resolver': 모듈 해석 방법을 정의. 여기서는 TypeScript 설정을 추가하여 ESLint가 TypeScript의 경로 및 별칭을 올바르게 해석할 수 있도록 설정
- react: ESLint 플러그인 리액트가 사용할 리액트 버전을 자동으로 감지하도록 설정합니다.
{
"semi": true,
"singleQuote": true,
"printWidth": 100,
"tabWidth": 2,
"trailingComma": "all",
"jsxBracketSameLine": false
}
Wrapper: ~Wrapper
예시: HeaderWrapper, NavbarWrapper
div 태그: ~Box
예시: InfoBox, ContentBox
section 태그: ~Section
ex) InfoSection, ContentSection
ul 태그: ~List
ex) InfoList, ContentList
li 태그: ~Item
ex) InfoItem, ContentItem
중첩된 스타일링은 최대 2단계까지 허용됨
ex) - list { li{ a{} } }는 허용됨
li 내에 스타일링할 요소가 3개 이상이면 li를 Item으로 추상화
기본 컴포넌트: NewsCard.tsx
스타일 컴포넌트: NewsCard.styled.tsx
컴포넌트 명: PascalCase
클래스 명: kebab-case
변수명, 함수명: camelCase
Netlify는 기본적으로 HTTPS를 사용하지만, AWS서버는 SSL 인증을 받지 않아 HTTP를 사용했습니다.
이는 배포 환경에서 프론트와 서버 간 통신을 금지하는 네트워크 정책으로 문제가 발생했습니다. 서버에 SSL 인증을 받아 HTTPS로 변경하는 해결책은 비용과 서버 간 통신 문제로 인해 어려웠고, Netlify에서 HTTPS를 HTTP로 변경하는 것도 불가능했습니다.
이에, 로컬 환경에서는 기존 서버 URL을 사용하고, Netlify 배포 환경에서는 baseURL에 API를 지정하여 public/_redirects에서 프록시를 통해 요청을 보내는 방식으로 문제를 해결했습니다.
👉 참고 사이트
기존의 모바일 중심 개발에서 반응형 레이아웃으로 개선하면서, 화면 크기에 따라 다른 라우터와 컴포넌트들을 표시해야 했습니다.
이를 CSS의 media query만으로 처리할 수 없어서, 라우트 컴포넌트에서 렌더링 할 컴포넌트들을 초기 처리를 한 뒤 각 컴포넌트들의 스타일에 대해 반응형 작업이 필요하다고 생각했습니다.
따라서 저는 라우트 컴포넌트 내에서 useEffect 훅을 사용하여 window.innerWidth로 화면 크기 변화를 감지했으며, 데스크탑 여부를 flag로 주어 업데이트 했습니다. 컴포넌트가 마운트 해제되거나 업데이트되기 전 useEffect의 클린업 함수가 실행될 때, 창 크기 조정 이벤트 리스너를 제거하여 메모리 누수를 방지하였습니다.
export default function Router() {
// 내용에 해당하는 코드 외에는 생략하였습니다.
const [isDesktop, setIsDesktop] = useState(false);
useEffect(() => {
const handleResize = () => {
if (window.innerWidth < 960) {
setIsDesktop(false);
} else {
setIsDesktop(true);
}
};
// 창 크기에 따라 isDesktop 결정
handleResize();
// 창 크기 조정 이벤트 수신
window.addEventListener('resize', handleResize);
return () => {
// 리턴되어 클린업 함수 호출 시(언마운트 혹은 업데이트 시), 이벤트 리스너 제거
window.removeEventListener('resize', handleResize);
};
}, [isDesktop]);
이 화면 크기 정보로 결정되는 isDesktop이라는 flag를 컴포넌트의 props로 전달하여 필요한 처리를 하였습니다. 이 값에 따라 컴포넌트 내에서 보여줄 컴포넌트 선별 및 스타일링을 했습니다.
return (
<BrowserRouter>
<Routes>
<Route path="/landing" element={<Layout main={<LandingPage />} />} />
{['/', '/home'].map((path) => (
<Route
key={path}
path={path}
element={
<>
{isLoggedIn === '' ? (
<Layout main={<LandingPage />} />
) : isLoggedIn ? (
// 컴포넌트에 isDesktop을 props로 넘겨줌
<Layout
main={<HomePage isDesktop={isDesktop} />}
navbar={<NavBar isDesktop={isDesktop} />}
/>
) : null}
</>
}
/>
))}
<Route
path="/calendar"
element={
<>
{isLoggedIn === '' ? (
<Layout main={<LandingPage />} />
) : isLoggedIn && !isDesktop ? (
// 컴포넌트에 isDesktop을 props로 넘겨줌
<Layout main={<CalendarPage />} navbar={<NavBar isDesktop={isDesktop} />} />
) : isLoggedIn && isDesktop ? (
// 데스크탑 사이즈인 경우 /calendar에서 /home으로 리다이렉트 되도록 설정
<Navigate to="/home" />
) : null}
</>
}
/>
이러한 접근 방식을 통해 화면 크기에 따라 적절한 라우팅 및 UI를 제공할 수 있었습니다.
운세 페이지에서는 Recoil을 사용하여 운세 데이터와 선택된 탭의 정보를 전역적으로 관리하고 있습니다. 그러나 Recoil을 적용한 후에 페이지를 새로고침할 때마다 운세 데이터가 초기화되는 문제가 발생했습니다. 이 문제를 해결하기 위해 recoil-persist 라이브러리를 적용했습니다.
import { atom } from 'recoil';
import { recoilPersist } from 'recoil-persist';
const { persistAtom } = recoilPersist();
새로고침이 되어도 상태가 유지되도록 하기 위해, 해당 atom에 effects_UNSTABLE 속성을 추가하여 recoil-persist를 활성화하였습니다.
export const fortuneDataAtom = atom<fortuneDataAtom>({
key: 'fortuneData',
default: { fortuneTitle: '', fortuneDesc: [] },
effects_UNSTABLE: [persistAtom],
});
이렇게 하면 Recoil이 페이지를 새로고침할 때 초기 상태를 가져와서 렌더링 하는 것이 아니라, 데이터를 Localstage에 저장하여 이전 상태를 복원합니다. 이를 통해 사용자가 페이지를 새로고침해도 운세 데이터가 유지될 수 있습니다.
