React

[React] 소셜 로그인(3)

HoneyIT 2025. 4. 26. 14:40
반응형

[리액트 로그인 처리]

const loginSlice = createSlice({
    name: 'LoginSlice',
    initialState : loadMemberCookie() || initState, // 쿠키가 없으면 초기값 사용
    reducers: {
        login: (state, action) =>{
            console.log("login.....")

            //소셜로그인 회원이 사용
            const payload = action.payload

            setCookie("member", JSON.stringify(payload), 1) // 1일
            return payload
        },
        logout: (state, action) => {
            console.log("logout.....")

            removeCookie('member')

            return {...initState}
        }
    },
    extraReducers: (builder) => {
        ...  생략
    }
})

export const {login,logout} = loginSlice.actions

export default loginSlice.reducer

- API 서버에서 받은 데이터는 기존의 로그인 데이터 구성과 동일하기 때문에 loginSlice의 login()을 사용해서 처리합니다.

 

const KakaoRedirectPage = () => {

    const [searchParams] = useSearchParams()

    const dispatch = useDispatch();

    const authCode = searchParams.get("code")

    useEffect(() => {

        getAccessToken(authCode).then(accessToken => {
            console.log(accessToken)

            getMemberWithAccessToken(accessToken).then(memberInfo => {

                console.log("-------------")
                console.log(memberInfo)

                dispatch(login(memberInfo))
            })
        })
    }, [authCode])

    return (
        <div>
            <div>Kakao Login Redirect</div>
            <div>{authCode}</div>
        </div>
    )
}

- KakaoRedirectpage에서 API 서버로 전송받은 결과를 dispatch()를 이용해서 login()을 호출합니다.

 

- API 서버에서 전송된 결과로 member 쿠키가 생성되는 것을 확인할 수 있습니다.

 

[화면 이동 처리]

//소셜 회원이 아니라면
if(memberInfo && !memberInfo.social){
    moveToPath("/")
} else {
    moveToPath("/member/modify")
}

- KakaoRedirectPage.js의 dispatch 하위에 로그인 시 소셜 회원 여부에 대한 경로 이동 로직을 추가해줍니다.

 

import { useEffect, useState } from "react";
import { useSelector } from "react-redux/es/hooks/useSelector";

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={'type'}
                    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;

- modifyComponent를 생성합니다.

 

import ModifyComponent from "../../components/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 디렉토리 하위에 modifyPage를 생성하고 modifyComponent를 사용해줍니다.

 

{
   path:"modify",
   element: <Suspense fallback={Loading}><MemberModify/></Suspense>
}

- memberRouter에 modify 페이지를 추가하면 소셜로그인 시 modify 페이지가 정상적으로 뜨는 것을 확인할 수 있습니다.

 

[API 서버 회원정보 수정]

package org.zerock.mallapi.dto;

import lombok.Data;

@Data
public class MemberModifyDTO {
    
    private String email;

    private String pw;

    private String nickname;
}

- dto 하위에 MemberModifyDTO를 생성해줍니다.

 

@Transactional
public interface MemberService {
    
    MemberDTO getKakaoMember(String accessToken);

    void modifyMember(MemberModifyDTO memberModifyDTO);

    default MemberDTO entityToDTO(Member member){
        MemberDTO dto = new MemberDTO(member.getEmail(), member.getPw(), member.getNickname(), member.isSocial(), member.getMemberRoleList().stream().map(memberRole -> memberRole.name()).collect(Collectors.toList()));
        return 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);
}

- memberService, memberServiceImpl에 modify() 기능을 추가합니다.

- 변경이 가능한 패스워드와 닉네임을 수정하고 social 속성 값을 false로 변경합니다.

 

@PutMapping("/api/member/modify")
public Map<String, String> modify(@RequestBody MemberModifyDTO memberModifyDTO) {
        
    log.info("member modify: " + memberModifyDTO);

    memberService.modifyMember(memberModifyDTO);

   return Map.of("result", "modified");
}

- socialController에 회원정보 수정 시 사용할 메서드를 추가합니다.

 

[리액트 API 연동]

export const modifyMember = async (member) => {
    const res = await jwtAxios.put(`${host}/modify`, member)
    return res.data
}

- memberApi.js에 /api/member/modify 경로를 호출하는 코드를 추가합니다.

- 회원정보 수정은 로그인이 될 수 있는 사용자만 가능하므로 jwtAxios를 이용합니다.

 

const handleClickModify = () => {
   modifyMember(member)
}

- ModifyComponent.js에 수정을 위한 handleClickModify 함수를 추가합니다.

 

- modify 페이지에서 소셜로그인 사용자 계정을 저장하면 social에 false(bit(1) 형태로는 0)으로 잘 저장된 것을 확인할 수 있습니다.

 

[수정 후 다시 로그인]

const colseModal = () => {
    setResult(null)
    moveToLogin()
}

return (
        <div className="mt-6">
            {result? <ResultModal title={'회원정보'} content={'정보수정완료'} collbackFn={colseModal}></ResultModal>:<></>}
            ...생략
    )
}

- 이전에 생성해놓은 modal 컴포넌트로 수정완료 처리를 해줍니다.

반응형