From 0b7ff9a28bafba6e23ef991058b42aa3c11e61ec Mon Sep 17 00:00:00 2001 From: saseungmin Date: Fri, 11 Dec 2020 01:51:47 +0900 Subject: [PATCH] [Feature] Modal window showing the list of study applicants and CSS --- .../introduce/ModeratorViewButton.jsx | 21 +- .../introduce/ModeratorViewButton.test.jsx | 34 +++- .../introduce/modals/ParticipantListModal.jsx | 190 ++++++++++++++++++ .../modals/ParticipantListModal.test.jsx | 57 ++++++ 4 files changed, 298 insertions(+), 4 deletions(-) create mode 100644 src/components/introduce/modals/ParticipantListModal.jsx create mode 100644 src/components/introduce/modals/ParticipantListModal.test.jsx diff --git a/src/components/introduce/ModeratorViewButton.jsx b/src/components/introduce/ModeratorViewButton.jsx index e1dbe30..0b28b10 100644 --- a/src/components/introduce/ModeratorViewButton.jsx +++ b/src/components/introduce/ModeratorViewButton.jsx @@ -1,9 +1,20 @@ -import React from 'react'; +import React, { useState } from 'react'; import StyledApplyStatusButton from '../../styles/StyledApplyStatusButton'; +import ParticipantListModal from './modals/ParticipantListModal'; const ModeratorViewButton = ({ group, user }) => { - const { moderatorId } = group; + const [ListModal, setListModal] = useState(false); + + const { moderatorId, participants } = group; + + const handleClick = () => { + setListModal(true); + }; + + const handelClose = () => { + setListModal(false); + }; if (moderatorId !== user) { return null; @@ -14,9 +25,15 @@ const ModeratorViewButton = ({ group, user }) => { 스터디 참여 승인하기 + index !== 0)} + /> ); }; diff --git a/src/components/introduce/ModeratorViewButton.test.jsx b/src/components/introduce/ModeratorViewButton.test.jsx index 76a4f26..38e2d61 100644 --- a/src/components/introduce/ModeratorViewButton.test.jsx +++ b/src/components/introduce/ModeratorViewButton.test.jsx @@ -1,6 +1,6 @@ import React from 'react'; -import { render } from '@testing-library/react'; +import { fireEvent, render } from '@testing-library/react'; import ModeratorViewButton from './ModeratorViewButton'; @@ -31,12 +31,42 @@ describe('ModeratorViewButton', () => { const group = { ...STUDY_GROUP, moderatorId: 'user', + participants: [ + { + confirm: true, + id: 'test1', + }, + { + confirm: false, + id: 'test2', + }, + ], }; - it('nothing renders', () => { + it('renders button', () => { const { container } = renderModeratorViewButton({ group, user: 'user' }); expect(container).toHaveTextContent('스터디 참여 승인하기'); }); + + it('click button and renders List of study applicants', () => { + const { getByText, container } = renderModeratorViewButton({ group, user: 'user' }); + const button = getByText('스터디 참여 승인하기'); + + fireEvent.click(button); + + expect(container).toHaveTextContent('스터디 신청자 목록'); + }); + + it('Clicking the close button closes the modal window.', () => { + const { getByText, container } = renderModeratorViewButton({ group, user: 'user' }); + const button = getByText('스터디 참여 승인하기'); + + fireEvent.click(button); + + fireEvent.click(getByText('닫기')); + + expect(container).not.toHaveTextContent('스터디 신청자 목록'); + }); }); }); diff --git a/src/components/introduce/modals/ParticipantListModal.jsx b/src/components/introduce/modals/ParticipantListModal.jsx new file mode 100644 index 0000000..84de0f7 --- /dev/null +++ b/src/components/introduce/modals/ParticipantListModal.jsx @@ -0,0 +1,190 @@ +import React from 'react'; + +import styled from '@emotion/styled'; + +import { css } from '@emotion/react'; + +import Button from '../../../styles/Button'; +import palette from '../../../styles/palette'; + +const ParticipantListModalWrapper = styled.div` + position: fixed; + z-index: 101; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.25); + display: flex; + justify-content: center; + align-items: center; + + ${(props) => props.visible && css` + &.animation { + animation-name: fade-in; + animation-fill-mode: both; + animation-duration: 0.3s; + } + + @keyframes fade-in { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } + } + `}; +`; + +const ModalBoxWrapper = styled.div` + display: flex; + flex-direction: column; + height: 550px; + width: 600px; + background: white; + padding: 1.5rem; + border-radius: 6px; + box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.125); + h2 { + margin-top: 0; + margin-bottom: 1rem; + text-align: center; + } + .buttons { + display: flex; + justify-content: flex-end; + } +`; + +const ParticipantTitleWrapper = styled.div` + display: grid; + grid-template-columns: 260px 190px 145px; + justify-items: center; + align-items: center; + margin: 0.5rem 0 0.3rem 0; + + div { + font-size: 1.1rem; + font-weight: bold; + margin-bottom: 0.3rem; + } +`; + +const ParticipantListWrapper = styled.div` + height: 100%; + width: 99%; + border: 2px solid ${palette.gray[3]}; + border-radius: 4px; + margin-bottom: 1rem; + padding-top: 0.5rem; + overflow-y: auto; +`; + +const ParticipantListBlock = styled.div` + display: grid; + grid-template-columns: 260px 186px 148px; + justify-items: center; + align-items: center; + margin-bottom: 0.5rem; + min-height: 0; + min-width: 0; +`; + +const StyledButton = styled(Button)` + &:last-of-type { + margin-left: .7rem; + } +`; + +const ConfirmButton = styled.button` + transition-duration: 0.08s; + transition-property: all; + transition-timing-function: ease-in-out; + transition-delay: initial; + font-size: 0.8rem; + padding: 0.15rem 0.7rem; + font-weight: bold; + outline: none; + border: none; + cursor: pointer; + background: none; + font-family: 'Noto Sans KR', sans-serif; + border-radius: 4px; + color: white; + + ${({ sky }) => sky && css` + background: #74c0fc; + border-bottom: 2px solid #4dabf7; + box-shadow: 0px 2px 4px #4dabf7; + &:hover{ + box-shadow: none; + border-bottom: 2px solid #74c0fc; + } + `}; + + ${({ confirm }) => confirm && css` + background: ${palette.teal[5]}; + border-bottom: 2px solid ${palette.teal[6]}; + box-shadow: 0px 2px 4px ${palette.teal[6]}; + &:hover{ + box-shadow: none; + border-bottom: 2px solid ${palette.teal[5]}; + } + `}; + + ${({ cancel }) => cancel && css` + background: ${palette.warn[1]}; + border-bottom: 2px solid ${palette.warn[2]}; + box-shadow: 0px 2px 4px ${palette.warn[2]}; + &:hover{ + box-shadow: none; + border-bottom: 2px solid ${palette.warn[1]}; + } + `}; +`; + +const ParticipantListModal = ({ visible, onClose, participants }) => { + if (!visible) { + return null; + } + + return ( + + +

