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);