Skip to content

Commit

Permalink
Merge pull request #43 from pepero-1/feature/#9
Browse files Browse the repository at this point in the history
Feature/#9 토큰 갱신, 회원 가입 아이디 중복 메시지
  • Loading branch information
2YH02 authored Nov 15, 2023
2 parents a37eacb + 77ad46e commit 8b25f91
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 155 deletions.
1 change: 1 addition & 0 deletions src/components/Game/Vote/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const Vote: React.FC<VoteProps> = ({
};

fetchDataFromFirebase();
// eslint-disable-next-line
}, []);

const user = useRecoilValue(userState);
Expand Down
287 changes: 151 additions & 136 deletions src/components/Login/SignUpModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import React, { useCallback, useRef, useState } from "react";
import { useForm, Controller } from "react-hook-form";
import {
Input,
Button,
Text,
Alert,
AlertIcon,
Modal,
ModalOverlay,
ModalContent,
ModalCloseButton,
ModalBody,
Box,
Button,
FormControl,
FormErrorMessage,
Box,
Image,
Input,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalOverlay,
Spinner,
Text,
} from "@chakra-ui/react";
import useFetch from "../../../hooks/useFetch";
import axios from "axios";
import styled from "styled-components";
import React, { useCallback, useRef, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { FaImage } from "react-icons/fa";
import styled from "styled-components";

interface FormData {
id: string;
Expand Down Expand Up @@ -56,19 +56,14 @@ const SignUpModal = ({ isOpen, onClose }: SignUpModalProps) => {
clearErrors,
formState: { errors },
} = useForm<FormData>();
const signUpFetch = useFetch({
url: "https://fastcampus-chat.net/signup",
method: "POST",
data: {},
start: false,
});
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [signUpStatus, setSignUpStatus] = useState<{
type: "success" | "error";
message: string;
} | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);

const [isLoading, setIsLoading] = useState(false);
const MAX_IMAGE_SIZE = 1024 * 1024; // 1MB
// ID 중복 검사
const checkIdDuplication = async (id: string): Promise<boolean> => {
try {
Expand All @@ -86,6 +81,11 @@ const SignUpModal = ({ isOpen, onClose }: SignUpModalProps) => {
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files ? event.target.files[0] : null;
if (file) {
if (file.size > MAX_IMAGE_SIZE) {
// 파일 크기 초과 에러 처리
alert("이미지는 1MB 이하이어야 합니다.");
return;
}
setSelectedFile(file);
}
};
Expand All @@ -110,6 +110,7 @@ const SignUpModal = ({ isOpen, onClose }: SignUpModalProps) => {

// 회원가입 제출
const onSubmit = async (formData: FormData) => {
setIsLoading(true); // 로딩 시작
let pictureAsString: string | undefined;

// 파일을 Base64 문자열로 변환
Expand Down Expand Up @@ -150,9 +151,20 @@ const SignUpModal = ({ isOpen, onClose }: SignUpModalProps) => {
});
}
} catch (error) {
console.error("회원가입 실패:", error);
setSignUpStatus({ type: "error", message: "회원가입에 실패하였습니다." });
if (axios.isAxiosError(error) && error.response?.status === 401) {
setError("id", {
type: "manual",
message: "이미 사용중인 ID입니다.",
});
} else {
console.error("회원가입 실패:", error);
setSignUpStatus({
type: "error",
message: "회원가입에 실패하였습니다.",
});
}
}
setIsLoading(false); // 로딩 종료
};

return (
Expand All @@ -176,122 +188,125 @@ const SignUpModal = ({ isOpen, onClose }: SignUpModalProps) => {
{signUpStatus.message}
</Alert>
)}
{/* 회원가입 폼 */}
<form onSubmit={handleSubmit(onSubmit)}>
<DragDropBox
onDrop={onDrop}
onDragOver={onDragOver}
onClick={triggerFileSelect}
>
{selectedFile ? (
<Image
src={URL.createObjectURL(selectedFile)}
alt="Upload Preview"
boxSize="40px"
/>
) : (
<FaImage size="40px" />
)}
<Text>이미지 업로드</Text>
<Input
type="file"
accept="image/*"
onChange={handleFileChange}
ref={fileInputRef}
display="none"
/>
</DragDropBox>
{/* 아이디 입력 */}
<FormControl isInvalid={!!errors.id} my={4} justifyContent="center">
<Controller
name="id"
control={control}
rules={{
required: "ID is required.",
pattern: {
value: /^[A-Za-z0-9]+$/,
message: "ID는 영문자와 숫자만 사용할 수 있습니다.",
},
validate: async (id) => {
try {
const isDuplicated = await checkIdDuplication(id);
if (isDuplicated) {
setError("id", {
type: "manual",
message: "이미 사용중인 ID입니다.",
});
return false;
}
clearErrors("id");
return true;
} catch (error) {
console.error("ID 중복 확인 중 오류 발생:", error);
return "ID 중복 확인 중 오류가 발생했습니다.";
}
},
}}
render={({ field }) => (
<Input
type="text"
placeholder="아이디 입력"
{...field}
width="300px"
m="auto"
{isLoading ? (
// 로딩 중 스피너 표시
<Spinner size="xl" />
) : (
// 로딩이 완료되면 회원가입 내용 표시
<form onSubmit={handleSubmit(onSubmit)}>
<DragDropBox
onDrop={onDrop}
onDragOver={onDragOver}
onClick={triggerFileSelect}
>
{selectedFile ? (
<Image
src={URL.createObjectURL(selectedFile)}
alt="Upload Preview"
boxSize="40px"
/>
) : (
<FaImage size="40px" />
)}
/>
<FormErrorMessage>
{errors.id && errors.id.message}
</FormErrorMessage>
</FormControl>
{/* 비밀번호 입력 */}
<FormControl isInvalid={!!errors.password} my={4}>
<Controller
name="password"
control={control}
rules={{
required: "비밀번호는 필수입니다.",
minLength: {
value: 5,
message: "비밀번호는 5자리 이상이어야 합니다.",
},
}}
render={({ field }) => (
<Input
type="password"
placeholder="비밀번호 입력"
{...field}
/>
)}
/>
<FormErrorMessage>
{errors.password && errors.password.message}
</FormErrorMessage>
</FormControl>
{/* 닉네임 입력 */}
<FormControl isInvalid={!!errors.name} my={4}>
<Controller
name="name"
control={control}
rules={{ required: "이름은 필수입니다." }}
render={({ field }) => (
<Input type="text" placeholder="닉네임" {...field} />
)}
/>
<FormErrorMessage>
{errors.name && errors.name.message}
</FormErrorMessage>
</FormControl>
<Button
type="submit"
isLoading={signUpFetch.loading}
width="300px"
m="auto"
my={14}
>
회원가입
</Button>
</form>
<Text>이미지 업로드</Text>
<Input
type="file"
accept="image/*"
onChange={handleFileChange}
ref={fileInputRef}
display="none"
/>
</DragDropBox>
{/* 아이디 입력 */}
<FormControl
isInvalid={!!errors.id}
my={4}
justifyContent="center"
>
<Controller
name="id"
control={control}
rules={{
required: "ID is required.",
pattern: {
value: /^[A-Za-z0-9]+$/,
message: "ID는 영문자와 숫자만 사용할 수 있습니다.",
},
validate: async (id) => {
try {
const isDuplicated = await checkIdDuplication(id);
if (isDuplicated) {
setError("id", {
type: "manual",
message: "이미 사용중인 ID입니다.",
});
return false;
}
clearErrors("id");
return true;
} catch (error) {
console.error("ID 중복 확인 중 오류 발생:", error);
return "ID 중복 확인 중 오류가 발생했습니다.";
}
},
}}
render={({ field }) => (
<Input
type="text"
placeholder="아이디 입력"
{...field}
width="300px"
m="auto"
/>
)}
/>
<FormErrorMessage>
{errors.id && errors.id.message}
</FormErrorMessage>
</FormControl>
{/* 비밀번호 입력 */}
<FormControl isInvalid={!!errors.password} my={4}>
<Controller
name="password"
control={control}
rules={{
required: "비밀번호는 필수입니다.",
minLength: {
value: 5,
message: "비밀번호는 5자리 이상이어야 합니다.",
},
}}
render={({ field }) => (
<Input
type="password"
placeholder="비밀번호 입력"
{...field}
/>
)}
/>
<FormErrorMessage>
{errors.password && errors.password.message}
</FormErrorMessage>
</FormControl>
{/* 닉네임 입력 */}
<FormControl isInvalid={!!errors.name} my={4}>
<Controller
name="name"
control={control}
rules={{ required: "이름은 필수입니다." }}
render={({ field }) => (
<Input type="text" placeholder="닉네임" {...field} />
)}
/>
<FormErrorMessage>
{errors.name && errors.name.message}
</FormErrorMessage>
</FormControl>
<Button type="submit" width="300px" m="auto" my={14}>
회원가입
</Button>
</form>
)}
</ModalBody>
</ModalContent>
</Modal>
Expand Down
Loading

0 comments on commit 8b25f91

Please sign in to comment.