Skip to content

Commit

Permalink
refactor: 고객센터 문의 데이터 전역 관리 방식으로 수정 #80
Browse files Browse the repository at this point in the history
  • Loading branch information
imdaxsz committed Oct 16, 2023
1 parent c863870 commit b404e2a
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 58 deletions.
120 changes: 120 additions & 0 deletions src/contexts/HelpDeskContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { createContext, useCallback, useState } from "react";
import { Outlet } from "react-router-dom";

export interface Form {
email: string;
title: string;
content: string;
}

type FormError = {
[K in keyof Form]: number;
};

interface State {
inquiryType: number;
form: Form;
error: FormError;
agree: boolean;
}

interface FormAction {
updateInquiryType: (i: number) => void;
handleInputChange: (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => void;
handleAgreeChange: () => void;
resetForm: () => void;
validateForm: () => boolean;
}

const INIT_FORM: Form = {
email: "",
title: "",
content: "",
};

const INIT_ERROR: FormError = {
email: 0,
title: 0,
content: 0,
};

export const HelpDeskContext = createContext<State & FormAction>({
inquiryType: 0,
form: INIT_FORM,
agree: false,
error: INIT_ERROR,
updateInquiryType: () => {},
handleInputChange: () => {},
handleAgreeChange: () => {},
resetForm: () => {},
validateForm: () => false,
});

export function HelpDeskProvider() {
const [inquiryType, setInquiryType] = useState(0);
const [form, setForm] = useState(INIT_FORM);
const [agree, setAgree] = useState(false);
const [error, setError] = useState(INIT_ERROR);

const updateInquiryType = (i: number) => {
setInquiryType(i);
};

const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
const { name, value } = e.target;

if (name === "title" && value.length > 50) return;
if (name === "content" && value.length > 1000) return;

setForm((prev) => ({ ...prev, [name]: value }));
};

const handleAgreeChange = () => {
setAgree((prev) => !prev);
};

const resetForm = useCallback(() => {
setForm(INIT_FORM);
setAgree(false);
setError(INIT_ERROR);
}, []);

const validateForm = () => {
const emailPattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
let result = true;

for (const key in form) {
setError((prev) => ({ ...prev, [key]: 0 }));
if (form[key as keyof Form].trim().length === 0) {
setError((prev) => ({ ...prev, [key]: 1 }));
result = false;
} else if (key === "email" && !emailPattern.test(form[key])) {
setError((prev) => ({ ...prev, [key]: 2 }));
result = false;
}
}
return result;
};

const value = {
inquiryType,
form,
agree,
error,
updateInquiryType,
handleInputChange,
handleAgreeChange,
validateForm,
resetForm,
};

return (
<HelpDeskContext.Provider value={value}>
<Outlet />
</HelpDeskContext.Provider>
);
}
53 changes: 21 additions & 32 deletions src/features/common/hooks/useForm.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,33 @@
import { useState } from "react";
import { useContext } from "react";

import useSnackBar from "@/components/SnackBar/useSnackBar";
import { HelpDeskContext } from "@/contexts/HelpDeskContext";

export default function useForm() {
const [form, setForm] = useState({ email: "", title: "", content: "" });
const [error, setError] = useState({ email: 0, title: 0, content: 0 });
const {
form,
agree,
error,
validateForm,
handleInputChange,
handleAgreeChange,
resetForm,
} = useContext(HelpDeskContext);
const snackbar = useSnackBar();

const errorMessage = {
email: ["", "이메일을 입력해 주세요.", "이메일 형식이 올바르지 않습니다."],
title: ["", "문의 제목을 입력해 주세요."],
content: ["", "문의 내용을 입력해 주세요."],
};

const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
const { name, value } = e.target;

if (name === "title" && value.length > 50) return;
if (name === "content" && value.length > 1000) return;

setForm((prev) => ({ ...prev, [name]: value }));
};

const resetForm = () => {
setForm({ email: "", title: "", content: "" });
setError({ email: 0, title: 0, content: 0 });
};

