Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

문의하기 모달 생성 #777

Merged
merged 10 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions frontend/src/components/ContactUs/ContactUs.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import styled from '@emotion/styled';

export const ContactUSButton = styled.button`
cursor: pointer;
`;

export const Form = styled.form`
display: flex;
flex-direction: column;
gap: 1.25rem;
`;
111 changes: 111 additions & 0 deletions frontend/src/components/ContactUs/ContactUs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { useInput, useInputWithValidate, useToggle } from '@/hooks';
import { useToast } from '@/hooks/useToast';
import { validateEmail } from '@/service/validates';
import { theme } from '@/style/theme';

import { Button, Input, Modal, Text, Textarea } from '..';
import * as S from './ContactUs.style';

const ContactUs = () => {
const [isModalOpen, toggleModal] = useToggle();
const [message, handleMessage, resetMessage] = useInput('');
const {
value: email,
handleChange: handleEmail,
resetValue: resetEmail,
errorMessage: emailErrorMessage,
} = useInputWithValidate('', validateEmail);

const { failAlert, successAlert } = useToast();

const isValidContents = message.trim().length;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사소하지만 isValidContents 변수명을 length가 들어가도록 바꾸거나, 아예 조건문을 넣어도 괜찮지 않을까 했습니다!
is-로 시작하니 boolean이면 좋지않을까 하는 생각이었어요.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아주 좋은 의견입니다.👍👍 isValidContentsboolean으로 사용하도록 조건문으로 변경하겠습니다~!


const handleSubmit = (e: React.FormEvent<HTMLFormElement> | React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();

if (!isValidContents || emailErrorMessage) {
return;
}

submitForm();
};

const submitForm = async () => {
const res = await sendData();

if (!res.ok) {
failAlert('보내기에 실패했습니다. 계속 실패한다면 이메일로 제보 부탁드립니다.');

return;
}

successSubmit();
successAlert('보내기 완료! 소중한 의견 감사합니다:)');
};

const sendData = () => {
const URL = process.env.GOOGLE_URL || '';

return fetch(URL, {
method: 'POST',
mode: 'no-cors',
body: JSON.stringify({ message, email }),
headers: {
'Content-Type': 'application/json',
},
});
};
Comment on lines +46 to +57
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다음 부분에서 env 변수가 없을 때, 빈 문자열을 넣는게 아니라, 그냥 google URL을 넣어줘도 되지 않을까 했는데 해당 URL은 key인 건가요?!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네네 특정 web app(구글 시트에 값을 넣어줄 수 있도록 구글에서 제공해주는 웹앱)의 url입니다! URL이 무조건 string 타입이어야해서 env 변수가 없을 경우 빈 문자열로 처리하도록 하였습니다.


const successSubmit = () => {
resetForm();
toggleModal();
};

const resetForm = () => {
resetEmail();
resetMessage();
};

return (
<>
<S.ContactUSButton onClick={toggleModal}>
<Text.Medium weight='bold' color={theme.color.light.secondary_800}>
문의하기
</Text.Medium>
</S.ContactUSButton>
<Modal isOpen={isModalOpen} toggleModal={toggleModal} size='large'>
<Modal.Header>문의하기</Modal.Header>
<Modal.Body>
<S.Form onSubmit={handleSubmit}>
<Text.Medium as='p' color={theme.color.light.secondary_500}>
질문/피드백을 편하게 남겨주세요! 여러분의 의견은 더 나은 서비스를 만드는 데 큰 도움이 됩니다. <br />
이미지 등을 함께 보내실 경우 [email protected]으로 직접 이메일을 보내실 수 있습니다.
</Text.Medium>
<Textarea id='voc' variant='outlined'>
<Textarea.Label htmlFor={'voc'}>무엇을 도와드릴까요?</Textarea.Label>
<Textarea.TextField minRows={5} maxRows={10} value={message} onChange={handleMessage} />
</Textarea>
<Text.Medium as='p' color={theme.color.light.secondary_500}>
답변이 필요하시면 아래 이메일 주소를 남겨주세요. 이메일은 오직 답변을 위해서만 사용됩니다 :)
</Text.Medium>
<Input variant='outlined' isValid={!emailErrorMessage}>
<Input.Label>이메일 (선택)</Input.Label>
<Input.TextField value={email} onChange={handleEmail} />
<Input.HelperText>{emailErrorMessage}</Input.HelperText>
</Input>
</S.Form>
</Modal.Body>
<Modal.Footer>
<Button onClick={toggleModal} variant='outlined'>
닫기
</Button>
<Button disabled={isValidContents && !emailErrorMessage ? false : true} onClick={handleSubmit}>
보내기
</Button>
</Modal.Footer>
</Modal>
</>
);
};

export default ContactUs;
9 changes: 3 additions & 6 deletions frontend/src/components/Footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Text } from '@/components';
import { ContactUs, Text } from '@/components';
import { useAuth } from '@/hooks/authentication';