스터디 신청자 목록 🙋‍♂️

+ +
신청자 이메일
+
신청서 보기
+
승인 여부
+
+ + {participants.length && participants.map(({ id, confirm }) => ( + +
{id}
+
+ + 신청서 보기 + +
+
+ {confirm === true ? ( + 취소하기 + ) : ( + 승인하기 + )} +
+
+ ))} + <> + +
+
+ 닫기 +
+
+
+ ); +}; + +export default ParticipantListModal; diff --git a/src/components/introduce/modals/ParticipantListModal.test.jsx b/src/components/introduce/modals/ParticipantListModal.test.jsx new file mode 100644 index 0000000..f0c1e1b --- /dev/null +++ b/src/components/introduce/modals/ParticipantListModal.test.jsx @@ -0,0 +1,57 @@ +import React from 'react'; + +import { fireEvent, render } from '@testing-library/react'; + +import ParticipantListModal from './ParticipantListModal'; + +describe('ParticipantListModal', () => { + const handleClose = jest.fn(); + + const renderParticipantListModal = ({ visible, participants }) => render(( + + )); + + context('with visible', () => { + const modal = { + visible: true, + participants: [{ + confirm: false, + id: 'test', + }], + }; + + it('renders Modal text', () => { + const { container } = renderParticipantListModal(modal); + + expect(container).toHaveTextContent('스터디 신청자 목록 🙋‍♂️'); + expect(container).toHaveTextContent('신청서 보기'); + expect(container).toHaveTextContent('test'); + }); + + it('click button call close', () => { + const { getByText } = renderParticipantListModal(modal); + + const button = getByText('닫기'); + + fireEvent.click(button); + + expect(handleClose).toBeCalled(); + }); + }); + + context('without visible', () => { + const modal = { + visible: false, + }; + + it("doesn't renders Modal text", () => { + const { container } = renderParticipantListModal(modal); + + expect(container).toBeEmptyDOMElement(); + }); + }); +});