const send = (setSuccess: React.Dispatch<React.SetStateAction<boolean>>) => {
const emailPattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;

for (const key in form) {
setError((prev) => ({ ...prev, [key]: 0 }));
if ((form as Record<string, string>)[key].trim().length === 0) {
setError((prev) => ({ ...prev, [key]: 1 }));
} else if (key === "email" && !emailPattern.test(form[key]))
setError((prev) => ({ ...prev, [key]: 2 }));
}
const isValidate = validateForm();

if (!agree)
snackbar.open({ message: "문의를 남기시려면 약관에 동의해주세요." });

const ok =
emailPattern.test(form.email) &&
form.title.trim().length !== 0 &&
form.content.trim().length !== 0;
const ok = isValidate && agree;

if (ok) {
// TODO: API 요청
Expand All @@ -52,9 +39,11 @@ export default function useForm() {

return {
form,
agree,
error,
errorMessage,
handleInputChange,
handleAgreeChange,
resetForm,
send,
};
Expand Down
28 changes: 23 additions & 5 deletions src/features/common/routes/HelpDesk/Form/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useEffect } from "react";
import { Link } from "react-router-dom";

import Button from "@/components/Button";
Expand All @@ -9,13 +10,25 @@ import { SelectContainer as FormContainer } from "../Select/style";
import { Content, FormItem, FormTextInput, FormTextarea, Terms } from "./style";

interface Props {
goPrev: () => void;
inquiryTypeName: string;
inquiryType: number;
setSuccess: React.Dispatch<React.SetStateAction<boolean>>;
}

export default function Form({ setSuccess }: Props) {
const { form, error, handleInputChange, errorMessage, send } = useForm();
export default function Form({ inquiryType, setSuccess }: Props) {
const {
form,
agree,
error,
handleInputChange,
handleAgreeChange,
errorMessage,
send,
resetForm,
} = useForm();

useEffect(() => {
if (inquiryType === 0) resetForm();
}, [inquiryType, resetForm]);

return (
<FormContainer>
Expand Down Expand Up @@ -59,7 +72,12 @@ export default function Form({ setSuccess }: Props) {
/>
</FormItem>
<Terms>
<CheckBox id="agree" name="agree" checked onChange={() => {}} />
<CheckBox
id="agree"
name="agree"
checked={agree}
onChange={handleAgreeChange}
/>
<label htmlFor="agree">
개인정보 처리 및 <Link to="/terms/email">이용 약관 동의</Link>
</label>
Expand Down
27 changes: 13 additions & 14 deletions src/features/common/routes/HelpDesk/index.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,38 @@
import { CaretLeft } from "@phosphor-icons/react";
import { useState } from "react";
import { useContext, useState } from "react";
import { useNavigate } from "react-router";

import Head from "@/components/Head";
import Header from "@/components/Layout/Header";
import { HelpDeskContext } from "@/contexts/HelpDeskContext";

import Form from "./Form";
import Select from "./Select";
import { HelpDeskContainer, Slide } from "./style";
import Success from "./Success";

export default function HelpDesk() {
const [inquiryType, setInquiryType] = useState(0);
const inquiryTypeName = ["기능 추가 건의", "버그 신고", "기타 문의"];
const [headerTitle, setHeaderTitle] = useState("고객센터");
const [translateX, setTranslateX] = useState(0);
const CHANGE_HEADER_TITLE_DELAY = 200;

const { inquiryType, updateInquiryType } = useContext(HelpDeskContext);
const [headerTitle, setHeaderTitle] = useState(
inquiryType === 0 ? "고객센터" : inquiryTypeName[inquiryType - 1],
);

const [translateX, setTranslateX] = useState(inquiryType === 0 ? 0 : -50);

const [success, setSuccess] = useState(false);
const navigate = useNavigate();

const CHANGE_HEADER_TITLE_DELAY = 200;

const goPrev = () => {
setInquiryType(0);
updateInquiryType(0);
setTranslateX(0);
setTimeout(() => setHeaderTitle("고객센터"), CHANGE_HEADER_TITLE_DELAY);
};

const handleItemClick = (i: number) => {
setInquiryType(i + 1);
updateInquiryType(i + 1);
setTranslateX(-50);
setTimeout(
() => setHeaderTitle(inquiryTypeName[i]),
Expand All @@ -54,12 +58,7 @@ export default function HelpDesk() {
</Header>
<Slide translateX={translateX}>
<Select inquiryTypeName={inquiryTypeName} onClick={handleItemClick} />
<Form
key={inquiryType}
inquiryTypeName={inquiryTypeName[inquiryType - 1]}
goPrev={goPrev}
setSuccess={setSuccess}
/>
<Form inquiryType={inquiryType} setSuccess={setSuccess} />
</Slide>
</HelpDeskContainer>
</>
Expand Down
20 changes: 13 additions & 7 deletions src/routes/publicRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { lazy } from "react";
import { RouteObject } from "react-router-dom";

import Layout from "@/components/Layout";
import { HelpDeskProvider } from "@/contexts/HelpDeskContext";
import Home from "@/features/common/routes/Home";
import ProfileEdit from "@/features/users/routes/Edit";

Expand All @@ -11,7 +12,7 @@ const AnimeList = lazy(() => import("@/features/animes/routes/List"));
const AnimeDetail = lazy(() => import("@/features/animes/routes/Detail"));
const Search = lazy(() => import("@/features/common/routes/Search"));
const HelpDesk = lazy(() => import("@/features/common/routes/HelpDesk"));
const EmailTerms = lazy(() => import("@/features/common/routes/Terms"));
const EmailTerms = lazy(() => import("@/features/common/routes/Terms/Email"));
const NoticeList = lazy(() => import("@/features/notices/routes/List"));
const Profile = lazy(() => import("@/features/users/routes/Profile"));
const NotFound = lazy(() => import("@/features/common/routes/Error/404"));
Expand Down Expand Up @@ -47,12 +48,17 @@ export const publicRoutes: RouteObject[] = [
element: <Search />,
},
{
path: "/helpdesk",
element: <HelpDesk />,
},
{
path: "/terms/email",
element: <EmailTerms />,
element: <HelpDeskProvider />,
children: [
{
path: "/helpdesk",
element: <HelpDesk />,
},
{
path: "/terms/email",
element: <EmailTerms />,
},
],
},
{
path: "/notices",
Expand Down

0 comments on commit b404e2a

Please sign in to comment.