Today I Learned (TIL)
24.01.23 수파베이스 스토리지 안쓰고 이미지 업로드
폼폼코
2024. 1. 23. 22:07
728x90
반응형
추가 기능 구현 중에 있습니다
간단한 닉네임 중복검사
const checkNickname = async (nickname: string) => {
// 닉네임 중복 검사 (userinfo테이블 기준입니다 /mypage에서 닉네임 변경할때도 userinfo만 변경됩니다)
const { data, error } = await supabase
.from('userinfo')
.select('nickname')
.eq('nickname', nickname);
if (error) {
console.log('닉네임 중복 검사 에러');
return;
}
return data.length > 0;
//닉네임 중복이 맞다면 true를 뱉어냄
};
수파베이스 유저정보를 모아둔 테이블에서 중복되는 닉네임이 있다면 ture를 반환합니다
const isDeduplication = await checkNickname(formData.displayName);
if (isDeduplication) {
alert('이미 사용죽인 닉네임입니다, 다른 닉네임을 사용해주세요');
return;
}
그리고 기존 유효성 검사에 추가


정상 작동은 하지만 다만 form 안에 집어넣어서 그런지 alert이 2번 떠버리네요
따로 분리해야겠습니다
는 아주 간단한 거였고요
<button onClick={handleCheckNickname} type="button">
닉네임 중복 확인
</button>
Form 안에 onSubmit 이 있어서 type="button" 을 넣어줘야 개별 버튼으로 인식합니다
아니면 그냥 온 서브 맛이라 다 같이 입력되어버립니다...!
<StyledH1>회원 가입</StyledH1>
<StyledForm onSubmit={handleSignup}>
<StInputGroup>
{' '}
<StyledLabel htmlFor="displayName">닉네임</StyledLabel>
<StyledInput
placeholder="사용할 닉네임을 적어주세요."
type="text"
id="displayName"
name="displayName"
value={formData.displayName}
onChange={handleChange}
/>
{errors.displayName && <p>{errors.displayName}</p>}
</StInputGroup>
<button onClick={handleCheckNickname} type="button">
닉네임 중복 확인
</button>
이메일 중복검사도 동일한 코드에서 nickName 부분을 email로 바꿔서 구현


이제 회원가입 페이지는 최종 완료입니다

