// 기존 코드
import pb from '@/api/pocketbase';
import ProfileUpload from '@/components/ProfileUpload/ProfileUpload';
import UserProfilePicture from '@/components/UserProfilePicture/UserProfilePicture';
import useFetchData from '@/hooks/useFetchData';
import useStorage from '@/hooks/useStorage';
import { useEffect, useState } from 'react';
import S from './UserProfileEdit.module.css';
const PB = import.meta.env.VITE_PB_URL;
const PB_USER_ENDPOINT = `${PB}/api/collections/users/records`;
function UserProfileEdit() {
const { data: userData, isLoading } = useFetchData(PB_USER_ENDPOINT);
const [nickname, setNickname] = useState('');
const [validationResult, setValidationResult] = useState('');
const [usernames, setUsername] = useState([]);
const { storageData: pocketbaseAuthData } = useStorage('pocketbase_auth');
useEffect(() => {
if (userData.items) {
const usernames = userData.items.map((user) =>
user.username.toLowerCase()
);
setUsername(usernames);
}
}, [userData.items]);
const handleCheckDuplicate = () => {
if (isLoading) {
return;
}
if (
nickname.length >= 2 &&
nickname.length <= 10 &&
/^[a-zA-Z0-9]+$/.test(nickname)
) {
const isDuplicate = usernames.includes(nickname.toLowerCase());
if (isDuplicate) {
setValidationResult('중복된 닉네임입니다.');
} else {
setValidationResult('사용가능한 닉네임입니다');
}
} else {
setValidationResult('2 ~ 10글자의 영문 대소문자와 숫자만 입력하세요.');
}
};
const handleNicknameChange = (value) => {
if (
value.length >= 2 &&
value.length <= 10 &&
/^[a-zA-Z0-9]+$/.test(value)
) {
setNickname(value);
setValidationResult('');
} else {
setNickname(value);
setValidationResult('2 ~ 10글자의 영문 대소문자와 숫자만 입력하세요.');
}
};
const handleButtonClick = () => {
if (!isLoading) {
handleCheckDuplicate();
}
};
const updateUsernameOnServer = async () => {
try {
const response = await pb
.collection('users')
.update(pocketbaseAuthData.id, {
username: nickname,
});
if (response.status === 200) {
return true;
} else {
return false;
}
} catch (error) {
console.error('서버 요청 오류:', error);
return false;
}
};
const username =
pocketbaseAuthData?.model?.username || '사용자 이름이 없습니다';
if (pocketbaseAuthData) {
return (
<div className={S.profile}>
<UserProfilePicture avatar={pocketbaseAuthData?.model} />
<div className="h-[90px] flex">
<span className="mt-5 text-xl font-semibold">{username}</span>
</div>
<div className={S.editwrapper}></div>
<div className="flex flex-col gap-6 mt-10">
<div className="flex gap-4 items-end">
<div className='flex flex-col'>
<label htmlFor="nickname" className="text-sm">
닉네임
</label>
<div className="flex gap-4">
<input
type="text"
id="nickname"
name="nickname"
placeholder="2 ~ 10문자(특수문자 사용불가)"
className={S.nick}
value={nickname}
onChange={(e) => handleNicknameChange(e.target.value)}
/>
<button
onClick={() => {
handleButtonClick();
}}
className={`h-10 text-gray-900 border rounded px-2 ${
nickname.length >= 2 &&
nickname.length <= 10 &&
/^[a-zA-Z0-9]+$/.test(nickname)
? 'bg-primary'
: 'bg-gray-300 cursor-not-allowed'
}`}
disabled={
isLoading ||
!(
nickname.length >= 2 &&
nickname.length <= 10 &&
/^[a-zA-Z0-9]+$/.test(nickname)
)
}
>
중복확인
</button>
</div>
{validationResult && (
<div
className={
validationResult === '중복된 닉네임입니다.'
? 'text-red-500 text-xs'
: 'text-green-500 text-xs'
}
>
{validationResult}
</div>
)}
</div>
</div>
<ProfileUpload />
</div>
<button
onClick={async () => {
if (!isLoading && validationResult === '사용가능한 닉네임') {
const isUsernameUpdated = await updateUsernameOnServer();
if (isUsernameUpdated) {
pocketbaseAuthData.username = nickname;
} else {
console.error('서버에서 username 업데이트 실패');
}
}
}}
className={S.save}
disabled={isLoading || validationResult !== '사용가능한 닉네임'}
>
저장하기
</button>
</div>
);
}
}
export default UserProfileEdit;
변ㄴ경후
import pb from '@/api/pocketbase';
import ProfileUpload from '@/components/ProfileUpload/ProfileUpload';
import UserProfilePicture from '@/components/UserProfilePicture/UserProfilePicture';
import useFetchData from '@/hooks/useFetchData';
import PropTypes from 'prop-types';
import { useEffect, useState } from 'react';
import toast from 'react-hot-toast';
import { useNavigate, useParams } from 'react-router-dom';
import style from './UserProfileEdit.module.css';
import useAuthStore from '@/store/auth';
const PB = import.meta.env.VITE_PB_URL;
const PB_USER_ENDPOINT = `${PB}/api/collections/users/records`;
function UserProfileEdit() {
const navigate = useNavigate();
const { userId } = useParams();
const { data: userData, isLoading } = useFetchData(PB_USER_ENDPOINT);
const [nickname, setNickname] = useState('');
const [validationResult, setValidationResult] = useState('');
const [usernameList, setUserNameList] = useState([]);
const user = useAuthStore((store) => store.user);
const checkLogIn = useAuthStore((store) => store.checkLogIn);
const handleCheckDuplicate = () => {
if (isLoading) {
return;
}
if (
nickname.length >= 5 &&
nickname.length <= 10 &&
/^[a-zA-Z0-9]+$/.test(nickname)
) {
const isDuplicate = usernameList.includes(nickname.toLowerCase());
if (isDuplicate) {
setValidationResult('중복된 닉네임입니다.');
} else {
setValidationResult('사용가능한 닉네임입니다');
}
} else {
setValidationResult('5~ 10글자의 영문 대소문자와 숫자만 입력하세요.');
}
};
const handleNicknameChange = (value) => {
if (
value.length >= 5 &&
value.length <= 10 &&
/^[a-zA-Z0-9]+$/.test(value)
) {
setNickname(value);
setValidationResult('');
} else {
setNickname(value);
setValidationResult('5 ~ 10글자의 영문 대소문자와 숫자만 입력하세요.');
}
};
const handleButtonClick = () => {
if (!isLoading) {
handleCheckDuplicate();
}
};
const updateUsernameOnServer = async () => {
try {
return await pb.collection('users').update(userId, {
username: nickname,
});
} catch (error) {
console.error('서버 요청 오류:', error);
return false;
}
};
const isNicknameValid =
nickname.length >= 5 &&
nickname.length <= 10 &&
/^[a-zA-Z0-9]+$/.test(nickname);
const isNicknameAvailable = validationResult === '사용가능한 닉네임입니다';
const isSaveButtonDisabled = isLoading || !isNicknameAvailable;
async function handleSaveButtonClick() {
try {
if (!isLoading && isNicknameAvailable) {
const result = await updateUsernameOnServer();
if (result) {
toast.success('성공적으로 변경되었습니다');
navigate(`/mypage/${userId}`);
}
}
} catch (error) {
toast.error(error.message);
}
}
const username = user?.username || '사용자 이름이 없습니다';
useEffect(() => checkLogIn(), [checkLogIn]);
useEffect(() => {
if (userData.items) {
const usernameList = userData.items.map((user) =>
user.username.toLowerCase()
);
setUserNameList(usernameList);
}
}, [userData.items]);
if (!user) {
return null;
}
return (
<div className={style.profile}>
<UserProfilePicture avatar={user} />
<div className="h-[90px] flex">
<span className="mt-5 text-xl font-semibold">{username}</span>
</div>
<div className={style.editwrapper} />
<div className="flex flex-col gap-6 mt-10">
<NicknameInputSection
nickname={nickname}
handleNicknameChange={handleNicknameChange}
handleButtonClick={handleButtonClick}
validationResult={validationResult}
isNicknameValid={isNicknameValid}
/>
<ProfileUpload />
</div>
<button
onClick={handleSaveButtonClick}
className={`${style.save} ${
isSaveButtonDisabled ? 'bg-gray-300' : 'bg-primary'
}`}
disabled={isSaveButtonDisabled}
>
저장하기
</button>
</div>
);
}
function NicknameInputSection({
nickname,
handleNicknameChange,
handleButtonClick,
validationResult,
isNicknameValid,
}) {
return (
<div className="flex gap-4 items-end">
<div className="flex flex-col">
<label htmlFor="nickname" className="text-sm">
닉네임
</label>
<div className="flex gap-4">
<input
type="text"
id="nickname"
name="nickname"
placeholder="5 ~ 10문자(특수문자 사용불가)"
className={style.nick}
value={nickname}
onChange={(e) => handleNicknameChange(e.target.value)}
/>
<button
onClick={handleButtonClick}
className={`h-10 text-gray-900 border rounded px-2 ${
isNicknameValid ? 'bg-primary' : 'bg-gray-300 cursor-not-allowed'
}`}
disabled={!isNicknameValid}
>
중복확인
</button>
</div>
{validationResult && (
<div
className={
validationResult === '중복된 닉네임입니다.'
? 'text-red-500 text-xs'
: 'text-green-500 text-xs'
}
>
{validationResult}
</div>
)}
</div>
</div>
);
}
NicknameInputSection.propTypes = {
nickname: PropTypes.string.isRequired,
handleNicknameChange: PropTypes.func.isRequired,
handleButtonClick: PropTypes.func.isRequired,
validationResult: PropTypes.string,
isNicknameValid: PropTypes.bool.isRequired,
};
export default UserProfileEdit;
중복되는 코드를 정리하고 가독성을 높이기 위해 코드를 정리했다.
prop-types을 정의해서 팀원들이 컴포넌트를 사용할 떄 어떤 타입을 사용하면 되는지 명시해놧다.
닉네임이 수정되지 않는 버그를 수정하였다
입력양식이 충족되지 않을 때에 저장하기 버튼을 disable로 설정하고 사용자가 알아보기 쉽도록 색을 변경하였다.
변경 후 마이페이지로 이동하게 만들었다.