이번 주차는 클론코딩을 해보는 주차였다.
어떠한 클론코딩을 해야지 실용적인 많은 기능들을 학습할 수 있을까 생각하다가
crud를 다 사용하고 좋아요, 친구추가 등 많은 유용한 기능들이 있는 페이스북으로 선정을 하였다.
프로젝트 기간 : 2021-07-19 ~ 2012-07-22
프로젝트명 : 페이스북 클론코딩
팀 구성 : 리액트 개발자 2명, 스프링부트 개발자 2명
사용 기술 : 리액트, 파이어베이스, 스프링부트
사용 IDE : VS Code
프로젝트 설명 : SNS로 유명한 페이스북의 클론코딩
기능 : 회원가입 및 로그인, 프로필 및 커버 이미지 업로드, 게시글 등록, 수정 및 삭제, 게시글 좋아요, 친구요청등
프로젝트 진행 순서 : 프로젝트 클론 코딩 대상 선정 -> 필요한 기능 선정 -> 필요한 api 별 정리(url 및 파라미터명) -> 프론트엔드 및 백엔드 개발자들 역할 분담 -> 기능 구현 -> 기능 테스트
구현된 api swagger - http://13.124.141.66/swagger-ui/index.html
// 회원가입 API
const signupAPI = (username, password, passwordChecker, emailAddress) => {
return function (dispatch, getState, { history }) {
const user = { username, password, passwordChecker, emailAddress };
instance.post("user/signup", user).then((result) => {
history.push("/login");
window.alert("회원가입 완료. 환영합니다!");
});
};
};
코드설명
// 로그인 API
const loginAPI = (emailAddress, password) => {
return function (dispatch, getState, { history }) {
const user_login = { emailAddress, password };
instance
.post("user/login", user_login)
.then((result) => {
const accessToken = result.data; // API 요청하는 콜마다 해더에 accessTocken 담아 보내도록 설정
instance.defaults.headers.common["Authorization"] = `${accessToken}`;
setCookie("token", accessToken, 1, "/");
var decoded = jwt_decode(accessToken);
dispatch(
logIn({
username: decoded.sub,
})
);
history.push("/");
})
.catch((error) => {
console.log(error);
window.alert("로그인 실패");
});
};
};
코드설명
3.loginCheck 함수로 현재 로그인 상태 유무 확인(새로고침하면 redux상태값이 초기화가 되므로 방지하기 위하여 App.js에 loginCheck() 넣기)
const loginCheck = () => {
return function (dispatch, getState, { history }) {
if (getCookie("token")) {
const token = getCookie("token");
var decoded = jwt_decode(token);
instance.defaults.headers.common["Authorization"] = `${token}`;
dispatch(
logIn({
username: decoded.sub,
})
);
}
};
};
코드설명
// 로그아웃 API
const logOutAPI = () => {
return function (dispatch, getState, { history }) {
deleteCookie("token");
instance.defaults.headers.common["Authorization"] = null;
delete instance.defaults.headers.common["Authorization"];
dispatch(logOut());
history.replace("/login");
};
};
코드설명
const selectProfileFile = (e) => {
const reader = new FileReader();
const file = profileInput.current.files[0];
reader.readAsDataURL(file);
reader.onloadend = () => {
dispatch(uploadAction.uploadProfileImg(file));
};
};
const uploadProfileImg = (file) => {
return function (dispatch, getState, { history }) {
dispatch(actionLoading.setLoading(true));
let _upload = storage.ref(`images/${file.name}`).put(file);
let username = getState().user.user.username;
_upload.then((snapshot) => {
snapshot.ref.getDownloadURL().then((url) => {
const param = { picture: url, username };
instance.put("user/userprofile/picture", param).then((result) => {
//내 프로필 이미지 리덕스 상태값에 url 넣기
dispatch(profileAction.getProfileImage(url));
//loading 컴포넌트 안보이게
dispatch(actionLoading.setLoading(false));
//개인상세페이지에 내가 등록한 포스트 리스트 뿌려주기
dispatch(postAction.getMyPostDB());
});
});
});
};
};
코드설명
const handleSearch = () => {
const inputValue = inputRef.current.value;
if (inputValue === "") {
dispatch(searchAction.deleteSearchListAll());
} else {
dispatch(searchAction.getSearchListDB(inputValue));
}
setCurrentValue(inputValue);
};
const getSearchListDB = (friendName) => {
return function (dispatch, getState, { history }) {
const username = getState().user.user.username;
instance
.get(`/user/search/contain-list/${username}/${friendName}`)
.then((result) => {
dispatch(getSearchList(result.data));
});
};
};
const keyUpHandler = (currentValue) => {
const searchedPara = document.querySelectorAll(".text");
if (searchedPara.length > 0) {
const words = currentValue;
const regex = RegExp(words, "gi");
const replacement = "<strong style=color:#3b5998>" + words + "</strong>";
for (let i = 0; i < searchedPara.length; i++) {
const newHTML = searchedPara[i].textContent.replace(regex, replacement);
searchedPara[i].innerHTML = newHTML;
}
}
};
const searchedWord = useSelector((state) => state.search.search_list);
useEffect(() => {
keyUpHandler(currentValue);
}, [searchedWord]);
코드설명
const handleRequestFriend = () => {
dispatch(friendAction.requestFriendDB(requestParam));
};
const handleCancleRequestFriend = () => {
dispatch(friendAction.requestCancleFriendDB(requestParam));
};
const requestFriendDB = (friend) => {
return function (dispatch, getState, { history }) {
instance.post("user/request-friend", friend).then((result) => {
const { friendName } = friend;
dispatch(
searchAction.getSearchDetailListDB(friendName.replace(/[0-9]/g, ""))
);
});
};
};
const requestCancleFriendDB = (friend) => {
return function (dispatch, getState, { history }) {
const { username, friendName } = friend;
instance
.delete(`user/decline-friend/given/${username}/${friendName}`)
.then((result) => {
dispatch(
searchAction.getSearchDetailListDB(friendName.replace(/[0-9]/g, ""))
);
});
};
};
코드설명
1.자신한테 친구 요청들어온 친구 요청 목록 가져오기
const requestedFriendListDB = () => {
return function (dispatch, getState, { history }) {
let username = getState().user.user.username;
instance
.get(`/user/request-friend-list/received/${username}`)
.then((result) => {
dispatch(getRequestedFriendList(result.data));
});
};
};
코드설명
const acceptRequestedFriend = (friendName) => {
return function (dispatch, getState, { history }) {
let username = getState().user.user.username;
const param = {
username,
friendName,
};
instance.post("/user/accept-friend", param).then(() => {
dispatch(requestedFriendListDB());
});
};
};
const getMyFriendListDB = () => {
return function (dispatch, getState, { history }) {
let username = getState().user.user.username;
instance.get(`user/friends/${username}`).then((result) => {
dispatch(getMyFriendList(result.data.friends));
});
};
};
코드설명
const selectImgFile = (e, type) => {
const reader = new FileReader();
const file = imageInput.current.files[0];
reader.readAsDataURL(file);
reader.onloadend = () => {
const param = { file: file, preview: reader.result };
dispatch(previewActions.setImagePreview(param));
};
};
export default handleActions(
{
[SET_IMAGE_PREVIEW]: (state, action) =>
produce(state, (draft) => {
draft.images.push(action.payload.image);
}),
},
initialState
);
const imagePreview = useSelector((state) => state.preview.images);
코드설명
const uploadImageFB = (type) => {
return function (dispatch, getState, { history }) {
const preview = getState().preview;
let article = getState().article.article;
const imageLength = preview.images.length;
const videoLength = preview.videos.length;
dispatch(actionLoading.setLoading(true));
if (imageLength > 0 || videoLength > 0) {
for (let key in preview) {
preview[key].map(({ file }) => {
let _upload;
if (key === "images") {
_upload = storage.ref(`images/${file.name}`).put(file);
} else if (key === "videos") {
_upload = storage.ref(`videos/${file.name}`).put(file);
}
_upload.then((snapshot) => {
snapshot.ref.getDownloadURL().then((url) => {
if (key === "images") {
dispatch(previewAction.deleteAllImagePreview());
dispatch(setUploadImageUrlList(url));
} else if (key === "videos") {
dispatch(setUploadVideoUrlList(url));
}
});
});
});
}
setTimeout(() => {
if (type === "add") {
dispatch(articleAction.addArticleDB(article));
} else {
dispatch(articleAction.updateArticleDB(article));
}
}, 4000);
} else {
if (type === "add") {
dispatch(articleAction.addArticleDB(article));
} else {
dispatch(articleAction.updateArticleDB(article));
}
}
};
};
const addArticleDB = (article) => {
return function (dispatch, getState, { history }) {
const picture = getState().upload.upload_img_url;
const video = getState().upload.upload_video_url;
const pictureParam = picture.join(',');
const videoParam = video.join(',');
const param = {
...article,
picture: pictureParam,
video: videoParam,
};
instance
.post(`/user/article`, param)
.then((result) => {
dispatch(actionLoading.setLoading(false));
history.replace('/');
})
.catch((error) => {
console.log('error : ', error);
});
};
};
코드설명
깃허브 url - https://github.com/hanghae25/facebook_clone_client