반응형
What I Learned
실전프로젝트를 시작한지 2주차가 시작되면서 기본적인 mvp 기능에 대한 구성이 어느정도 자리잡혔다. MVP 기간을 설정하고 프론트와 디자이너의 협업, 백엔드와의 소통 등의 다양한 과정을 거치며 구현해야하는 기능을 구현해나갔다.
구현해야할 페이지 및 MVP 기능
- 페이지
- 메인, 회원가입, 로그인, 마이페이지, 책 목록, 책 상세, 펀딩 목록, 펀딩 상세, 펀딩 등록, 오디오북 등록, 오디오 상세, 1:1 채팅 페이지, 오디오북 요청하기, 판매자 상세페이지
- MVP 기능
- 메인 : 카테고리별 도서, 오늘의 크리에이터, 추천도서, 랜덤 오디오펀딩 get
- 로그인 & 회원가입 : 로그인 회원가입 유효성 검사, 중복확인, 로그인 유지
- 책 목록 & 책 상세 : 카테고리별 도서 get, 책 상세페이지에서 오디오 미리듣기 제공, 팔로우, 오디오북 요청 CRUD, 오디오북 등록 CR, 펀딩 등록 CR
- 오디오 상세 : 목차별 오디오 재생목록 출력, 재생목록 클릭시 각 목차 재생, 후기 CRUD
- 펀딩 : 펀딩 등록 CR, 펀딩 좋아요, 펀딩 목록 get
- 마이페이지 : 프로필 등록 및 수정, 판매자 목소리 등록, 내가 등록한 펀딩, 오디오북, 내가 찜한 책 get
- 크리에이터 상세페이지 : 팔로우, 언팔로우 기능, 판매자가 등록한 펀딩과 책 get
- 채팅 : 1:1 채팅
- react-h5-audio-player을 이용한 오디오 플레이어 기능 구축
react-h5-audio-player 랜더링 시 자동재생 문제 발생
오디오를 등록한 후 페이지 이동 후 랜더링이 될 때 모든 플레이어의 음악이 자동재생되는 문제가 발생했다. react-h5-audio-player에서 기본적으로 제공하는 autoplay를 이용해보려고 했지만 autoplay가 먹히지 않았다. 그래서 리액트 훅 useRef를 이용하여 플레이어를 정지시키는 방법을 이용했다.
1. useRef를 이용해 마이페이지에 있는 오디오 플레이어에 접근
2. useEffect를 이용해 랜더링 시 재생 버튼 정지
import React, { useEffect, useRef } from 'react';
import styled from 'styled-components';
import AudioPlayer from "react-h5-audio-player";
import MyPageAudioBook from '../components/MyPageAudioBook';
import { useParams } from 'react-router-dom';
import { history } from '../redux/configureStore';
import { actionCreators as libraryActions } from "../redux/modules/mypage";
import { actionCreators as followActions } from "../redux/modules/creator";
import { useDispatch, useSelector } from 'react-redux';
import Box from '@mui/material/Box';
import Modal from '@mui/material/Modal';
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'background.paper',
border: '2px solid #000',
boxShadow: 24,
p: 4,
};
const MyPage = () => {
const dispatch = useDispatch();
const params = useParams();
const category = params.category;
// 개인 프로필 정보
const profile = useSelector((state) => state.mypage.profile);
const following = useSelector((state) => state.creator.creator_following);
const follower = useSelector((state) => state.creator.creator_follower);
const authority = localStorage.getItem("seller");
const sellerId = profile.userId;
// 마이페이지 카테고리 정보
const likeBook = useSelector((state) => state.mypage.library_likeBook);
const listenAudio = useSelector((state) => state.mypage.library_listenAudio);
const myFunding = useSelector((state) => state.mypage.library_registerFunding);
const myAudio = useSelector((state) => state.mypage.library_registerAudioBook);
// 팔로우, 팔로잉 모달창
const [open, setOpen] = React.useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
const [open2, setOpen2] = React.useState(false);
const handleOpen2 = () => setOpen2(true);
const handleClose2 = () => setOpen2(false);
// 플레이어 자동재생 막기
const player = useRef();
useEffect(() => {
if (authority === "ROLE_SELLER") {
player.current.audio.current.pause(); // -3-
}
}, [profile]);
// 카테고리별 리스트 불러오기
useEffect(() => {
dispatch(libraryActions.getProfileAC());
if (category === "likeBook") {
dispatch(libraryActions.getLikeBookAC());
} else if (category === "listen") {
dispatch(libraryActions.getListenAudioAC());
} else if (category === "myAudio") {
dispatch(libraryActions.getRegisterAudioBookAC());
} else {
dispatch(libraryActions.getRegisterFundingAC());
}
}, []);
// 권한이 없는 사용자는 마이페이지에 접근할 수 없음
useEffect(() => {
if (!authority) {
history.push("/login")
}
}, []);
return (
<React.Fragment>
<Wrap>
<Menu>
<Profile>
<ProfileBox>
<div id='img'>
<img
alt="유저 이미지"
src={profile.userImage ? profile.userImage : "https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTB2Sn%2FbtrB4PINn6v%2FpPKEkCp0WIdi5JI9NGvzrk%2Fimg.png"} />
</div>
<div id='username'>
<h4>{profile.userName}</h4>
<h5 onClick={() => {
dispatch(followActions.followingListAC(sellerId));
handleOpen();
}}>팔로잉 <span>{profile.followingCnt}명</span></h5>
{authority === "ROLE_SELLER" ?
<h5
onClick={() => {
dispatch(followActions.followerListAC(sellerId));
handleOpen2();
}}
>팔로워 <span>{profile.followerCnt}명</span></h5>
:
null
}
</div>
</ProfileBox>
<ProfileBox>
<h3>
{profile.introduce}
</h3>
</ProfileBox>
<Modal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<h2 style={{ width: "100%", textAlign: "center" }}>팔로잉</h2>
<BoxSt>
{following && following.map((item, idx) =>
<FollowerList key={idx}>
<div id='name'>
<ImageBox>
<img
style={{ width: "100%" }}
alt="유저 이미지"
src={item.img ? item.img : "https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTB2Sn%2FbtrB4PINn6v%2FpPKEkCp0WIdi5JI9NGvzrk%2Fimg.png"}
/>
</ImageBox>
<h3 style={{ fontSize: "16px" }}>
{item.name}
</h3>
</div>
<button>unfollow</button>
</FollowerList>
)
}
</BoxSt>
</Box>
</Modal>
<Modal
open={open2}
onClose={handleClose2}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box sx={style}>
<h2 style={{ width: "100%", textAlign: "center" }}>팔로워</h2>
<BoxSt>
{follower && follower.map((item, idx) =>
<FollowerList key={idx}>
<div id='name'>
<ImageBox>
<img
style={{ width: "100%" }}
alt="유저 이미지"
src={item.img ? item.img : "https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTB2Sn%2FbtrB4PINn6v%2FpPKEkCp0WIdi5JI9NGvzrk%2Fimg.png"}
/>
</ImageBox>
<h3 style={{ fontSize: "16px" }}>
{item.name}
</h3>
</div>
<button>unfollow</button>
</FollowerList>
)
}
</BoxSt>
</Box>
</Modal>
{authority === "ROLE_SELLER" ?
<AudioPlayer
className='audio'
autoPlay={false}
src={profile.sellerVoice}
volume={1}
timeFormat={"mm:ss"}
defaultCurrentTime={"00:00"}
showJumpControls={false}
ref={player}
onPlay={e => console.log("onPlay")}
/>
:
null
}
<button
id='btn'
onClick={() => {
history.push(`/profileEdit`)
}}
>
프로필 편집
</button>
{authority !== "ROLE_SELLER" ?
<span
id='creatorform'
onClick={() => {
window.open(`https://forms.gle/UR8cGG2YDWnc7f1y8`)
}}
>크리에이터 신청하기</span>
:
profile.sellerVoice ?
<span
id='creatorform'
onClick={() => {
history.push(`/addvoice`)
}}
>내 목소리 다시 올리기</span>
:
<span
id='creatorform'
onClick={() => {
history.push(`/addvoice`)
}}
>내 목소리 등록하기</span>
}
</Profile>
<List>
{authority === "ROLE_SELLER" ?
<ListBox>
<h2>| 크리에이터</h2>
<h3
style={{ textDecoration: (category === "myAudio" ? "underline" : null) }}
onClick={() => {
history.push(`/mypage/myAudio`)
dispatch(libraryActions.getRegisterAudioBookAC());
}}>업로드한 오디오북
</h3>
<h3
style={{ textDecoration: (category === "myFunding" ? "underline" : null) }}
onClick={() => {
history.push(`/mypage/myFunding`)
dispatch(libraryActions.getRegisterFundingAC());
}}>등록한 펀딩
</h3>
</ListBox>
:
null
}
<ListBox>
<h2>| 서재</h2>
<h3
style={{ textDecoration: (category === "listen" ? "underline" : null) }}
onClick={() => {
history.push(`/mypage/listen`)
dispatch(libraryActions.getListenAudioAC());
}}>듣고 있는 오디오북
</h3>
<h3
style={{ textDecoration: (category === "likeBook" ? "underline" : null) }}
onClick={() => {
history.push(`/mypage/likeBook`)
dispatch(libraryActions.getLikeBookAC());
}}>찜한 책
</h3>
</ListBox>
</List>
</Menu>
<div>
{
category === "myAudio" && myAudio ?
<span>총 {myAudio.length}개</span>
:
category === "myFunding" && myFunding ?
<span>총 {myFunding.length}개</span>
:
category === "listen" && listenAudio ?
<span id='num'>총 {listenAudio.length}개</span>
:
category === "likeBook" && likeBook ?
<span id='num'>총 {likeBook.length}개</span>
:
null
}
{
(category === "myAudio") && (myAudio && myAudio.length === 0) ?
<AudioReviewNone>
아직 등록한 오디오북이 없네요! 오디오북을 등록해볼까요?
</AudioReviewNone>
:
(category === "myFunding") && (myFunding && myFunding.length === 0) ?
<AudioReviewNone>
아직 펀딩을 시도하지 않았어요! 펀딩을 시작해볼까요?
</AudioReviewNone>
:
(category === "listen") && (listenAudio && listenAudio.length === 0) ?
<AudioReviewNone>
아직 듣고 있는 오디오북이 없어요! 들으러 가볼까요?
</AudioReviewNone>
:
(category === "likeBook") && (likeBook && likeBook.length === 0) ?
<AudioReviewNone>
아직 찜한 책이 없어요! 책을 둘러보러 가볼까요?
</AudioReviewNone>
:
null
}
<Body>
{category === "myAudio" ? myAudio.map((item, idx) => (
<MyPageAudioBook key={idx} item={item} />
))
:
category === "myFunding" ? myFunding.map((item, idx) => (
<MyPageAudioBook key={idx} item={item} />
))
:
category === "listen" ? listenAudio.map((item, idx) => (
<MyPageAudioBook key={idx} item={item} />
))
:
category === "likeBook" ? likeBook.map((item, idx) => (
<MyPageAudioBook key={idx} item={item} />
))
:
null
}
</Body>
</div>
</Wrap>
</React.Fragment>
)
}
const FollowerList = styled.div`
width: 90%;
height: 60px;
margin: 10px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
border: 1px solid #000000;
border-radius: 5px;
#name {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
margin-left: 10px;
h3 {
margin-left: 15px;
}
}
button {
border: none;
background: none;
color: #000000;
margin-right: 10px;
:hover {
cursor: pointer;
color: #D05943;
}
}
`
const BoxSt = styled.div`
height: 400px;
overflow-y: scroll;
::-webkit-scrollbar {
/* 세로 스크롤 넓이 */
width: 7px;
border-radius: 6px;
background: #FFFFFC;
border: 1px solid #000000;
}
::-webkit-scrollbar-thumb {
height: 17%;
background-color: #000000;
border-radius: 6px;
}
`
const ImageBox = styled.div`
width: 50px;
height: 50px;
border-radius: 100px;
overflow: hidden;
border: 1px solid #878787;
img {
width:100%;
height:100%;
object-fit:cover;
}
`
const AudioReviewNone = styled.div`
width: 100%;
min-height: 200px;
display: flex;
flex-direction: row;
justify-content: space-around;
align-items: center;
`
const Wrap = styled.div`
width: 1100px;
min-height: 700px;
margin: 0 auto;
margin-top: 36px;
display: flex;
flex-direction: row;
justify-content: space-between;
position: relative;
padding-top: 30px;
padding-bottom: 30px;
font-family: 'Pretendard';
font-style: normal;
font-weight: 400;
#num {
font-size: 16px;
margin: 0px 0px 5px 8px;
}
`
const Menu = styled.div`
width: 290px;
min-height: 800px;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
border-radius: 10px;
padding-bottom: 30px;
`
const Body = styled.div`
width: 750px;
height: 700px;
overflow-y: scroll;
::-webkit-scrollbar {
/* 세로 스크롤 넓이 */
width: 7px;
height: 100%;
border-radius: 6px;
}
::-webkit-scrollbar-thumb {
height: 17%;
background-color: #000000;
border-radius: 6px;
}
flex-wrap: wrap;
display: flex;
flex-direction: row;
padding-bottom: 30px;
`
const Profile = styled.div`
width: 290px;
min-height: 220px;
display: flex;
flex-direction: column;
align-items: center;
.audio {
width: 100%;
height: 80px;
border-radius: 5px;
background: none;
margin-bottom: 20px;
.rhap_progress-indicator {
background: #0C0A0A;;
}
.rhap_volume-indicator {
background: #0C0A0A;;
}
div {
color : black;
}
div.rhap_progress-filled {
background-color : #0C0A0A;;
}
button {
color : #0C0A0A;;
}
}
#btn {
width: 290px;
height: 48px;
border-radius: 10px;
background-color: #0C0A0A;;
color: #FFFFFF;
font-weight: 400;
font-size: 14px;
cursor: pointer;
}
#creatorform {
width: 100%;
margin-top: 13px;
margin-left: 3px;
font-weight: 300;
font-size: 16px;
line-height: 100%;
text-decoration-line: underline;
color: #0C0A0A;
:hover {
color: purple;
cursor: pointer;
}
}
`
const ProfileBox = styled.div`
width: 290px;
display: flex;
flex-direction: row;
justify-content: space-between;
margin: 5px 0px;
#img {
width: 100px;
height: 100px;
border-radius: 15px;
border: 1px solid black;
overflow: hidden;
img {
width:100%;
height:100%;
object-fit:cover;
}
}
#username {
width: 59%;
h4 {
font-weight: 700;
font-size: 22px;
margin: 0px 0px 10px 0px;
}
h5 {
font-weight: 300;
font-size: 13px;
margin: 2px 0px 0px 0px;
:hover {
color: #D05943;
cursor: pointer;
}
span {
font-weight: 500;
}
}
}
h3 {
width: 100%;
font-weight: 400;
font-size: 13px;
line-height: 150%;
text-align: justify;
color: #0C0A0A;
}
`
const List = styled.div`
width: 290px;
height: 300px;
margin-top: 20px;
display: flex;
flex-direction: column;
align-items: center;
`
const ListBox = styled.div`
width: 100%;
display: flex;
flex-direction: column;
margin: 10px 0px;
h3 {
margin: 8px 0px;
cursor: pointer;
font-weight: 400;
font-size: 16px;
color: #525252;
}
`
export default MyPage;
<항해 99 9주차 실전 프로젝트>
프로젝트 기간 : 2022.04.21.~2022.06.02 (총 6주간)
서비스명 : EYAGI(이야기)
팀 노션 : https://balanced-desk-3a4.notion.site/EYAGI-06e6113484324fe8ba37ec83e5e70b8d
9주차 공부시간 기록 : 131시간 16분
반응형