회원관리(MariaDB) - Part3 회원 정보 수정
ㆍProject Diary/React + MariaDB (PicShare WebApp)
사용자가 회원 정보를 수정할 수 있는 기능을 구현합니다. 사용자는 닉네임, 비밀번호, 프로필 사진을 변경할 수 있으며, 이를 위해 프론트엔드와 백엔드가 상호작용합니다. 데이터베이스에 저장된 정보를 업데이트하고, 중복된 닉네임을 체크하며, 새로운 프로필 사진을 업로드하는 과정을 포함합니다.
프론트엔드
1. 회원 정보 수정 페이지 (ProfileModify.jsx)
1-1 초기 상태 및 사용자 정보 로드
import React, { useRef, useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
import styled from "styled-components";
import axios from "axios";
import { fetchUsers, userLogout } from "@/store/member";
const serverUrl = import.meta.env.VITE_API_URL;
const ProfileModifyBlock = styled.div`
// 생략: 스타일 관련 코드
`;
const ProfileModify = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const userNicknameRef = useRef();
const [userInfo, setUserInfo] = useState({
userNickname: "",
currentPassword: "",
newPassword: "",
photo: "",
});
const [error, setError] = useState({});
const [success, setSuccess] = useState({});
const [useDefaultProfile, setUseDefaultProfile] = useState(false);
const [profilePreview, setProfilePreview] = useState("");
const user = useSelector((state) => state.members.user);
useEffect(() => {
if (user) {
setProfilePreview(`${serverUrl}/uploads/${user.profilePicture}`);
setUserInfo((prevState) => ({
...prevState,
userNickname: user.userNickname,
}));
}
}, [user]);
// 이하 생략
};
- useState 및 useEffect: 사용자 정보를 로드하고 초기 상태를 설정합니다.
- useSelector: Redux 스토어에서 현재 사용자 정보를 가져옵니다.
- useEffect: 사용자 정보가 변경될 때마다 프로필 프리뷰와 닉네임을 설정합니다.
1-2 프로필 이미지 변경
const handleCheckboxChange = () => {
setUseDefaultProfile((prevUseDefaultProfile) => {
const newUseDefaultProfile = !prevUseDefaultProfile;
setProfilePreview(
newUseDefaultProfile
? `${serverUrl}/uploads/defaultProfile.jpg`
: `${serverUrl}/uploads/${user.profilePicture}`
);
return newUseDefaultProfile;
});
};
const handleFileChange = (e) => {
const file = e.target.files[0];
setUserInfo((prevUserInfo) => ({ ...prevUserInfo, photo: file }));
setProfilePreview(URL.createObjectURL(file));
};
- handleCheckboxChange: 체크박스를 통해 기본 프로필 이미지 사용 여부를 토글합니다.
- 기본 프로필 사용 시: defaultProfile.jpg를 프리뷰로 설정합니다.
- 사용자 정의 프로필 사용 시: 업로드된 프로필 사진을 프리뷰로 설정합니다. - handleFileChange: 파일 입력 시 선택한 파일을 userInfo 상태에 저장하고, 프로필 사진 미리보기를 업데이트합니다.
- 파일 선택 시: URL.createObjectURL(file)을 사용하여 미리보기 이미지를 설정합니다.
1-3 닉네임 중복 체크 및 입력 처리
const handleChange = async (e) => {
const { value, name } = e.target;
setUserInfo((userInfo) => ({ ...userInfo, [name]: value }));
setError((error) => ({ ...error, [name]: "" }));
setSuccess((success) => ({ ...success, [name]: "" }));
// 닉네임 중복 체크
if (name === "userNickname" && value) {
try {
await axios.post(`${serverUrl}/auth/check-nickname`, {
userNickname: value,
});
setSuccess((success) => ({
...success,
userNickname: "사용 가능한 닉네임입니다.",
}));
} catch (err) {
if (err.response && err.response.data) {
setError((error) => ({
...error,
userNickname: err.response.data.message,
}));
}
}
}
};
- handleChange: 입력 필드가 변경될 때 호출
- 사용자 정보를 업데이트하고, 오류 메시지를 초기화합니다.
- 닉네임 중복 체크를 수행합니다.
1-4 프로필 업데이트 처리
const updateprofile = async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append("userNo", user.userNo);
formData.append("userNickname", userInfo.userNickname);
formData.append("currentPassword", userInfo.currentPassword);
formData.append("newPassword", userInfo.newPassword);
if (!useDefaultProfile && userInfo.photo) {
formData.append("photo", userInfo.photo);
}
try {
const res = await axios.put(
`${serverUrl}/auth/update-profile`,
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
}
);
if (res.data.affectedRows >= 1) {
alert("회원정보가 수정되었습니다. 변경 사항을 적용하려면 로그아웃 후 다시 로그인해 주세요");
dispatch(userLogout(user.userNo));
navigate("/login");
} else {
alert("회원수정이 실패했습니다.");
}
} catch (err) {
console.error("서버 오류:", err);
if (err.response && err.response.data) {
const { field, message } = err.response.data;
setError((prevError) => ({ ...prevError, [field]: message }));
} else {
console.error(err);
}
}
};
- updateprofile: 사용자 프로필 정보를 업데이트하는 함수
- FormData를 사용하여 파일 업로드와 함께 데이터를 전송합니다.
- 서버로부터 응답을 받아 성공 여부에 따라 알림을 표시합니다.
- 업데이트된 정보를 적용하려면 로그아웃 후 다시 로그인하도록 안내합니다.
1-5 회원 탈퇴 처리
const handleDelete = (userNo) => {
console.log("탈퇴회원", userNo);
const confirmDelete = window.confirm(
"정말 회원 탈퇴를 하시겠습니까?\n\n" +
"회원 탈퇴 시, 귀하의 계정 및 모든 데이터가 영구적으로 삭제됩니다.\n"
);
if (confirmDelete) {
axios
.delete(`${serverUrl}/auth/delete`, { params: { userNo } })
.then((res) => {
if (res) {
console.log(res.data);
dispatch(fetchUsers());
alert("회원탈퇴가 완료되었습니다.");
dispatch(userLogout());
navigate("/login");
} else {
alert("삭제하지 못했습니다.");
}
})
.catch((err) => console.log(err));
}
};
- handleDelete: 회원 탈퇴를 처리하는 함수
- 사용자에게 확인 메시지를 표시하고, 동의하면 탈퇴 요청을 서버로 전송합니다.
- 성공적으로 탈퇴되면 알림을 표시하고, 사용자 로그아웃 및 로그인 페이지로 이동합니다.
백엔드
1. 회원 정보 수정 API 엔드포인트 (authRouter.js)
2-1 회원 정보 수정 엔드포인트
authRouter.put("/update-profile", upload.single("photo"), (req, res) => {
const { userNo, userNickname, currentPassword, newPassword } = req.body;
const photo = req.file ? req.file.filename : "defaultProfile.jpg";
// 닉네임과 프로필 사진 업데이트
if (photo || userNickname) {
let updateQuery = "UPDATE users SET";
let queryParams = [];
if (photo) {
updateQuery += " profilePicture = ?";
queryParams.push(photo);
}
if (userNickname) {
if (queryParams.length > 0) updateQuery += ",";
updateQuery += " userNickname = ?";
queryParams.push(userNickname);
}
updateQuery += " WHERE userNo = ?";
queryParams.push(userNo);
db.query(updateQuery, queryParams, (err, result) => {
if (err) {
return res.status(500).json({
message: "서버 오류가 발생했습니다. 다시 시도해주세요.",
});
}
if (newPassword) {
updatePassword();
} else {
res.status(200).json({ affectedRows: result.affectedRows });
}
});
} else if (newPassword) {
updatePassword();
}
function updatePassword() {
// 현재 비밀번호 확인
db.query(
"SELECT * FROM users WHERE userNo = ?",
[userNo],
(err, results) => {
if (err) {
return res.status(500).json({
message: "서버 오류가 발생했습니다. 다시 시도해주세요.",
});
}
const user = results[0];
if (user.password !== currentPassword) {
return res.status(400).json({
field: "currentPassword",
message: "현재 비밀번호가 일치하지 않습니다.",
});
}
// 새로운 비밀번호로 업데이트
db.query(
"UPDATE users SET password = ? WHERE userNo = ?",
[newPassword, userNo],
(err, results) => {
if (err) {
return res.status(500).json({
message: "서버 오류가 발생했습니다. 다시 시도해주세요.",
});
}
res.status(200).json({ affectedRows: results.affectedRows });
}
);
}
);
}
});
sql 쿼리문
authRouter.put("/update-profile", upload.single("photo"), (req, res) => { ... });
- 회원 정보 수정 요청을 처리합니다.
- upload.single("photo")
Multer를 사용하여 단일 파일 업로드를 처리합니다. - 닉네임과 프로필 사진을 업데이트합니다.
- 비밀번호가 변경되면 현재 비밀번호를 확인하고 새로운 비밀번호로 업데이트합니다.