[배포 URL]
[계정]
- 🧑🏻💻 id: [email protected]
- 🔐 password: myroom
-
🏠금방내방은 다양한 사용자들의 방구석 인테리어 트렌드 및 일상을 공유할 수 있는 SNS서비스입니다.
-
'금방'
,'내방'
두 단어의 합성어로 우리의 서비스를 통해 언제든지 쉽고 빠르게 '내방'을 탈바꿈 할 수 있다는 의미를 가지고있습니다. -
게시자는 자신의 방을 사진을 통해 공유할 수 있으며, 다른 사용자와 좋아요 및 댓글을 주고 받을 수 있습니다.
-
다른 사용자들과의 팔로우를 통해 자유로운 댓글 작성 및 홈 피드를 공유할 수 있습니다.
FE 김예지 | FE 배기훈 | FE 전서희 | FE 황혜명 |
---|---|---|---|
GitHub: yeeed711 blog: yeeed_log |
GitHub: qorlgns1 | GitHub: SeoHee3478 blog: jeon0768 |
GitHub: CosmicLatte009 |
[개발 환경]
- Front-End: React, React-router, Hooks, Styled-components
- Back-End: 제공된 API 사용
- 서비스 배포 환경: 🔗 vercel
- 버전 관리 및 이슈: GitHub-Wiki | GitHub-Issues
- Git-Flow
react-router-dom": "^5.2.0"
styled-components": "^5.3.5"
react": "^18.2.0"
axios": "^0.27.2"
[배포 URL]
- 프로젝트 회의 : 6.18(토) ~ 6.26(일)
- 코드 컨벤션, 기술스택 논의
- 아이디어 회의 및 협업 툴 결정
- 스크럼 기록 및 스프린트 계획
- 기능구현 : 6.27(월) ~ 7월 30(토)
├── 📁 public
│ ├── favicon.png
│ └── index.html
└── 📁 src
├── 📁 apis
│ ├── feedApi.js
│ ├── followApi.js
│ ├── imageApi.js
│ ├── index.js
│ ├── postApi.js
│ ├── productApi.js
│ ├── profileApi.js
│ └── searchApi.js
├── 📁 components
│ ├── 📁 common
│ │ ├── 📁 button
│ │ │ ├── Button.jsx
│ │ │ └── LongButton.jsx
│ │ ├── 📁 input
│ │ │ ├── ErrorMessageBox.jsx
│ │ │ ├── InputBox.jsx
│ │ │ └── InputImageUploadBox.jsx
│ │ ├── 📁 modal
│ │ │ ├── AlertModal.jsx
│ │ │ ├── ModalContainer.jsx
│ │ │ └── ModalList.jsx
│ │ └── 📁 nav
│ │ ├── BottomNavMenu.jsx
│ │ ├── PostViewChangeNav.jsx
│ │ ├── TopNavBasic.jsx
│ │ ├── TopNavHome.jsx
│ │ ├── TopNavSearch.jsx
│ │ ├── TopNavUpload.jsx
│ │ └── 📁 item
│ │ └── BottomNavMenuItem.jsx
│ ├── 📁 email
│ │ └── EmailSignUp.jsx
│ ├── 📁 feed
│ │ ├── BasicFeed.jsx
│ │ └── FollowingPostList.jsx
│ ├── 📁 followProfile
│ │ ├── FollowProfileCard.jsx
│ │ └── FollowProfileList.jsx
│ ├── 📁 login
│ │ ├── LoginCard.jsx
│ │ └── LoginTitle.jsx
│ ├── 📁 post
│ │ ├── CommentAddBox.jsx
│ │ ├── CommentItem.jsx
│ │ ├── PostItem.jsx
│ │ └── PostList.jsx
│ ├── 📁 product
│ │ ├── ProductForm.jsx
│ │ ├── ProductListOnSales.jsx
│ │ └── ProductOnSales.jsx
│ ├── 📁 profileImg
│ │ ├── ProfileImg.jsx
│ │ └── UserProfileImg.jsx
│ ├── 📁 search
│ │ ├── SearchCard.jsx
│ │ └── SearchCardList.jsx
│ ├── 📁 splash
│ │ └── Splash.jsx
│ └── 📁 userProfile
│ ├── ProfileContainer.jsx
│ └── ProfileDataCard.jsx
├── 📁 pages
│ ├── FeedPage.jsx
│ ├── FollowPage.jsx
│ ├── JoinPage.jsx
│ ├── LoginHomePage.jsx
│ ├── LoginPage.jsx
│ ├── PostPage.jsx
│ ├── PostUploadPage.jsx
│ ├── ProductPage.jsx
│ ├── ProfileEditPage.jsx
│ ├── ProfilePage.jsx
│ ├── ProfileSettingPage.jsx
│ └── SearchPage.jsx
├── 📁 utils
│ ├── 📁 route
│ │ └── PrivateRoute.jsx
│ ├── defaultImage.js
│ └── userInfo.js
├── App.jsx
└── index.jsx
- splash 페이지, 로그인 페이지, 상대방 프로필 페이지
- 게시글 컴포넌트, 프로필 카드 컴포넌트
- axios 모듈화, 커스텀 라우터 개발
- 상단 네브바 컴포넌트, 판매중인 상품 컴포넌트, 팝업 모달 컴포넌트
- 회원가입 페이지, 프로필 페이지, 프로필 수정 페이지, 팔로잉/팔로우 페이지, 상품등록 페이지, 상품수정 페이지, 게시글 업로드 페이지
- 메인 피드 페이지, 검색 페이지
- 프로필 이미지 컴포넌트, 하단 네브바 컴포넌트
- 디자인 기획 및 에셋 제작
- 이메일 회원가입 페이지, 게시글 업로드 페이지, 게시글 상세 페이지
- 슬라이드 모달 컴포넌트, 버튼 컴포넌트, 댓글 컴포넌트
- 디자인 기획 및 에셋 제작
🔗 1) 홈
Splash | 회원가입 | 로그인 | 금방내방 피드 |
---|---|---|---|
서비스 접속시 처음에 보이는 화면입니다. |
이메일과 비밀번호, 사용자 이름, 계정 ID로 회원가입할 수 있습니다. |
이메일과 비밀번호로 로그인할 수 있습니다. |
팔로우한 사용자들의 게시물을 확인할 수 있습니다. |
검색 | 팔로우 및 팔로잉 | 상대방 프로필 | 프로필 수정하기 |
---|---|---|---|
다른 사용자들의 계정을 검색할 수 있습니다. |
자기 프로필 페이지에서 팔로워, 팔로잉한 계정을 확인할 수 있습니다 |
상대방 프로필을 정보를 확인할 수 있습니다. |
프로필 수정 페이지에서 사진, 이름, 소개를 수정할 수 있습니다. |
🔗 3) 게시글
게시글 작성 | 게시글 상세 | 게시글 수정 | 게시글 삭제 |
---|---|---|---|
사진을 등록하고, 글을 입력하여 게시글을 등록할 수 있습니다. |
게시글 상세 화면에서 댓글을 작성할 수 있습니다. |
게시글을 수정할 수 있습니다. | 게시글을 삭제할 수 있습니다. |
🔗 4) 상품
상품 등록 | 상품 수정 | 상품 삭제 |
---|---|---|
상품의 사진, 가격, 링크를 등록할 수 있습니다. |
상품의 정보를 수정할 수 있습니다. |
상품을 삭제할 수 있습니다. |
댓글 등록 | 댓글 삭제 | 좋아요 누르기 |
---|---|---|
댓글을 등록할 수 있습니다. | 자신이 작성한 댓글을 삭제할 수 있습니다. | 게시글에 좋아요 버튼을 클릭할 수 있습니다. |
기존에 페이지마다 useEffect를 이용해서 유저 정보를 확인했는데 이러한 중복코드가 발생해서 커스텀 라우터를 개발했습니다.
useEffect(() => {
const userInfo = JSON.parse(localStorage.getItem('userInfo'));
const token = userInfo?.token;
if (!token) {
props.history.push('/login');
return;
}
}, []);
페이지에 들어올 때 유저정보가 존재하지않다면 로그인 화면으로 보낼 수 있도록 구현했습니다.
export default function PrivateRoute({ children, ...rest }) {
const userInfo = getUserInfo();
return (
<Route
{...rest}
render={(props) =>
userInfo ? (
React.cloneElement(children, { ...props })
) : (
<Redirect
to={{
pathname: '/login',
state: { from: props.location },
}}
/>
)
}
/>
);
}
커스텀 라우터를 사용할 때는 아래와 같이 사용할 수 있습니다.
<PrivateRoute path='/profile' exact>
<ProfilePage />
</PrivateRoute>
기존에 Fetch를 이용해서 API 통신을 했는데, 중복된 코드가 너무 많이 생긴다고 생각했습니다.
async function handlePostDeleteRequest(token) {
const url = 'https://mandarin.api.weniv.co.kr';
const reqPath = `/post/${id}`;
try {
const res = await fetch(url + reqPath, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${token}`,
'Content-type': 'application/json',
},
});
const resData = await res.json();
return resData;
} catch (err) {
console.error('error');
}
}
그래서 axios를 도입하고, axios instance 만들어서 url과 header를 넣어놓았습니다.
export const axiosInstanceWithToken = axios.create({
baseURL: API_URL,
headers: {
'Content-Type': 'application/json',
},
});
그리고 axios의 인터셉터를 이용해서 request 보내기 이전에 토큰정보가 있는지 확인하고 토큰정보가 존재하면 요청을 보낼 수 있도록 개발을 했습니다.
axiosInstanceWithToken.interceptors.request.use((request) => {
// axiosInstanceWithToken에서 토큰이 존재하지 않으면 서버에 request를 보내지 않습니다.
const { token } = getUserInfo();
if (!token) {
throw new Error('토큰이 없습니다. 다시 로그인해주세요.');
}
// 토큰이 존재하면 headers에 토큰을 넣어서 서버에 request를 보냅니다.
request.headers.Authorization = `Bearer ${token}`;
return request;
});
axios api는 apis 폴더에 따로 분리해서 작성하고 axios를 사용하는곳에서는 사용할 모듈을 import하여 사용할 수 있도록 만들었습니다.
export const axiosGetPostDetail = async (postId) => {
try {
const { data } = await axiosInstanceWithToken.get(`/post/${postId}`);
return data;
} catch (error) {
console.error('axiosGetPostDetail error', error);
}
};
- React 사용법
- fetch와 axios를 이용한 API통신
- Git flow 브랜치 전략
- 깃 커밋 메시지 컨벤션
- 커밋 단위를 작게 가져가기.
- 이슈와 풀리퀘에 상세한 설명 달기.
- 필요한 경우 주석 달기.
- 로켓으로 머지 사인 날리기 🚀
- 고생한 것을 쉽게 끝내버릴까봐 기다려주는 팀원 🤔
- 단서가 될만한 한마디를 들으려면 어떻게 질문해야하는지부터가 막막한 팀원 😯
- 데일리스크럼을 통한 상황공유가 첫번째 해결책. 그 외에 코드 설명하는 영상을 찍어서 다시 볼 수 있게끔 해주거나 막연한 질문을 받아적어서 이해해보려는 노력 등이 오갔다.
- 약 200번 가량의 업로드 시도.
URL.createObjectURL
로는 프리뷰 이미지 만들고,readAsDataURL
로는 파일 읽고,formData
에 읽은 파일들 append하고,formData
에 들어간 배열 요소들을 연결하여 하나의 문자열로 API에 전송.- 가장 어려웠던 점 : 질문 후 힌트 코드를 서너번 받았음에도 내 코드에 적용을 못했고 어디서부터 다시 이해해야할지 막막했다.
- 레슨런: 번아웃이 가깝게 왔지만 끝까지 붙잡고 완벽하진 않아도 작동하게 만든 것은 성공. 더 끈질기게 질문하진 못해서 시간이 많이 소요된 것은 아쉬운 점. 팀프로젝트인만큼 속도 또한 퀄리티 요소로 생각해야했다.
-
배터디룸
- 팀원분 집에서 같이 지내며 프로젝트를 진행, 온전히 프로그래밍에만 집중할 수 있는 뜻깊은 경험.
- 언제든지 방문해서 함께 코딩 가능한 공간.
-
밤샘 줌
- 줌 시간 무제한 결제.
- 빠른 의사결정.
- 원격 제어를 통한 페어프로그래밍.
-
구글 스프레드 시트와 데일리 스크럼
- 아침 9시 30분에 오늘 진행할 일과 도움 청할 일을 간단히 공유.
- 구글 스프레드 시트를 이용하여 참여도 활성화.