import * as S from './Footer.style';
Expand All @@ -19,11 +19,8 @@ const Footer = () => {
</Text.Small>{' '}
© All rights reserved.
</Text.Small>
<S.ContactEmail href='mailto:[email protected]'>
<Text.Small color='inherit' weight='bold'>
문의 :
</Text.Small>{' '}
<Text.Small color='inherit'>[email protected]</Text.Small>{' '}
<S.ContactEmail>
<ContactUs />
Comment on lines -22 to +23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

</S.ContactEmail>
</S.FooterContainer>
);
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/Header/Header.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const HeaderContainer = styled.nav`
padding: 0 2rem;

background: white;
border-bottom: 2px solid ${theme.color.light.secondary_200};
border-bottom: 1px solid ${theme.color.light.secondary_300};

@media (max-width: 768px) {
padding: 0 1rem;
Expand Down
29 changes: 19 additions & 10 deletions frontend/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect } from 'react';
import { Link, useLocation } from 'react-router-dom';

import { CodeZapLogo, HamburgerIcon, PlusIcon } from '@/assets/images';
import { Button, Flex, Heading, Text } from '@/components';
import { Button, ContactUs, Flex, Heading, Text } from '@/components';
import { ToastContext } from '@/contexts';
import { useCustomContext, useCustomNavigate, useToggle } from '@/hooks';
import { useAuth } from '@/hooks/authentication/useAuth';
Expand Down Expand Up @@ -56,6 +56,7 @@ const Header = ({ headerRef }: { headerRef: React.RefObject<HTMLDivElement> }) =
<S.NavContainer>
{!isChecking && isLogin && <NavOption route={END_POINTS.MY_TEMPLATES} name='내 템플릿' />}
<NavOption route={END_POINTS.TEMPLATES_EXPLORE} name='구경가기' />
<ContactUs />
</S.NavContainer>
<S.NavContainer>
<S.MobileHiddenButton
Expand Down Expand Up @@ -100,15 +101,23 @@ const Logo = () => (
</Link>
);

const NavOption = ({ route, name }: { route: string; name: string }) => (
<Link to={route}>
<S.NavOptionButton>
<Text.Medium weight='bold' color={theme.color.light.secondary_800}>
{name}
</Text.Medium>
</S.NavOptionButton>
</Link>
);
const NavOption = ({ route, name }: { route: string; name: string }) => {
const location = useLocation();
const isCurrentPage = location.pathname === route;

return (
<Link to={route}>
<S.NavOptionButton>
<Text.Medium
weight='bold'
color={isCurrentPage ? theme.color.light.primary_500 : theme.color.light.secondary_800}
>
Comment on lines +118 to +121
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

색으로만 구분하니 직관적이지 않을 것 같아서, 색 말고 밑줄 혹은 색과 함께 밑줄까지 하면 어떨까요?
디자인적인 의견이라 이야기해보면 좋을 것 같아요.
좋은 VoC 반영입니다!

{name}
</Text.Medium>
</S.NavOptionButton>
</Link>
);
};

const LogoutButton = () => {
const { mutateAsync } = useLogoutMutation();
Expand Down
24 changes: 3 additions & 21 deletions frontend/src/components/Input/Input.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import {
Children,
HTMLAttributes,
InputHTMLAttributes,
isValidElement,
LabelHTMLAttributes,
PropsWithChildren,
ReactNode,
} from 'react';
import { HTMLAttributes, InputHTMLAttributes, LabelHTMLAttributes, PropsWithChildren } from 'react';

import { getChildOfType, getChildrenWithoutTypes } from '@/utils';

import * as S from './Input.style';

Expand All @@ -29,18 +23,6 @@ export interface AdornmentProps extends HTMLAttributes<HTMLDivElement> {

export interface HelperTextProps extends HTMLAttributes<HTMLSpanElement> {}

const getChildOfType = (children: ReactNode, type: unknown) => {
const childrenArray = Children.toArray(children);

return childrenArray.find((child) => isValidElement(child) && child.type === type);
};

const getChildrenWithoutTypes = (children: ReactNode, types: unknown[]) => {
const childrenArray = Children.toArray(children);

return childrenArray.filter((child) => !(isValidElement(child) && types.includes(child.type)));
};

const TextField = ({ ...rests }: TextFieldProps) => <S.TextField {...rests} />;

const Label = ({ children, ...rests }: PropsWithChildren<LabelProps>) => <S.Label {...rests}>{children}</S.Label>;
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { HTMLAttributes, PropsWithChildren, ReactNode } from 'react';
import { createPortal } from 'react-dom';

import { usePressESC } from '@/hooks/usePressESC';
import { useScrollDisable } from '@/hooks/useScrollDisable';

import { theme } from '../../style/theme';
import Heading from '../Heading/Heading';
import * as S from './Modal.style';
Expand All @@ -14,6 +17,9 @@ export interface BaseProps extends HTMLAttributes<HTMLDivElement> {
}

const Base = ({ isOpen, toggleModal, size = 'small', children, ...props }: PropsWithChildren<BaseProps>) => {
usePressESC(isOpen, toggleModal);
useScrollDisable(isOpen);

if (!isOpen) {
return null;
}
Expand Down
Loading