diff --git a/src/components/introduce/ApplyStatusButton.jsx b/src/components/introduce/ApplyStatusButton.jsx new file mode 100644 index 0000000..e9741ef --- /dev/null +++ b/src/components/introduce/ApplyStatusButton.jsx @@ -0,0 +1,54 @@ +import React from 'react'; + +import styled from '@emotion/styled'; + +const ApplyStatusButtonWrapper = styled.button``; + +const ApplyStatusButton = ({ + timeStatus, onApply, user, applyStatus, +}) => { + if (applyStatus) { + return ( + + 신청 완료 + + ); + } + + if (timeStatus) { + return ( + + 모집 마감 + + ); + } + + if (!user) { + return ( + + 로그인 후 신청 가능합니다. + + ); + } + + return ( + + 신청하기 + + ); +}; + +export default ApplyStatusButton; diff --git a/src/components/introduce/ApplyStatusButton.test.jsx b/src/components/introduce/ApplyStatusButton.test.jsx new file mode 100644 index 0000000..b338ff6 --- /dev/null +++ b/src/components/introduce/ApplyStatusButton.test.jsx @@ -0,0 +1,54 @@ +import React from 'react'; + +import { render } from '@testing-library/react'; + +import ApplyStatusButton from './ApplyStatusButton'; + +describe('ApplyStatusButton', () => { + const handleApply = jest.fn(); + + const renderApplyStatusButton = ({ + applyStatus = false, + timeStatus = false, + user = true, + }) => render(( + + )); + + context('When the study application is completed', () => { + it('renders application completed', () => { + const { container } = renderApplyStatusButton({ applyStatus: true }); + + expect(container).toHaveTextContent('신청 완료'); + }); + }); + + context('When the study application deadline', () => { + it('renders application deadline', () => { + const { container } = renderApplyStatusButton({ timeStatus: true }); + + expect(container).toHaveTextContent('모집 마감'); + }); + }); + + context('When not log in', () => { + it('renders "You can apply after logging in." text', () => { + const { container } = renderApplyStatusButton({ user: false }); + + expect(container).toHaveTextContent('로그인 후 신청 가능합니다.'); + }); + }); + + context('When it is possible to apply', () => { + it('renders "apply" text', () => { + const { container } = renderApplyStatusButton({}); + + expect(container).toHaveTextContent('신청하기'); + }); + }); +}); diff --git a/src/components/introduce/StudyIntroduceForm.jsx b/src/components/introduce/StudyIntroduceForm.jsx index 3c02014..0d124e7 100644 --- a/src/components/introduce/StudyIntroduceForm.jsx +++ b/src/components/introduce/StudyIntroduceForm.jsx @@ -9,6 +9,7 @@ import { isCheckedTimeStatus } from '../../util/utils'; import Tags from '../common/Tags'; import palette from '../../styles/palette'; import DateTimeChange from '../common/DateTimeChange'; +import ApplyStatusButton from './ApplyStatusButton'; const StudyIntroduceWrapper = styled.div` margin-top: 6em; @@ -99,7 +100,9 @@ const IntroduceContent = styled.div` padding: 1.5rem; `; -const StudyIntroduceForm = ({ group, realTime }) => { +const StudyIntroduceForm = ({ + group, realTime, onApply, user, +}) => { const { title, contents, tags, moderatorId, personnel, participants, applyEndDate, } = group; @@ -110,20 +113,13 @@ const StudyIntroduceForm = ({ group, realTime }) => {

{title}

- {isCheckedTimeStatus({ ...group, time: realTime, applyEndTime }) ? ( - - ) : ( - + {moderatorId !== user && ( + )}
diff --git a/src/components/introduce/StudyIntroduceForm.test.jsx b/src/components/introduce/StudyIntroduceForm.test.jsx index 72b8647..b30e40d 100644 --- a/src/components/introduce/StudyIntroduceForm.test.jsx +++ b/src/components/introduce/StudyIntroduceForm.test.jsx @@ -9,9 +9,10 @@ import StudyIntroduceForm from './StudyIntroduceForm'; import STUDY_GROUP from '../../../fixtures/study-group'; describe('StudyIntroduceForm', () => { - const renderStudyIntroduceForm = ({ group, time }) => render(( + const renderStudyIntroduceForm = ({ group, time, user = 'user' }) => render(( @@ -22,7 +23,6 @@ describe('StudyIntroduceForm', () => { const { container } = renderStudyIntroduceForm({ group: STUDY_GROUP }); expect(container).toHaveTextContent('스터디를 소개합니다.2'); - expect(container).toHaveTextContent('우리는 이것저것 합니다.2'); }); it('renders links of tags', () => { @@ -31,6 +31,14 @@ describe('StudyIntroduceForm', () => { expect(container.innerHTML).toContain(' { + it("doesn't renders apply button", () => { + const { container } = renderStudyIntroduceForm({ group: STUDY_GROUP, user: 'user2' }); + + expect(container).not.toHaveTextContent('신청하기'); + }); + }); + context('When the study recruitment is closed', () => { const time = Date.now(); diff --git a/src/containers/introduce/IntroduceContainer.jsx b/src/containers/introduce/IntroduceContainer.jsx index f5dbefd..e172017 100644 --- a/src/containers/introduce/IntroduceContainer.jsx +++ b/src/containers/introduce/IntroduceContainer.jsx @@ -1,10 +1,10 @@ -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { useInterval } from 'react-use'; import { useDispatch, useSelector } from 'react-redux'; import { get } from '../../util/utils'; -import { loadStudyGroup } from '../../reducers/slice'; +import { loadStudyGroup, updateStudyGroup } from '../../reducers/slice'; import StudyIntroduceForm from '../../components/introduce/StudyIntroduceForm'; @@ -13,16 +13,23 @@ const IntroduceContainer = ({ groupId }) => { const dispatch = useDispatch(); + const group = useSelector(get('group')); + const user = useSelector(get('user')); + useEffect(() => { dispatch(loadStudyGroup(groupId)); - }, []); - - const group = useSelector(get('group')); + }, [dispatch, groupId]); useInterval(() => { setRealTime(Date.now()); }, 1000); + const onApplyStudy = useCallback(() => { + if (user) { + dispatch(updateStudyGroup()); + } + }, [dispatch, user]); + if (!group) { return (
로딩중..
@@ -31,8 +38,10 @@ const IntroduceContainer = ({ groupId }) => { return ( ); }; diff --git a/src/containers/introduce/IntroduceContainer.test.jsx b/src/containers/introduce/IntroduceContainer.test.jsx index 17c5d4b..cf29ff5 100644 --- a/src/containers/introduce/IntroduceContainer.test.jsx +++ b/src/containers/introduce/IntroduceContainer.test.jsx @@ -2,10 +2,12 @@ import React from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { render } from '@testing-library/react'; +import { fireEvent, render } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; +import STUDY_GROUP from '../../../fixtures/study-group'; + import IntroduceContainer from './IntroduceContainer'; describe('IntroduceContainer', () => { @@ -18,6 +20,7 @@ describe('IntroduceContainer', () => { useSelector.mockImplementation((state) => state({ group: given.group, + user: given.user, })); }); @@ -65,4 +68,38 @@ describe('IntroduceContainer', () => { expect(container).toHaveTextContent('로딩중..'); }); }); + + context('with user', () => { + given('group', () => (STUDY_GROUP)); + given('user', () => ('user')); + + it('click event dispatches action call updateStudyGroup', () => { + const { getByText } = renderIntroduceContainer(1); + + const button = getByText('신청하기'); + + expect(button).not.toBeNull(); + + fireEvent.click(button); + + expect(dispatch).toBeCalledTimes(2); + }); + }); + + context('without user', () => { + given('group', () => (STUDY_GROUP)); + given('user', () => (null)); + + it("click event doesn't dispatches action call updateStudyGroup", () => { + const { getByText } = renderIntroduceContainer(1); + + const button = getByText('로그인 후 신청 가능합니다.'); + + expect(button).not.toBeNull(); + + fireEvent.click(button); + + expect(dispatch).toBeCalledTimes(1); + }); + }); }); diff --git a/src/reducers/slice.js b/src/reducers/slice.js index b3ca5f9..4eada5f 100644 --- a/src/reducers/slice.js +++ b/src/reducers/slice.js @@ -9,6 +9,7 @@ import { postUserLogin, postUserLogout, postUserRegister, + updateParticipants, } from '../services/api'; import { removeItem, saveItem } from '../services/storage'; @@ -176,7 +177,10 @@ export const loadStudyGroup = (id) => async (dispatch) => { const group = await getStudyGroup(id); - dispatch(setStudyGroup(group)); + dispatch(setStudyGroup({ + ...group, + id, + })); }; export const writeStudyGroup = () => async (dispatch, getState) => { @@ -193,6 +197,19 @@ export const writeStudyGroup = () => async (dispatch, getState) => { dispatch(clearWriteFields()); }; +export const updateStudyGroup = () => async (dispatch, getState) => { + const { group, user } = getState(); + + const newGroup = produce(group, (draft) => { + draft.participants.push(user); + }); + + // TODO: 같은 유저가 들어가도 update 된다. validation 하기 + await updateParticipants(newGroup); + + dispatch(setStudyGroup(newGroup)); +}; + export const requestRegister = () => async (dispatch, getState) => { const { register: { userEmail, password } } = getState(); diff --git a/src/reducers/slice.test.js b/src/reducers/slice.test.js index 89c6750..5bc8f7e 100644 --- a/src/reducers/slice.test.js +++ b/src/reducers/slice.test.js @@ -22,6 +22,7 @@ import reducer, { setUser, logout, requestLogout, + updateStudyGroup, } from './slice'; import STUDY_GROUPS from '../../fixtures/study-groups'; @@ -332,7 +333,7 @@ describe('async actions', () => { const actions = store.getActions(); expect(actions[0]).toEqual(setStudyGroup(null)); - expect(actions[1]).toEqual(setStudyGroup(undefined)); + expect(actions[1]).toEqual(setStudyGroup({ id: 1 })); }); }); @@ -343,7 +344,7 @@ describe('async actions', () => { }); }); - it('dispatches clearWriteFields', async () => { + it('dispatches clearWriteFields and successWrite', async () => { await store.dispatch(writeStudyGroup()); const actions = store.getActions(); @@ -353,6 +354,26 @@ describe('async actions', () => { }); }); + describe('updateStudyGroup', () => { + beforeEach(() => { + store = mockStore({ + group: STUDY_GROUP, + user: 'example', + }); + }); + + it('dispatches setStudyGroup', async () => { + await store.dispatch(updateStudyGroup()); + + const actions = store.getActions(); + + expect(actions[0]).toEqual(setStudyGroup({ + ...STUDY_GROUP, + participants: [...STUDY_GROUP.participants, 'example'], + })); + }); + }); + describe('requestRegister', () => { const register = { userEmail: 'seungmin@naver.com', diff --git a/src/services/__mocks__/api.js b/src/services/__mocks__/api.js index e981e42..f1f1aa7 100644 --- a/src/services/__mocks__/api.js +++ b/src/services/__mocks__/api.js @@ -9,3 +9,5 @@ export const postUserRegister = jest.fn(); export const postUserLogin = jest.fn(); export const postUserLogout = jest.fn(); + +export const updateParticipants = jest.fn(); diff --git a/src/services/api.js b/src/services/api.js index 29ba0b4..499bb83 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -27,6 +27,14 @@ export const postStudyGroup = async (post) => { return id; }; +export const updateParticipants = async (group) => { + const { id, participants } = group; + + const groups = db.collection('groups').doc(id); + + await groups.update({ participants }); +}; + export const postUserRegister = async ({ userEmail, password }) => { const response = await auth .createUserWithEmailAndPassword(userEmail, password);