일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 집합_SET
- 예외미루기
- 예외처리
- 컬렉션 타입
- 자동차수리시스템
- 인터페이스
- NestedFor
- 환경설정
- 오라클
- 메소드오버로딩
- cursor문
- 사용자예외클래스생성
- 자바
- 대덕인재개발원
- 객체 비교
- 한국건설관리시스템
- 정수형타입
- exception
- abstract
- 제네릭
- 참조형변수
- Java
- EnhancedFor
- 어윈 사용법
- 다형성
- 컬렉션프레임워크
- 추상메서드
- 생성자오버로드
- oracle
- GRANT VIEW
- Today
- Total
거니의 velog
(42) 리액트 소셜 로그인 5 본문
6. 회원정보 수정
* 회원정보 수정은 현재 애플리케이션의 상태로 유지되고 있는 사용자 정보로 화면에 출력하고 수정하게 된다. 이를 위해서 먼저, 화면에서 회원정보를 수정할 수 있도록 처리하는 작업과, API 서버의 작업, API 서버 연동 순으로 처리한다.
(1) 회원정보 수정 화면 처리
* 프로젝트의 components/member 폴더에는 ModifyComponent.js를 추가한다.
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
const initState = {
email: "",
pw: "",
nickname: "",
};
const ModifyComponent = () => {
const [member, setMember] = useState(initState);
const loginInfo = useSelector((state) => state.loginSlice);
useEffect(() => {
setMember({ ...loginInfo, pw: "ABCD" });
}, [loginInfo]);
const handleChange = (e) => {
member[e.target.name] = e.target.value;
setMember({ ...member });
};
return (
<div className="mt-6">
<div className="flex justify-center">
<div className="relative mb-4 flex w-full flex-wrap items-stretch">
<div className="w-1/5 p-6 text-right font-bold">Email</div>
<input
className="w-4/5 p-6 rounded-r border border-solid border-neutral-300 shadow-md"
name="email"
type={"text"}
value={member.email}
readOnly
></input>
</div>
</div>
<div className="flex justify-center">
<div className="relative mb-4 flex w-full flex-wrap items-stretch">
<div className="w-1/5 p-6 text-right font-bold">Password</div>
<input
className="w-4/5 p-6 rounded-r border border-solid border-neutral-300 shadow-md"
name="pw"
type={"password"}
value={member.pw}
onChange={handleChange}
></input>
</div>
</div>
<div className="flex justify-center">
<div className="relative mb-4 flex w-full flex-wrap items-stretch">
<div className="w-1/5 p-6 text-right font-bold">Nickname</div>
<input
className="w-4/5 p-6 rounded-r border border-solid border-neutral-300 shadow-md"
name="nickname"
type={"text"}
value={member.nickname}
onChange={handleChange}
></input>
</div>
</div>
<div className="flex justify-center">
<div className="relative mb-4 flex w-full flex-wrap justify-end">
<button
type="button"
className="rounded p-4 m-2 text-xl w-32 text-white bg-blue-500"
>
Modify
</button>
</div>
</div>
</div>
);
};
export default ModifyComponent;
* pages/member 에는 ModifyPage 를 추가한다.
import React from "react";
import ModifyComponent from "../../components/member/ModifyComponent";
import BasicLayout from "../../layouts/BasicLayout";
const ModifyPage = () => {
return (
<BasicLayout>
<div className=" text-3xl">Member Modify Page</div>
<div className="bg-white w-full mt-4 p-2">
<ModifyComponent></ModifyComponent>
</div>
</BasicLayout>
);
};
export default ModifyPage;
* 마지막으로 /member/modify 경로에 대한 라우팅 관련 처리를 추가한다.
import React, { Suspense, lazy } from "react";
const Loading = <div>Loading....</div>;
const Login = lazy(() => import("../pages/member/LoginPage"));
const Logout = lazy(() => import("../pages/member/LogoutPage"));
const KakaoRedirect = lazy(() => import("../pages/member/KakaoRedirectPage"));
const MemberModify = lazy(() => import("../pages/member/ModifyPage"));
const memberRouter = () => {
return [
{
path: "login",
element: (
<Suspense fallback={Loading}>
<Login />
</Suspense>
),
},
{
path: "logout",
element: (
<Suspense fallback={Loading}>
<Logout />
</Suspense>
),
},
{
path: "kakao",
element: (
<Suspense fallback={Loading}>
<KakaoRedirect />
</Suspense>
),
},
{
path: "modify",
element: (
<Suspense fallback={Loading}>
<MemberModify />
</Suspense>
),
},
];
};
export default memberRouter;
* 데이터베이스에 존재하지 않는 사용자이거나 카카오 로그인만 했던 사용자의 경우 자동으로 /member/modify 로 이동하는 것을 확인한다.
(2) API 서버의 회원정보 수정
* API 서버에서는 회원정보를 수정할 수 있도록 기능을 추가한다. 우선 회원정보를 의미하는 MemberDTO는 스프링 시큐리티와 관련해서 생성자 함수가 존재하므로 컨트롤러에서 파라미터 수집 시에 불편하기 때문에 별도로 MemberModifyDTO를 작성한다.
package com.unlimited.mallapi.dto;
import lombok.Data;
@Data
public class MemberModifyDTO {
private String email;
private String pw;
private String nickname;
}
* MemberService와 MemberServiceImpl에 modify() 기능을 추가한다.
package com.unlimited.mallapi.service;
import java.util.stream.Collectors;
import org.springframework.transaction.annotation.Transactional;
import com.unlimited.mallapi.domain.Member;
import com.unlimited.mallapi.dto.MemberDTO;
import com.unlimited.mallapi.dto.MemberModifyDTO;
// 트랜잭션 처리 어노테이션, rollbackFor 속성을 사용하여 모든 예외에 대해 롤백 처리
@Transactional(rollbackFor = Exception.class)
public interface MemberService {
// Kakao의 AccessToken을 이용해 회원 정보를 가져오는 메서드
MemberDTO getKakaoMember(String accessToken);
// Entity(Member)를 DTO(MemberDTO)로 변환하는 디폴트 메서드
default MemberDTO entityToDTO(Member member) {
// Member 엔티티에서 MemberDTO로 필요한 정보를 복사하여 변환
MemberDTO dto = new MemberDTO(
member.getEmail(),
member.getPw(),
member.getNickname(),
member.isSocial(),
// Member 엔티티에 속한 MemberRole 열거형을 문자열 리스트로 변환
member.getMemberRoleList().stream().map(memberRole -> memberRole.name()).collect(Collectors.toList()));
return dto; // 변환된 DTO 반환
}
/**
* 회원 정보를 수정합니다.
*
* @param memberModifyDTO 수정할 회원 정보를 담고 있는 DTO 객체
*/
void modifyMember(MemberModifyDTO memberModifyDTO);
}
* MemberServiceImpl에서는 변경이 가능한 패스워드와 닉네임을 수정하고 social 속성값을 false 로 변경한다.
/**
* 회원 정보를 수정합니다.
*
* @param memberModifyDTO 수정할 회원 정보를 담고 있는 DTO 객체
*/
@Override
public void modifyMember(MemberModifyDTO memberModifyDTO) {
// 회원 이메일을 기반으로 회원을 조회
Optional<Member> result = memberRepository.findById(memberModifyDTO.getEmail());
// 회원이 존재하지 않으면 예외를 던집니다.
Member member = result.orElseThrow();
// 비밀번호를 암호화하여 변경합니다.
member.changePw(passwordEncoder.encode(memberModifyDTO.getPw()));
// 소셜 로그인 여부를 변경합니다.
member.changeSocial(false);
// 닉네임을 변경합니다.
member.changeNickName(memberModifyDTO.getNickname());
// 변경된 회원 정보를 저장합니다.
memberRepository.save(member);
}
* SocialController에 회원정보 수정 시에 사용할 메서드를 추가한다.
/**
* 회원 정보를 수정하는 엔드포인트입니다.
*
* @param memberModifyDTO 수정할 회원 정보를 담은 DTO 객체
* @return 수정 결과를 나타내는 Map (예: {"result": "modified"})
*/
@PutMapping("/api/member/modify")
public Map<String, String> modify(@RequestBody MemberModifyDTO memberModifyDTO) {
log.info("회원 수정: " + memberModifyDTO);
// 회원 정보 수정 서비스를 호출하여 회원 정보를 변경합니다.
memberService.modifyMember(memberModifyDTO);
// 수정 결과를 포함한 Map을 반환합니다.
return Map.of("result", "modified");
}
(3) 리액트와 API 연동
* 리액트에서는 작성된 /api/member/modify 경로를 호출하는 코드를 memberApi.js에 추가한다. 회원정보 수정은 로그인이 될 수 있는 사용자만 가능하므로 jwtAxios를 이용한다.
import axios from "axios";
import { API_SERVER_HOST } from "./todoApi";
import jwtAxios from "../util/jwtUtil";
const host = `${API_SERVER_HOST}/api/member`;
export const loginPost = async (loginParam) => {
const header = { headers: { "Content-Type": "x-www-form-urlencoded" } };
const form = new FormData();
form.append("username", loginParam.email);
form.append("password", loginParam.pw);
const res = await axios.post(`${host}/login`, form, header);
return res.data;
};
export const modifyMember = async (member) => {
const res = await jwtAxios.put(`${host}/modify`, member);
return res.data;
};
* 화면을 구성하는 components/member/ModifyComponent에서는 modifyMember()를 호출하도록 수정한다.
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { modifyMember } from "../../api/memberApi";
const initState = {
email: "",
pw: "",
nickname: "",
};
const ModifyComponent = () => {
(...)
const handleClickModify = () => {
modifyMember(member);
};
return (
<div className="mt-6">
(...)
<div className="flex justify-center">
<div className="relative mb-4 flex w-full flex-wrap justify-end">
<button
type="button"
className="rounded p-4 m-2 text-xl w-32 text-white bg-blue-500"
onClick={handleClickModify}
>
Modify
</button>
</div>
</div>
</div>
);
};
export default ModifyComponent;
* 브라우저에서 카카오 로그인 후 에 회원정보를 수정해 보자. 화면에는 변화가 없지만 데이터베이스에는 변경된 내용을 확인할 수 있는데 특히 기존에 소셜 회원(social) 속성값이 변경된 것을 볼 수 있다.
[수정 후 다시 로그인 하기]
* API 서버에서 정상적으로 회원정보가 수정되었다면 결과를 보여주고 다시 로그인 화면으로 이동해서 로그인 해보도록 한다. 일반 회원으로 전환된 사용자는 카카오 로그인으로 하거나 일반 로그인 모두 가능하다.
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { modifyMember } from "../../api/memberApi";
import useCustomLogin from "../../hooks/useCustomLogin";
import ResultModal from "../common/ResultModal";
const initState = {
email: "",
pw: "",
nickname: "",
};
const ModifyComponent = () => {
const [member, setMember] = useState(initState);
const loginInfo = useSelector((state) => state.loginSlice);
const { moveToLogin } = useCustomLogin();
const [result, setResult] = useState();
useEffect(() => {
setMember({ ...loginInfo, pw: "ABCD" });
}, [loginInfo]);
const handleChange = (e) => {
member[e.target.name] = e.target.value;
setMember({ ...member });
};
const handleClickModify = () => {
modifyMember(member).then((result) => {
setResult("Modified");
});
};
const closeModal = () => {
setResult(null);
moveToLogin();
};
return (
<div className="mt-6">
{result ? (
<ResultModal
title={"회원정보"}
content={"정보수정완료"}
callbackFn={closeModal}
></ResultModal>
) : (
<></>
)}
(...)
</div>
);
};
export default ModifyComponent;
* 회원정보가 수정되면 로그아웃이 되면서 로그인 페이지로 이동하게 된다. 이미 일반 회원으로 전환되었기 때문에 카카오 로그인을 하더라도 / 경로로 이동하게 된다.
'SpringBoot_React 풀스택 프로젝트' 카테고리의 다른 글
(44) 장바구니 API 만들기 2 (0) | 2024.03.08 |
---|---|
(43) 장바구니 API 만들기 1 (0) | 2024.03.08 |
(41) 리액트 소셜 로그인 4 (0) | 2024.03.07 |
(40) 리액트 소셜 로그인 3 (0) | 2024.03.07 |
(39) 리액트 소셜 로그인 2 (0) | 2024.03.07 |