프로필 이미지도 업로드해 봤는데
수파베이스 스토리지를 사용하지 않고 base64를 이용하여 바로 테이블에 url 형태로 들어갑니다
(단점은 이미지 파일 용량이 클 경우 DB 성능이 저하될 우려가 있음)
import React, { useCallback, useRef, useState } from 'react';
import styled from 'styled-components';
import boomarkIcon from 'assets/icons/boomarkIcon.svg';
import communityIcon from 'assets/icons/communityIcon.svg';
import editProfileIcon from 'assets/icons/editProfileIcon.svg';
import userimg from 'assets/img/userimg.png';
import { useSelector } from 'react-redux';
import { RootState } from 'redux/config/configStore';
import { useDispatch } from 'react-redux';
import { supabase } from 'shared/supabase';
interface MypageProps {
onCategoryChange: (category: string) => void;
}
const MypageNav = ({ onCategoryChange }: MypageProps) => {
const user = useSelector((state: RootState) => state.userSlice.userInfo);
const dispatch = useDispatch();
const fileInputRef = useRef<HTMLInputElement>(null);
const [imageUrl, setImageUrl] = useState(user?.profile ? user.profile : userimg);
// 이미지를 업로드하면 base64로 바꿔 imgurl로 저장함
const handleFileChange = useCallback(
async (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target) {
const selectedFile = e.target.files;
if (selectedFile && selectedFile.length > 0) {
const fileReader = new FileReader();
fileReader.onload = async (event) => {
if (event.target) {
const imageUrl = event.target.result as string;
setImageUrl(imageUrl);
if (user) {
// id가 존재하는지 확인
// 'userinfo' 테이블의 'avatar_url' 업데이트
const { error } = await supabase.from('userinfo').update({ avatar_url: imageUrl }).eq('id', user.id);
if (error) {
console.log('프로필 이미지 업데이트 중 에러가 발생했습니다:', error.message);
alert('이미지 업로드 오류');
} else {
console.log('프로필 이미지가 성공적으로 업데이트되었습니다.');
alert('이미지 업로드 성공!');
window.location.reload();
}
} else {
console.log('유저 ID가 존재하지 않습니다.');
alert('유저 ID가 존재하지 않습니다.');
}
}
};
fileReader.readAsDataURL(selectedFile[0]);
}
}
},
[user]
);
const triggerFileInput = useCallback(() => {
if (fileInputRef.current) {
fileInputRef.current.value = '';
fileInputRef.current.click();
}
}, [fileInputRef]);
console.log(imageUrl);
return (
<StContainer>
<StUserProfileWrapper>
<StProfileImageWrapper>
<img src={user?.avatar_url} alt="프로필이미지" />
</StProfileImageWrapper>
<a onClick={triggerFileInput}>프로필 이미지 변경</a>
<p>{user?.nickname ? user.nickname : 'KAKAO USER'}</p>
<input
type="file"
accept="image/*"
style={{ display: 'none' }}
ref={fileInputRef}
onChange={handleFileChange}
/>
</StUserProfileWrapper>
<StCategoryWrapper>
<StCategory onClick={() => onCategoryChange('profile')}>
<StEditProfileIcon />
<p>프로필 편집</p>
</StCategory>
<StCategory onClick={() => onCategoryChange('community')}>
<StCommunityIcon />
<p>내가 쓴 글</p>
</StCategory>
<StCategory onClick={() => onCategoryChange('bookmark')}>
<StBookMarkIcon />
<p>찜 목록</p>
</StCategory>
</StCategoryWrapper>
</StContainer>
);
};
export default MypageNav;
그리고 마이페이지에서 닉네임 수정 시에 유효성 검사 도 추가했습니다
const handleNickNameChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const nickname = e.target.value;
dispatch(setUser({ ...user, nickname: nickname } as UserData));
if (nickname.length < 2 || nickname.length > 6) {
setNicknameError('닉네임은 최소 2글자, 최대 6글자로 작성해주세요.');
setIsValid(false); // 유효성 검사에 실패하면 isValid를 false로 설정합니다.
return;
}
const isDuplicate = await checkNickname(nickname);
if (isDuplicate) {
setNicknameError('이미 사용 중인 닉네임입니다. 다른 닉네임을 선택해주세요.');
setIsValid(false); // 유효성 검사에 실패하면 isValid를 false로 설정합니다.
return;
}
setNicknameError(''); // 에러가 없다면 에러 메시지를 초기화합니다.
setIsValid(true); // 모든 유효성 검사를 통과하면 isValid를 true로 설정합니다.
};
이전에 사용한 코드 재활용했습니다
<StUserinfoBox>
<h2>프로필 정보</h2>
<label htmlFor="nickname">닉네임(필수)</label>
<input
id="nickname"
type="text"
value={nickname ? nickname : ''}
onChange={handleNickNameChange}
maxLength={6}
minLength={1}
placeholder={nickname || '닉네임 (2 ~ 6자)'}
autoComplete="off"
/>
<StNickNameCount>{nickname?.length ? nickname.length : '0'} / 30</StNickNameCount>
실시간 유효성 검사 진행 가능하고
const [isValid, setIsValid] = useState<boolean>(true);
const isButtonDisabled = !(
nickname &&
nickname.length >= 2 &&
nickname.length <= 6 &&
profile &&
profile.length >= 10 &&
isValid
);
<Button type="submit" size="medium" disabled={isButtonDisabled}>
수정하기
</Button>
버튼에 disabled 추가해서 유효성 검사 통과 못하면 수정하기 버튼 비활성화했습니다

이제 비밀번호 변경만 구현하면 얼추 마무리..!
728x90
반응형