diff --git a/src/components/common/DateTimeChange.jsx b/src/components/common/DateTimeChange.jsx
index 0e77ca8..a4da9c2 100644
--- a/src/components/common/DateTimeChange.jsx
+++ b/src/components/common/DateTimeChange.jsx
@@ -44,7 +44,7 @@ const DateTimeChange = ({ group, page, time }) => {
if (isCheckedTimeStatus(valid)) {
return (
- 모집마감
+ 모집 마감
);
}
diff --git a/src/components/common/DateTimeChange.test.jsx b/src/components/common/DateTimeChange.test.jsx
index 37c6a93..868e160 100644
--- a/src/components/common/DateTimeChange.test.jsx
+++ b/src/components/common/DateTimeChange.test.jsx
@@ -133,7 +133,7 @@ describe('DateTimeChange', () => {
const { container } = renderDateTimeChange({ group, page, time });
- expect(container).toHaveTextContent('모집마감');
+ expect(container).toHaveTextContent('모집 마감');
});
});
@@ -153,7 +153,7 @@ describe('DateTimeChange', () => {
const { container } = renderDateTimeChange({ group, page, time });
- expect(container).toHaveTextContent('모집마감');
+ expect(container).toHaveTextContent('모집 마감');
});
});
});
diff --git a/src/components/introduce/ApplyStatusButton.jsx b/src/components/introduce/ApplyStatusButton.jsx
index a88dceb..0f7c301 100644
--- a/src/components/introduce/ApplyStatusButton.jsx
+++ b/src/components/introduce/ApplyStatusButton.jsx
@@ -22,6 +22,15 @@ const ApplyStatusButtonWrapper = styled.button`
color: ${palette.gray[5]};
}
+ &.apply-cancel {
+ cursor: pointer;
+ background: ${palette.orange[4]};
+ color: white;
+ &:hover {
+ background: ${palette.orange[3]};
+ }
+ }
+
&.apply-complete {
background: ${palette.gray[1]};
border: 2px solid #a5d8ff;
@@ -32,20 +41,27 @@ const ApplyStatusButtonWrapper = styled.button`
color: white;
cursor: pointer;
background: ${palette.teal[5]};
- &:hover{
+ &:hover {
background: ${palette.teal[4]};
}
}
-
- &.no-login{
- cursor: not-allowed;
- color: ${palette.gray[5]};
- }
`;
const ApplyStatusButton = ({
- timeStatus, onApply, applyStatus,
+ timeStatus, onApply, applyStatus, onCancel,
}) => {
+ if (!timeStatus && applyStatus) {
+ return (
+
+ 신청 취소
+
+ );
+ }
+
if (applyStatus) {
return (
{
/>
));
+ context('When the applicant applies before the application deadline', () => {
+ it('renders Cancel application', () => {
+ const { container } = renderApplyStatusButton({ applyStatus: true });
+
+ expect(container).toHaveTextContent('신청 취소');
+ });
+ });
+
context('When the study application is completed', () => {
it('renders application completed', () => {
- const { container } = renderApplyStatusButton({ applyStatus: true });
+ const { container } = renderApplyStatusButton({ applyStatus: true, timeStatus: true });
expect(container).toHaveTextContent('신청 완료');
});
diff --git a/src/components/introduce/IntroduceHeader.jsx b/src/components/introduce/IntroduceHeader.jsx
index 593cf9c..940303a 100644
--- a/src/components/introduce/IntroduceHeader.jsx
+++ b/src/components/introduce/IntroduceHeader.jsx
@@ -22,7 +22,7 @@ const IntroduceHeaderWrapper = styled.div`
`;
const IntroduceHeader = ({
- group, onApply, user, realTime,
+ group, onApply, user, realTime, onApplyCancel,
}) => {
const [modal, setModal] = useState(false);
@@ -56,6 +56,7 @@ const IntroduceHeader = ({
<>
{
const handleApply = jest.fn();
+ const handleApplyCancel = jest.fn();
beforeEach(() => {
jest.clearAllMocks();
@@ -19,6 +20,7 @@ describe('IntroduceHeader', () => {
group={group}
realTime={time}
onApply={handleApply}
+ onApplyCancel={handleApplyCancel}
/>
));
@@ -28,6 +30,36 @@ describe('IntroduceHeader', () => {
expect(container).toHaveTextContent('스터디를 소개합니다.2');
});
+ context(`When the application date is earlier than the
+ deadline date and the application deadline is not reached`, () => {
+ const time = Date.now();
+
+ const nowDate = new Date();
+ const tomorrow = nowDate.setDate(nowDate.getDate() + 1);
+
+ const group = {
+ ...STUDY_GROUP,
+ applyEndDate: tomorrow,
+ participants: [
+ 'user2',
+ 'user',
+ ],
+ personnel: 3,
+ };
+
+ it('Call the cancel application button.', () => {
+ const { getByText } = renderIntroduceHeader({ group, user: 'user', time });
+
+ const button = getByText('신청 취소');
+
+ expect(button).not.toBeNull();
+
+ fireEvent.click(button);
+
+ expect(handleApplyCancel).toBeCalled();
+ });
+ });
+
context('When the author and the logged-in user have the same ID', () => {
it("doesn't renders apply button", () => {
const { container } = renderIntroduceHeader({ group: STUDY_GROUP, user: 'user2' });
diff --git a/src/containers/introduce/IntroduceContainer.jsx b/src/containers/introduce/IntroduceContainer.jsx
index 62ef687..b0222e1 100644
--- a/src/containers/introduce/IntroduceContainer.jsx
+++ b/src/containers/introduce/IntroduceContainer.jsx
@@ -4,7 +4,7 @@ import { useInterval } from 'react-use';
import { useDispatch, useSelector } from 'react-redux';
import { getAuth, getGroup } from '../../util/utils';
-import { loadStudyGroup, updateStudyGroup } from '../../reducers/groupSlice';
+import { deleteParticipant, loadStudyGroup, updateParticipant } from '../../reducers/groupSlice';
import StudyIntroduceForm from '../../components/introduce/StudyIntroduceForm';
import GroupContentLoader from '../../components/introduce/GroupsContentLoader';
@@ -27,9 +27,15 @@ const IntroduceContainer = ({ groupId }) => {
}, 1000);
const onApplyStudy = useCallback(() => {
- dispatch(updateStudyGroup());
+ dispatch(updateParticipant());
}, [dispatch]);
+ const onApplyCancel = useCallback(() => {
+ if (user) {
+ dispatch(deleteParticipant());
+ }
+ }, [dispatch, user]);
+
if (!group) {
return (
@@ -43,6 +49,7 @@ const IntroduceContainer = ({ groupId }) => {
group={group}
realTime={realTime}
onApply={onApplyStudy}
+ onApplyCancel={onApplyCancel}
/>
{
});
});
- context('without group', () => {
+ context('without group ', () => {
given('group', () => (null));
it('renders "loading.." text', () => {
@@ -73,13 +73,15 @@ describe('IntroduceContainer', () => {
});
});
- describe('with user', () => {
+ context('with group & user', () => {
given('group', () => (STUDY_GROUP));
given('user', () => ('user'));
- it('click event dispatches action call updateStudyGroup', () => {
+ it('click event dispatches action call updateParticipant', () => {
const { getByText } = renderIntroduceContainer(1);
+ expect(dispatch).toBeCalledTimes(1);
+
const button = getByText('신청하기');
expect(button).not.toBeNull();
@@ -89,4 +91,37 @@ describe('IntroduceContainer', () => {
expect(dispatch).toBeCalledTimes(2);
});
});
+
+ describe(`When the application date is earlier than the deadline
+ date and the application deadline is not reached`, () => {
+ const nowDate = new Date();
+ const tomorrow = nowDate.setDate(nowDate.getDate() + 1);
+
+ const group = {
+ ...STUDY_GROUP,
+ applyEndDate: tomorrow,
+ participants: [
+ 'user2',
+ 'user',
+ ],
+ personnel: 3,
+ };
+
+ given('group', () => (group));
+ given('user', () => ('user'));
+
+ it('click event dispatches action call deleteParticipant', () => {
+ const { getByText } = renderIntroduceContainer(1);
+
+ expect(dispatch).toBeCalledTimes(1);
+
+ const button = getByText('신청 취소');
+
+ expect(button).not.toBeNull();
+
+ fireEvent.click(button);
+
+ expect(dispatch).toBeCalledTimes(2);
+ });
+ });
});
diff --git a/src/reducers/groupSlice.js b/src/reducers/groupSlice.js
index e1ea2b7..7f8b84f 100644
--- a/src/reducers/groupSlice.js
+++ b/src/reducers/groupSlice.js
@@ -6,7 +6,8 @@ import {
getStudyGroup,
getStudyGroups,
postStudyGroup,
- updateParticipants,
+ updatePostParticipant,
+ deletePostParticipant,
} from '../services/api';
const writeInitialState = {
@@ -112,14 +113,33 @@ export const writeStudyGroup = () => async (dispatch, getState) => {
dispatch(clearWriteFields());
};
-export const updateStudyGroup = () => async (dispatch, getState) => {
- const { groupReducer, authReducer } = getState();
+export const updateParticipant = () => async (dispatch, getState) => {
+ const { groupReducer: { group }, authReducer: { user } } = getState();
- const newGroup = produce(groupReducer.group, (draft) => {
- draft.participants.push(authReducer.user);
+ const newGroup = produce(group, (draft) => {
+ draft.participants.push(user);
});
- await updateParticipants(newGroup);
+ await updatePostParticipant({
+ user,
+ id: group.id,
+ });
+
+ dispatch(setStudyGroup(newGroup));
+};
+
+export const deleteParticipant = () => async (dispatch, getState) => {
+ const { groupReducer: { group }, authReducer: { user } } = getState();
+
+ const newGroup = {
+ ...group,
+ participants: group.participants.filter((participant) => participant !== user),
+ };
+
+ await deletePostParticipant({
+ user,
+ id: group.id,
+ });
dispatch(setStudyGroup(newGroup));
};
diff --git a/src/reducers/groupSlice.test.js b/src/reducers/groupSlice.test.js
index 2a4c654..aa92267 100644
--- a/src/reducers/groupSlice.test.js
+++ b/src/reducers/groupSlice.test.js
@@ -12,7 +12,8 @@ import reducer, {
writeStudyGroup,
clearWriteFields,
successWrite,
- updateStudyGroup,
+ updateParticipant,
+ deleteParticipant,
} from './groupSlice';
import STUDY_GROUPS from '../../fixtures/study-groups';
@@ -196,7 +197,7 @@ describe('async actions', () => {
});
});
- describe('updateStudyGroup', () => {
+ describe('updateParticipant', () => {
beforeEach(() => {
store = mockStore({
groupReducer: {
@@ -209,7 +210,7 @@ describe('async actions', () => {
});
it('dispatches setStudyGroup', async () => {
- await store.dispatch(updateStudyGroup());
+ await store.dispatch(updateParticipant());
const actions = store.getActions();
@@ -219,4 +220,37 @@ describe('async actions', () => {
}));
});
});
+
+ describe('deleteParticipant', () => {
+ const group = {
+ id: 1,
+ participants: [
+ 'user2',
+ 'example',
+ ],
+ };
+ const user = 'example';
+
+ beforeEach(() => {
+ store = mockStore({
+ groupReducer: {
+ group,
+ },
+ authReducer: {
+ user,
+ },
+ });
+ });
+
+ it('dispatches setStudyGroup', async () => {
+ await store.dispatch(deleteParticipant());
+
+ const actions = store.getActions();
+
+ expect(actions[0]).toEqual(setStudyGroup({
+ ...group,
+ participants: group.participants.filter((participant) => participant !== user),
+ }));
+ });
+ });
});
diff --git a/src/services/__mocks__/api.js b/src/services/__mocks__/api.js
index f1f1aa7..840f49e 100644
--- a/src/services/__mocks__/api.js
+++ b/src/services/__mocks__/api.js
@@ -10,4 +10,6 @@ export const postUserLogin = jest.fn();
export const postUserLogout = jest.fn();
-export const updateParticipants = jest.fn();
+export const updatePostParticipant = jest.fn();
+
+export const deletePostParticipant = jest.fn();
diff --git a/src/services/api.js b/src/services/api.js
index 328b949..9a2887a 100644
--- a/src/services/api.js
+++ b/src/services/api.js
@@ -63,12 +63,20 @@ export const postStudyGroup = async (group) => {
return id;
};
-export const updateParticipants = async (group) => {
- const { id, participants } = group;
+export const updatePostParticipant = async ({ id, user }) => {
+ const groups = db.collection('groups').doc(id);
+ await groups.update({
+ participants: fireStore.FieldValue.arrayUnion(user),
+ });
+};
+
+export const deletePostParticipant = async ({ id, user }) => {
const groups = db.collection('groups').doc(id);
- await groups.update({ participants });
+ await groups.update({
+ participants: fireStore.FieldValue.arrayRemove(user),
+ });
};
export const postUserRegister = async ({ userEmail, password }) => {