회원 정보 수정은 modifyUser 메서드와 modifyUser API 엔드포인트를 통해 이루어집니다.
클라이언트에서는 MyPage 컴포넌트를 통해 회원 정보 수정 페이지로 이동하고,
사용자가 수정할 정보를 입력한 후 수정을 완료할 수 있습니다.
import React, { useState } from 'react';
import axios from 'axios';
import { useQuery } from 'react-query';
import { useNavigate } from 'react-router-dom';
const MyPage = () => {
const navigate = useNavigate();
// 변경 부분<< 처음에는 authState를 useRecoilState를 통해서 true false값만 주었지만
// atom을 만들 당시 바로 default값에 변수를 추가해서 isAuthenticated를 선언하고
// useRecoilValue를 통해 boolean변수 하나만 선언하여 상태를 관리한다.
// 인증을 하여 개인정보를 가지고 오는 principal 관련 get요청은 모두 enabled로 잡아주면된다.
const authState = useRecoilValue(authenticationState);
const [checkType,setCheckType] = useState("myplan");
const [userInfo, setUserInfo] = useState({
email: '',
userId: '',
profileImg: ''
})
const principal = useQuery(["principal"], async () => {
const accessToken = localStorage.getItem("accessToken");
const response = await axios.get('http://localhost:8080/api/v1/auth/principal', { params: { accessToken } });
return response;
}, {
enabled: authState.isAuthenticated,
onSuccess : (response) => {
setUserInfo({
email: response.data.email,
userId: response.data.userId,
name: response.data.name,
profileImg: response.data.postsImgUrl
})
}
});
const myPlanChangeHandler = (type) => {
setCheckType(type);
}
return (
<div css={container}>
<main css={main}>
<div css={imgContainer}>
<img css={imgStyle} src={userInfo.profileImg} alt=""/>
</div>
<div>{userInfo.email || '이메일 로딩 중...'}</div>
<div css={modifyButtons}>
<ModifyButton onClick={() => navigate(`/user/modify/${principal?.data?.data?.userId || ''}`)}>수정하기</ModifyButton>
<ModifyButton onClick={() => navigate(`/user/modify/password/${principal?.data?.data?.userId || ''}`)}>비밀번호 변경</ModifyButton>
</div>
<div css={mainContents}>
<div css={myPlanAndReview} onClick={() => myPlanChangeHandler('myPlan')}>
<span>나의 일정</span>
<span>0</span>
</div>
<div css={myPlanAndReview} onClick={() => myPlanChangeHandler('myReview')}>
<span>나의 리뷰</span>
<span>0</span>
</div>
</div>
<div css={planAndReviewContainer}>
{checkType === 'myPlan'?(<TravelList/>):(<MyReviewList/>)}
</div>
</main>
</div>
);
};
export default MyPage;
@RestController
@RequestMapping("/api/v1/user")
public class UserController {
// user 정보 수정
@PutMapping("/{userId}")
public ResponseEntity<?> modifyUser(@PathVariable int userId, UserModifyReqDto userModifyReqDto) {
return ResponseEntity.ok(DataRespDto.of(userService.modifyUser(userId, userModifyReqDto)));
}
// 기타 코드 생략...
}
MyPage 컴포넌트에서는 현재 사용자의 이메일을 표시하고,
회원 정보 수정 버튼과 비밀번호 변경 버튼을 제공합니다.
각 버튼을 클릭하면 해당 기능을 수행하기 위해 해당 페이지로 이동합니다.
import React, { useState } from 'react';
import axios from 'axios';
const ModifyForm = () => {
const [ authState, setAuthState ] = useRecoilState(authenticationState);
const [ imgFiles, setImgFiles ] = useState(null);
const [ preview, setPreview ] = useState('');
const principal = useQuery(["principal"], async () => {
const accessToken = localStorage.getItem("accessToken");
const response = await axios.get('http://localhost:8080/api/v1/auth/principal', {params: {accessToken}});
return response;
}, {
enabled: authState.isAuthenticated,
onSuccess: (response) => {
setUpdateUser({
profileImg: response.data.postsImgUrl,
email: response.data.email,
name: response.data.name,
phone: response.data.phone,
address: response.data.address
})
}
});
const [inputDisabled, setInputDisabled] = useState({
name: true,
phone: true,
address: true
});
const [buttonText, setButtonText] = useState({
name: 'Edit',
phone: 'Edit',
address: 'Edit'
});
const [ updateUser, setUpdateUser ] = useState({
profileImg: '',
email: '',
name: '',
phone: '',
address: ''
});
const [ errorMessages, setErrorMessages ] = useState({
name: '',
phone: ''
});
const [ isName, setIsName ] = useState(true)
const [ isPhone, setIsPhone ] = useState(true)
// 유효성 검사 -> user 정보수정
const onChangeHandler = (e) => {
const { name, value } = e.target;
if(name === 'name') {
const nameRegex = /^[가-힣]{2,8}$/;
if(!nameRegex.test(value)) {
setErrorMessages((errors) => ({
...errors,
name: '2글자 이상 8글자 미만으로 입력해주세요.'
}));
setIsName(false);
} else {
setErrorMessages((errors) => ({
...errors,
name: '올바른 이름 형식입니다. :)'
}));
setIsName(true);
}
} else if (name === 'phone') {
const phoneRegex = /^\d{3}-\d{3,4}-\d{4}$/;
if (!phoneRegex.test(value)) {
setErrorMessages((errors) => ({
...errors,
phone: '올바른 전화번호 형식이 아닙니다.'
}));
setIsPhone(false);
} else {
setErrorMessages((errors) => ({
...errors,
phone: '올바른 전화번호 형식입니다. :)'
}));
setIsPhone(true);
}
}
setUpdateUser(
{
...updateUser,
[name]: value
}
)
};
const modifyUser = useMutation(async (modifyData) => {
try {
const formData = new FormData();
formData.append("email", modifyData.email)
formData.append("name", modifyData.name)
formData.append("phone", modifyData.phone)
formData.append("address", modifyData.address)
formData.append("profileImg", imgFiles)
console.log(imgFiles);
console.log(formData.get("profileImg"));
const option = {
headers: {
'Content-Type': 'multipart/form-data',
Authorization: `${localStorage.getItem('accessToken')}`
}
}
const response = await axios.put(`http://localhost:8080/api/v1/user/${principal.data.data.userId}`, formData, option);
setErrorMessages({
profileImg: '',
email: '',
name: '',
phone: '',
address: ''});
return response
}catch (error) {
}
}, {
onSuccess: (response) => {
alert('회원정보 수정 완료');
window.location.replace('/');
}
})
const updateUserHandleSubmit = () => {
if(isName && isPhone) {
modifyUser.mutate(updateUser);
}
}
const toggleEdit = (field) => {
setInputDisabled((prevState) => ({
...prevState,
[field]: !prevState[field]
}));
setButtonText((prevState) => ({
...prevState,
[field]: prevState[field] === "Edit" ? "Modify" : "Edit"
}));
};
const saveImgFileHandle = (e) => {
setImgFiles(e.target.files[0]);
const fileReader = new FileReader();
fileReader.readAsDataURL(e.target.files[0]);
fileReader.onload = (e) => {
setPreview(e.target.result);
};
}
const imgClickHandle = (e) => {
}
// const removeFileHandle = (e) => {
// const idToRemove = parseInt(e.target.value);
// setImgFiles(prevImgFiles => prevImgFiles.filter(imgFile => imgFile.id !== idToRemove));
// }
if(authState) {
if (principal.isLoading) {
return (<Box sx={{ display: 'flex' }}>
<CircularProgress />
</Box>)
}
return (
<Grid component="main" maxWidth="xs" css={signupContainer}>
<Box css={signupBox}>
<Typography component="h1" variant="h5" css={signupText}>
Edit Member Information
</Typography>
<div css={profileImgContainer} onClick={imgClickHandle}>
<img css={imgStyle} src={preview ? preview : updateUser.profileImg} alt=""/>
<input css={hiddenInput} type="file" multiple={false} accept={".jpg, .png, .jpeg"} onChange={saveImgFileHandle}/>
<label>
</label>
</div>
<Box component="form" css={inputContainer}>
<StyleInput
id="email"
label="이메일"
name="email"
autoComplete="email"
onChange={onChangeHandler}
autoFocus
value={principal.data.data.email}
disabled={true}
/>
<div css={editInputStyle}>
<StyleInput
id="name"
label="이름"
name="name"
autoComplete="name"
onChange={onChangeHandler}
value={updateUser.name}
disabled={inputDisabled.name}
/>
<button type={"button"} css={editButtonStyle} onClick={() => toggleEdit("name")}>
{buttonText.name}
</button>
</div>
<div css={errorMsg}>{errorMessages.name}</div>
<div css={editInputStyle}>
<StyleInput
id="phone"
label="연락처"
placeholder="010-1234-1234"
name="phone"
autoComplete="tel"
value={updateUser.phone}
onChange={onChangeHandler}
disabled={inputDisabled.phone}
/>
<button type={"button"} css={editButtonStyle} onClick={() => toggleEdit("phone")}>
{buttonText.phone}
</button>
</div>
<div css={errorMsg}>{errorMessages.phone}</div>
<div css={editInputStyle}>
<Box width={"100%"}>
<FormControl css={addressForm}>
<InputLabel id="addressSelectLabel">주소</InputLabel>
<Select
labelId="addressSelectLabel"
id="address"
value={updateUser.address}
label="주소"
onChange={(event) => setUpdateUser({...updateUser, address: event.target.value})}
disabled={inputDisabled.address}
>
{address.map((item) => (
<MenuItem key={item} value={item}>
{item}
</MenuItem>
))}
</Select>
</FormControl>
</Box>
<button type={"button"} css={addressEditButtonStyle} onClick={() => toggleEdit("address")}>
{buttonText.address}
</button>
</div>
<Button css={submitButton}
type='button'
onClick={updateUserHandleSubmit}
fullWidth
variant="contained"
disabled={!isName || !isPhone}
sx={{ mt: 3, mb: 2 }}
>
Modify Member
</Button>
</Box>
</Box>
</Grid>
);
}
};
export default ModifyForm;
비밀번호 수정 및 찾기 등을 나타내는 페이지와 백엔드 기능을 알아보도록 하겠습니다.