diff --git a/src/components/Challenge/ChallengeCardDone.spec.tsx b/src/components/Challenge/ChallengeCardDone.spec.tsx index 07371a118bd120b2a9ec85ed01d9bed5cd09845b..433705ca28738406bf12cceeca4bc17f6fa8435f 100644 --- a/src/components/Challenge/ChallengeCardDone.spec.tsx +++ b/src/components/Challenge/ChallengeCardDone.spec.tsx @@ -1,6 +1,12 @@ +import { Button } from '@material-ui/core' import ChallengeCardDone from 'components/Challenge/ChallengeCardDone' -import { shallow } from 'enzyme' +import { mount } from 'enzyme' +import toJson from 'enzyme-to-json' import React from 'react' +import * as reactRedux from 'react-redux' +import { Provider } from 'react-redux' +import configureStore from 'redux-mock-store' +import { waitForComponentToPaint } from '../../../tests/__mocks__/testUtils' import { userChallengeData } from '../../../tests/__mocks__/userChallengeData.mock' jest.mock('cozy-ui/transpiled/react/I18n', () => ({ @@ -8,11 +14,10 @@ jest.mock('cozy-ui/transpiled/react/I18n', () => ({ t: (str: string) => str, })), })) -const mockImportIconById = jest.fn() const mockFormatNumberValues = jest.fn() jest.mock('utils/utils', () => { return { - importIconById: jest.fn(() => mockImportIconById), + importIconById: jest.fn(() => null), formatNumberValues: jest.fn(() => mockFormatNumberValues), getChallengeTitleWithLineReturn: jest.fn(() => 'Challenge 1'), } @@ -24,11 +29,91 @@ jest.mock('react-router-dom', () => ({ useNavigate: () => mockedNavigate, })) +const mockUpdateUserChallenge = jest.fn() +jest.mock('services/challenge.service', () => { + return jest.fn(() => { + return { + updateUserChallenge: mockUpdateUserChallenge, + } + }) +}) + +const mockStore = configureStore([]) +const mockDispatch = jest.fn() +const useDispatchSpy = jest.spyOn(reactRedux, 'useDispatch') + describe('ChallengeCardDone component', () => { - it('should be rendered correctly', () => { - const component = shallow( - <ChallengeCardDone userChallenge={userChallengeData[0]} /> - ).getElement() - expect(component).toMatchSnapshot() + const storeNoCurrentChallenge = mockStore({ + ecolyo: { + challenge: { currentChallenge: null }, + }, + }) + it('should be rendered correctly', async () => { + const wrapper = mount( + <Provider store={storeNoCurrentChallenge}> + <ChallengeCardDone userChallenge={userChallengeData[0]} /> + </Provider> + ) + await waitForComponentToPaint(wrapper) + expect(toJson(wrapper)).toMatchSnapshot() + }) + + describe('Reset final challenge', () => { + beforeEach(() => { + mockUpdateUserChallenge.mockClear() + mockDispatch.mockClear() + }) + it('should reset challenge if no other challenge is on going', async () => { + useDispatchSpy.mockImplementationOnce(() => mockDispatch) + const wrapper = mount( + <Provider store={storeNoCurrentChallenge}> + <ChallengeCardDone userChallenge={userChallengeData[0]} /> + </Provider> + ) + wrapper.find(Button).last().simulate('click') + await waitForComponentToPaint(wrapper) + expect(mockDispatch).toBeCalledTimes(1) + expect(mockDispatch).toBeCalledWith({ + type: 'challenge/updateUserChallengeList', + }) + expect(mockUpdateUserChallenge).toBeCalledTimes(1) + }) + it('should not reset challenge if another challenge is on going', async () => { + useDispatchSpy.mockImplementationOnce(() => mockDispatch) + const store = mockStore({ + ecolyo: { + challenge: { currentChallenge: userChallengeData[1] }, + }, + }) + const wrapper = mount( + <Provider store={store}> + <ChallengeCardDone userChallenge={userChallengeData[0]} /> + </Provider> + ) + wrapper.find(Button).last().simulate('click') + await waitForComponentToPaint(wrapper) + expect(mockDispatch).toBeCalledTimes(0) + expect(mockUpdateUserChallenge).toBeCalledTimes(0) + }) + it('should be primary button is challenge is lost', async () => { + const wrapper = mount( + <Provider store={storeNoCurrentChallenge}> + <ChallengeCardDone userChallenge={userChallengeData[1]} /> + </Provider> + ) + await waitForComponentToPaint(wrapper) + const resetButton = wrapper.find('button').last() + expect(resetButton.hasClass('btn-primary-challenge')).toBe(true) + }) + it('should be secondary button is challenge is won', async () => { + const wrapper = mount( + <Provider store={storeNoCurrentChallenge}> + <ChallengeCardDone userChallenge={userChallengeData[0]} /> + </Provider> + ) + await waitForComponentToPaint(wrapper) + const resetButton = wrapper.find('button').last() + expect(resetButton.hasClass('btn-secondary-negative')).toBe(true) + }) }) }) diff --git a/src/components/Challenge/ChallengeCardDone.tsx b/src/components/Challenge/ChallengeCardDone.tsx index 9430da1fbfba471072a8763eb91278819e8d9315..217f24cd8255b055b506af755e312b081c32ce47 100644 --- a/src/components/Challenge/ChallengeCardDone.tsx +++ b/src/components/Challenge/ChallengeCardDone.tsx @@ -2,11 +2,19 @@ import { Button } from '@material-ui/core' import defaultIcon from 'assets/icons/visu/duelResult/default.svg' import classNames from 'classnames' import StyledIcon from 'components/CommonKit/Icon/StyledIcon' +import { useClient } from 'cozy-client' import { useI18n } from 'cozy-ui/transpiled/react/I18n' -import { UserChallengeSuccess } from 'enum/userChallenge.enum' +import { + UserChallengeSuccess, + UserChallengeUpdateFlag, +} from 'enum/userChallenge.enum' import { UserChallenge } from 'models' -import React, { useEffect, useState } from 'react' +import React, { Dispatch, useEffect, useState } from 'react' +import { useDispatch, useSelector } from 'react-redux' import { useNavigate } from 'react-router-dom' +import ChallengeService from 'services/challenge.service' +import { AppActionsTypes, AppStore } from 'store' +import { updateUserChallengeList } from 'store/challenge/challenge.slice' import { formatNumberValues, getChallengeTitleWithLineReturn, @@ -21,8 +29,14 @@ const ChallengeCardDone = ({ }) => { const { t } = useI18n() const navigate = useNavigate() + const client = useClient() + const dispatch = useDispatch<Dispatch<AppActionsTypes>>() + const [winIcon, setWinIcon] = useState<string>(defaultIcon) const [lossIcon, setLossIcon] = useState<string>(defaultIcon) + const { currentChallenge } = useSelector( + (state: AppStore) => state.ecolyo.challenge + ) const getUserSaving = (_userChallenge: UserChallenge) => { let label @@ -52,6 +66,15 @@ const ChallengeCardDone = ({ navigate('/challenges/duel?id=' + userChallenge.id) } + const handleChallengeReset = async () => { + const challengeService = new ChallengeService(client) + const updatedChallenge = await challengeService.updateUserChallenge( + userChallenge, + UserChallengeUpdateFlag.DUEL_RESET + ) + dispatch(updateUserChallengeList(updatedChallenge)) + } + useEffect(() => { async function handleEcogestureIcon() { const icon = await importIconById(userChallenge.id + '-1', 'duelResult') @@ -106,16 +129,32 @@ const ChallengeCardDone = ({ {t('challenge.card_done.final_defi')} </span> </div> - <Button - aria-label={t('challenge.card_done.final_defi_view')} - onClick={goDuel} - classes={{ - root: 'btn-secondary-negative review-btn', - label: 'text-15-bold', - }} - > - {t('challenge.card_done.final_defi_view')} - </Button> + <div className="buttons"> + <Button + aria-label={t('challenge.card_done.final_defi_view')} + onClick={goDuel} + classes={{ + root: 'btn-secondary-negative grey-border', + label: 'text-15-bold', + }} + > + {t('challenge.card_done.final_defi_view')} + </Button> + <Button + aria-label={t('challenge.card_done.reset_defi')} + onClick={handleChallengeReset} + classes={{ + root: + userChallenge.success === UserChallengeSuccess.WIN + ? 'btn-secondary-negative grey-border' + : 'btn-primary-challenge', + label: 'text-15-bold', + }} + disabled={currentChallenge !== null} + > + {t('challenge.card_done.reset_defi')} + </Button> + </div> </div> ) } diff --git a/src/components/Challenge/ChallengeCardUnlocked.spec.tsx b/src/components/Challenge/ChallengeCardUnlocked.spec.tsx index 13cb72235c2e921b95a7d37d006a05a4016e2c95..a5f422cda7c87dd61208ee76dd7b84f5d0b22a2f 100644 --- a/src/components/Challenge/ChallengeCardUnlocked.spec.tsx +++ b/src/components/Challenge/ChallengeCardUnlocked.spec.tsx @@ -1,3 +1,4 @@ +import { Button } from '@material-ui/core' import defaultIcon from 'assets/icons/visu/challenge/challengeLocked.svg' import { FluidType } from 'enum/fluid.enum' import { mount } from 'enzyme' @@ -6,8 +7,10 @@ import { Provider } from 'react-redux' import UsageEventService from 'services/usageEvent.service' import { createMockEcolyoStore, + mockChallengeState, mockGlobalState, } from '../../../tests/__mocks__/store' +import { waitForComponentToPaint } from '../../../tests/__mocks__/testUtils' import { userChallengeData } from '../../../tests/__mocks__/userChallengeData.mock' import ChallengeCardUnlocked from './ChallengeCardUnlocked' import ChallengeNoFluidModal from './ChallengeNoFluidModal' @@ -70,6 +73,7 @@ describe('ChallengeCardUnlocked component', () => { fluidTypes: [FluidType.ELECTRICITY], fluidStatus: [{ ...mockGlobalState.fluidStatus[0], status: 200 }], }, + challenge: mockChallengeState, }) const wrapper = mount( <Provider store={store}> @@ -81,4 +85,22 @@ describe('ChallengeCardUnlocked component', () => { expect(wrapper.find(ChallengeNoFluidModal).prop('open')).toBeFalsy() expect(mockStartUserChallenge).toHaveBeenCalledWith(userChallengeData[0]) }) + + it('should not be able to launch challenge if another one is active', () => { + const store = createMockEcolyoStore({ + global: mockGlobalState, + challenge: { + ...mockChallengeState, + currentChallenge: userChallengeData[1], + }, + }) + const wrapper = mount( + <Provider store={store}> + <ChallengeCardUnlocked userChallenge={userChallengeData[0]} /> + </Provider> + ) + waitForComponentToPaint(wrapper) + const resetButton = wrapper.find(Button).last() + expect(resetButton.prop('disabled')).toBe(true) + }) }) diff --git a/src/components/Challenge/ChallengeCardUnlocked.tsx b/src/components/Challenge/ChallengeCardUnlocked.tsx index e0e70808ecfdbca719ea7cf69cb6dd745aed06c0..dc76880064e5c52a9e680bf394130e73ba743dd2 100644 --- a/src/components/Challenge/ChallengeCardUnlocked.tsx +++ b/src/components/Challenge/ChallengeCardUnlocked.tsx @@ -24,10 +24,11 @@ const ChallengeCardUnlocked = ({ const { t } = useI18n() const client: Client = useClient() const dispatch = useDispatch<Dispatch<AppActionsTypes>>() - const { fluidTypes, fluidStatus } = useSelector( - (state: AppStore) => state.ecolyo.global - ) const [openNoFluidModal, setopenNoFluidModal] = useState(false) + const { + global: { fluidTypes, fluidStatus }, + challenge: { currentChallenge }, + } = useSelector((state: AppStore) => state.ecolyo) const [challengeIcon, setChallengeIcon] = useState(defaultIcon) let statusRequirementOk = false @@ -93,6 +94,7 @@ const ChallengeCardUnlocked = ({ root: 'btn-duel-active', label: 'text-16-bold', }} + disabled={currentChallenge !== null} > {t('challenge.card_unlocked.button_launch')} </Button> diff --git a/src/components/Challenge/ChallengeView.tsx b/src/components/Challenge/ChallengeView.tsx index 95f526de73d4673de4b050e9e3567ca74837688f..94673b49562f4f32338b9c073ca73032bd37894a 100644 --- a/src/components/Challenge/ChallengeView.tsx +++ b/src/components/Challenge/ChallengeView.tsx @@ -6,7 +6,6 @@ import CozyBar from 'components/Header/CozyBar' import Header from 'components/Header/Header' import { useI18n } from 'cozy-ui/transpiled/react/I18n' import { UserChallengeState } from 'enum/userChallenge.enum' -import { UserChallenge } from 'models' import React, { useCallback, useEffect, useState } from 'react' import { useSelector } from 'react-redux' import { AppStore } from 'store' @@ -22,12 +21,11 @@ const ChallengeView = () => { const marginPx = 16 const cardWidth = window.outerWidth < 500 ? window.outerWidth - marginPx * 6 : 285 - const cardHeight = window.outerHeight * 0.6 + const cardHeight = window.outerHeight * 0.65 const [headerHeight, setHeaderHeight] = useState<number>(0) const [touchStart, setTouchStart] = useState<number>() const [touchEnd, setTouchEnd] = useState<number>() const [index, setIndex] = useState<number>(0) - const [lastChallengeIndex, setLastChallengeIndex] = useState<number>(0) const [isLastDuelDone, setIsLastDuelDone] = useState<boolean>(false) const [containerTranslation, setContainerTranslation] = useState<number>(marginPx) @@ -46,22 +44,14 @@ const ChallengeView = () => { index < userChallengeList.length - 1 || (isLastDuelDone && index < userChallengeList.length) ) { - if (index === 0) - setContainerTranslation( - (prev: number) => prev - cardWidth - marginPx * 1.2 - ) - else if (index >= 1) - setContainerTranslation((prev: number) => prev - cardWidth - marginPx) - else setContainerTranslation((prev: number) => prev - cardWidth) + setContainerTranslation(prev => prev - cardWidth - marginPx) setIndex(prev => prev + 1) } - }, [cardWidth, index, userChallengeList.length]) + }, [cardWidth, index, isLastDuelDone, userChallengeList.length]) const moveSliderLeft = useCallback(() => { if (index > 0) { - if (index >= 1) - setContainerTranslation((prev: number) => prev + cardWidth + marginPx) - else setContainerTranslation((prev: number) => prev + cardWidth) + setContainerTranslation(prev => prev + cardWidth + marginPx) setIndex(prev => prev - 1) } if (index <= 1) { @@ -95,28 +85,20 @@ const ChallengeView = () => { } useEffect(() => { - userChallengeList.forEach((challenge: UserChallenge, i: number) => { - if ( + let currentChallengeIndex = userChallengeList.findIndex( + challenge => challenge.state === UserChallengeState.UNLOCKED || challenge.state === UserChallengeState.ONGOING || challenge.state === UserChallengeState.DUEL - ) { - setLastChallengeIndex(i) - if (lastChallengeIndex === 0) return - else if (lastChallengeIndex === 1) { - setContainerTranslation(0 - cardWidth * lastChallengeIndex) - } else { - setContainerTranslation( - 0 - cardWidth * lastChallengeIndex - marginPx * 1.2 - ) - } - if (isLastDuelDone) { - setLastChallengeIndex(i + 1) - } - setIndex(i) - } - }) - }, [userChallengeList, lastChallengeIndex, cardWidth, isLastDuelDone]) + ) + if (currentChallengeIndex === -1) { + currentChallengeIndex = isLastDuelDone ? userChallengeList.length : 0 + } + setContainerTranslation( + -currentChallengeIndex * (cardWidth + marginPx) + marginPx + ) + setIndex(currentChallengeIndex) + }, [userChallengeList, cardWidth, isLastDuelDone]) useEffect(() => { if ( @@ -166,7 +148,7 @@ const ChallengeView = () => { {isLastDuelDone && ( <ChallengeCard indexSlider={index} - index={5} + index={userChallengeList.length} cardWidth={cardWidth} cardHeight={cardHeight} isChallengeCardLast={true} diff --git a/src/components/Challenge/__snapshots__/ChallengeCardDone.spec.tsx.snap b/src/components/Challenge/__snapshots__/ChallengeCardDone.spec.tsx.snap index 54a1e470aabfe564142211875f79cde6e1102c84..4065b79a95263d9d80105608cc039d1d1a998470 100644 --- a/src/components/Challenge/__snapshots__/ChallengeCardDone.spec.tsx.snap +++ b/src/components/Challenge/__snapshots__/ChallengeCardDone.spec.tsx.snap @@ -1,58 +1,501 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ChallengeCardDone component should be rendered correctly 1`] = ` -<div - className="cardContent cardDone" +<Provider + store={ + Object { + "clearActions": [Function], + "dispatch": [Function], + "getActions": [Function], + "getState": [Function], + "replaceReducer": [Function], + "subscribe": [Function], + } + } > - <div - className="challengeName text-22-bold" - > - Challenge 1 - </div> - <div - className="iconResult" - > - <StyledIcon - className="imgResult" - icon="test-file-stub" - size={180} - /> - </div> - <div - className="statsResult" + <ChallengeCardDone + userChallenge={ + Object { + "action": Object { + "ecogesture": null, + "startDate": null, + "state": 0, + }, + "description": "Description challenge 1", + "duel": Object { + "description": "Je parie un ours polaire que vous ne pouvez pas consommer moins que #CONSUMPTION € en 1 semaine", + "duration": "P30D", + "fluidTypes": Array [], + "id": "DUEL001", + "startDate": null, + "state": 0, + "threshold": 0, + "title": "Title DUEL001", + "userConsumption": 0, + }, + "endingDate": null, + "exploration": Object { + "complementary_description": "Refaire un tour dans son profil si déjà fait", + "date": null, + "description": "Avoir complété son profil", + "ecogesture_id": "", + "fluid_condition": Array [], + "id": "EXPLORATION001", + "message_success": "Vous avez complété votre profil ou refait un tour dans votre profil", + "progress": 0, + "state": 0, + "target": 1, + "type": 1, + }, + "id": "CHALLENGE0001", + "progress": Object { + "actionProgress": 0, + "explorationProgress": 0, + "quizProgress": 0, + }, + "quiz": Object { + "customQuestion": Object { + "interval": 20, + "period": Object {}, + "questionLabel": "Custom1", + "result": 0, + "singleFluid": false, + "timeStep": 20, + "type": 0, + }, + "id": "QUIZ001", + "questions": Array [ + Object { + "answers": Array [ + Object { + "answerLabel": "86 km", + "isTrue": true, + }, + Object { + "answerLabel": "78 km", + "isTrue": false, + }, + Object { + "answerLabel": "56 km", + "isTrue": false, + }, + ], + "explanation": "L’aqueduc du Gier est un des aqueducs antiques de Lyon desservant la ville antique de Lugdunum. Avec ses 86 km il est le plus long des quatre aqueducs ayant alimenté la ville en eau, et celui dont les structures sont le mieux conservées. Il doit son nom au fait qu'il puise aux sources du Gier, affluent du Rhône", + "questionLabel": "Quelle longueur faisait l’aqueduc du Gier pour acheminer l’eau sur Lyon à l’époque romaine ?", + "result": 0, + "source": "string", + }, + Object { + "answers": Array [ + Object { + "answerLabel": "1 point d’eau public pour 800 habitants.", + "isTrue": true, + }, + Object { + "answerLabel": "1 point d’eau public pour 400 habitants.", + "isTrue": false, + }, + Object { + "answerLabel": "1 point d’eau public pour 200 habitants.", + "isTrue": false, + }, + ], + "explanation": "string", + "questionLabel": "En 1800 à Lyon, combien de points d'eau y avait-il par habitants ?", + "result": 0, + "source": "string", + }, + Object { + "answers": Array [ + Object { + "answerLabel": "François Mitterrand", + "isTrue": false, + }, + Object { + "answerLabel": "Napoléon Ier", + "isTrue": true, + }, + Object { + "answerLabel": "Napoléon III", + "isTrue": false, + }, + ], + "explanation": "string", + "questionLabel": "Qui officialise la création de la Compagnie Générale des eaux ?", + "result": 0, + "source": "string", + }, + Object { + "answers": Array [ + Object { + "answerLabel": "string", + "isTrue": false, + }, + Object { + "answerLabel": "string", + "isTrue": false, + }, + Object { + "answerLabel": "Aristide Dumont", + "isTrue": true, + }, + ], + "explanation": "string", + "questionLabel": "Quel ingénieur est à l’origine du projet d’alimentation en eau en 1856 ?", + "result": 0, + "source": "string", + }, + ], + "result": 0, + "startDate": null, + "state": 0, + }, + "startDate": null, + "state": 4, + "success": 2, + "target": 15, + "title": "Challenge 1", + } + } > <div - className="labelResult win" - > - challenge.card_done.win - </div> - <span - className="text-18" + className="cardContent cardDone" > - challenge.card_done.saving - <span - className="text-18-bold" + <div + className="challengeName text-22-bold" > - function () { + Challenge 1 + </div> + <div + className="iconResult" + > + <StyledIcon + className="imgResult" + icon="test-file-stub" + size={180} + > + <Icon + aria-hidden={true} + className="imgResult" + icon="test-file-stub" + size={180} + spin={false} + > + <Component + aria-hidden={true} + className="imgResult styles__icon___23x3R" + height={180} + style={Object {}} + width={180} + > + <svg + aria-hidden={true} + className="imgResult styles__icon___23x3R" + height={180} + style={Object {}} + width={180} + > + <use + xlinkHref="#test-file-stub" + /> + </svg> + </Component> + </Icon> + </StyledIcon> + </div> + <div + className="statsResult" + > + <div + className="labelResult win" + > + challenge.card_done.win + </div> + <span + className="text-18" + > + challenge.card_done.saving + <span + className="text-18-bold" + > + function () { return fn.apply(this, arguments); } - € - </span> - <br /> - challenge.card_done.final_defi - </span> - </div> - <WithStyles(ForwardRef(Button)) - aria-label="challenge.card_done.final_defi_view" - classes={ - Object { - "label": "text-15-bold", - "root": "btn-secondary-negative review-btn", - } - } - onClick={[Function]} - > - challenge.card_done.final_defi_view - </WithStyles(ForwardRef(Button))> -</div> + € + </span> + <br /> + challenge.card_done.final_defi + </span> + </div> + <div + className="buttons" + > + <WithStyles(ForwardRef(Button)) + aria-label="challenge.card_done.final_defi_view" + classes={ + Object { + "label": "text-15-bold", + "root": "btn-secondary-negative grey-border", + } + } + onClick={[Function]} + > + <ForwardRef(Button) + aria-label="challenge.card_done.final_defi_view" + classes={ + Object { + "colorInherit": "MuiButton-colorInherit", + "contained": "MuiButton-contained", + "containedPrimary": "MuiButton-containedPrimary", + "containedSecondary": "MuiButton-containedSecondary", + "containedSizeLarge": "MuiButton-containedSizeLarge", + "containedSizeSmall": "MuiButton-containedSizeSmall", + "disableElevation": "MuiButton-disableElevation", + "disabled": "Mui-disabled", + "endIcon": "MuiButton-endIcon", + "focusVisible": "Mui-focusVisible", + "fullWidth": "MuiButton-fullWidth", + "iconSizeLarge": "MuiButton-iconSizeLarge", + "iconSizeMedium": "MuiButton-iconSizeMedium", + "iconSizeSmall": "MuiButton-iconSizeSmall", + "label": "MuiButton-label text-15-bold", + "outlined": "MuiButton-outlined", + "outlinedPrimary": "MuiButton-outlinedPrimary", + "outlinedSecondary": "MuiButton-outlinedSecondary", + "outlinedSizeLarge": "MuiButton-outlinedSizeLarge", + "outlinedSizeSmall": "MuiButton-outlinedSizeSmall", + "root": "MuiButton-root btn-secondary-negative grey-border", + "sizeLarge": "MuiButton-sizeLarge", + "sizeSmall": "MuiButton-sizeSmall", + "startIcon": "MuiButton-startIcon", + "text": "MuiButton-text", + "textPrimary": "MuiButton-textPrimary", + "textSecondary": "MuiButton-textSecondary", + "textSizeLarge": "MuiButton-textSizeLarge", + "textSizeSmall": "MuiButton-textSizeSmall", + } + } + onClick={[Function]} + > + <WithStyles(ForwardRef(ButtonBase)) + aria-label="challenge.card_done.final_defi_view" + className="MuiButton-root btn-secondary-negative grey-border MuiButton-text" + component="button" + disabled={false} + focusRipple={true} + focusVisibleClassName="Mui-focusVisible" + onClick={[Function]} + type="button" + > + <ForwardRef(ButtonBase) + aria-label="challenge.card_done.final_defi_view" + className="MuiButton-root btn-secondary-negative grey-border MuiButton-text" + classes={ + Object { + "disabled": "Mui-disabled", + "focusVisible": "Mui-focusVisible", + "root": "MuiButtonBase-root", + } + } + component="button" + disabled={false} + focusRipple={true} + focusVisibleClassName="Mui-focusVisible" + onClick={[Function]} + type="button" + > + <button + aria-label="challenge.card_done.final_defi_view" + className="MuiButtonBase-root MuiButton-root btn-secondary-negative grey-border MuiButton-text" + disabled={false} + onBlur={[Function]} + onClick={[Function]} + onDragLeave={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onKeyUp={[Function]} + onMouseDown={[Function]} + onMouseLeave={[Function]} + onMouseUp={[Function]} + onTouchEnd={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + tabIndex={0} + type="button" + > + <span + className="MuiButton-label text-15-bold" + > + challenge.card_done.final_defi_view + </span> + <WithStyles(memo) + center={false} + > + <ForwardRef(TouchRipple) + center={false} + classes={ + Object { + "child": "MuiTouchRipple-child", + "childLeaving": "MuiTouchRipple-childLeaving", + "childPulsate": "MuiTouchRipple-childPulsate", + "ripple": "MuiTouchRipple-ripple", + "ripplePulsate": "MuiTouchRipple-ripplePulsate", + "rippleVisible": "MuiTouchRipple-rippleVisible", + "root": "MuiTouchRipple-root", + } + } + > + <span + className="MuiTouchRipple-root" + > + <TransitionGroup + childFactory={[Function]} + component={null} + exit={true} + /> + </span> + </ForwardRef(TouchRipple)> + </WithStyles(memo)> + </button> + </ForwardRef(ButtonBase)> + </WithStyles(ForwardRef(ButtonBase))> + </ForwardRef(Button)> + </WithStyles(ForwardRef(Button))> + <WithStyles(ForwardRef(Button)) + aria-label="challenge.card_done.reset_defi" + classes={ + Object { + "label": "text-15-bold", + "root": "btn-secondary-negative grey-border", + } + } + disabled={false} + onClick={[Function]} + > + <ForwardRef(Button) + aria-label="challenge.card_done.reset_defi" + classes={ + Object { + "colorInherit": "MuiButton-colorInherit", + "contained": "MuiButton-contained", + "containedPrimary": "MuiButton-containedPrimary", + "containedSecondary": "MuiButton-containedSecondary", + "containedSizeLarge": "MuiButton-containedSizeLarge", + "containedSizeSmall": "MuiButton-containedSizeSmall", + "disableElevation": "MuiButton-disableElevation", + "disabled": "Mui-disabled", + "endIcon": "MuiButton-endIcon", + "focusVisible": "Mui-focusVisible", + "fullWidth": "MuiButton-fullWidth", + "iconSizeLarge": "MuiButton-iconSizeLarge", + "iconSizeMedium": "MuiButton-iconSizeMedium", + "iconSizeSmall": "MuiButton-iconSizeSmall", + "label": "MuiButton-label text-15-bold", + "outlined": "MuiButton-outlined", + "outlinedPrimary": "MuiButton-outlinedPrimary", + "outlinedSecondary": "MuiButton-outlinedSecondary", + "outlinedSizeLarge": "MuiButton-outlinedSizeLarge", + "outlinedSizeSmall": "MuiButton-outlinedSizeSmall", + "root": "MuiButton-root btn-secondary-negative grey-border", + "sizeLarge": "MuiButton-sizeLarge", + "sizeSmall": "MuiButton-sizeSmall", + "startIcon": "MuiButton-startIcon", + "text": "MuiButton-text", + "textPrimary": "MuiButton-textPrimary", + "textSecondary": "MuiButton-textSecondary", + "textSizeLarge": "MuiButton-textSizeLarge", + "textSizeSmall": "MuiButton-textSizeSmall", + } + } + disabled={false} + onClick={[Function]} + > + <WithStyles(ForwardRef(ButtonBase)) + aria-label="challenge.card_done.reset_defi" + className="MuiButton-root btn-secondary-negative grey-border MuiButton-text" + component="button" + disabled={false} + focusRipple={true} + focusVisibleClassName="Mui-focusVisible" + onClick={[Function]} + type="button" + > + <ForwardRef(ButtonBase) + aria-label="challenge.card_done.reset_defi" + className="MuiButton-root btn-secondary-negative grey-border MuiButton-text" + classes={ + Object { + "disabled": "Mui-disabled", + "focusVisible": "Mui-focusVisible", + "root": "MuiButtonBase-root", + } + } + component="button" + disabled={false} + focusRipple={true} + focusVisibleClassName="Mui-focusVisible" + onClick={[Function]} + type="button" + > + <button + aria-label="challenge.card_done.reset_defi" + className="MuiButtonBase-root MuiButton-root btn-secondary-negative grey-border MuiButton-text" + disabled={false} + onBlur={[Function]} + onClick={[Function]} + onDragLeave={[Function]} + onFocus={[Function]} + onKeyDown={[Function]} + onKeyUp={[Function]} + onMouseDown={[Function]} + onMouseLeave={[Function]} + onMouseUp={[Function]} + onTouchEnd={[Function]} + onTouchMove={[Function]} + onTouchStart={[Function]} + tabIndex={0} + type="button" + > + <span + className="MuiButton-label text-15-bold" + > + challenge.card_done.reset_defi + </span> + <WithStyles(memo) + center={false} + > + <ForwardRef(TouchRipple) + center={false} + classes={ + Object { + "child": "MuiTouchRipple-child", + "childLeaving": "MuiTouchRipple-childLeaving", + "childPulsate": "MuiTouchRipple-childPulsate", + "ripple": "MuiTouchRipple-ripple", + "ripplePulsate": "MuiTouchRipple-ripplePulsate", + "rippleVisible": "MuiTouchRipple-rippleVisible", + "root": "MuiTouchRipple-root", + } + } + > + <span + className="MuiTouchRipple-root" + > + <TransitionGroup + childFactory={[Function]} + component={null} + exit={true} + /> + </span> + </ForwardRef(TouchRipple)> + </WithStyles(memo)> + </button> + </ForwardRef(ButtonBase)> + </WithStyles(ForwardRef(ButtonBase))> + </ForwardRef(Button)> + </WithStyles(ForwardRef(Button))> + </div> + </div> + </ChallengeCardDone> +</Provider> `; diff --git a/src/components/Challenge/__snapshots__/ChallengeView.spec.tsx.snap b/src/components/Challenge/__snapshots__/ChallengeView.spec.tsx.snap index 3ddca6ea025581559a79db5eacfab2fa73695af8..5b7b889d71ab7c6a096b771181cb0862a3a228c0 100644 --- a/src/components/Challenge/__snapshots__/ChallengeView.spec.tsx.snap +++ b/src/components/Challenge/__snapshots__/ChallengeView.spec.tsx.snap @@ -38,15 +38,15 @@ exports[`ChallengeView component should be rendered correctly 1`] = ` className="challenge-container" style={ Object { - "transform": "translateX(-874.2px)", + "transform": "translateX(-586px)", } } > <mock-challengecard - cardHeight={460.79999999999995} + cardHeight={499.20000000000005} cardWidth={285} index={0} - indexSlider={3} + indexSlider={2} key="CHALLENGE0001" moveToSlide={[Function]} userChallenge={ @@ -194,10 +194,10 @@ exports[`ChallengeView component should be rendered correctly 1`] = ` } /> <mock-challengecard - cardHeight={460.79999999999995} + cardHeight={499.20000000000005} cardWidth={285} index={1} - indexSlider={3} + indexSlider={2} key="CHALLENGE0002" moveToSlide={[Function]} userChallenge={ @@ -345,10 +345,10 @@ exports[`ChallengeView component should be rendered correctly 1`] = ` } /> <mock-challengecard - cardHeight={460.79999999999995} + cardHeight={499.20000000000005} cardWidth={285} index={2} - indexSlider={3} + indexSlider={2} key="CHALLENGE0003" moveToSlide={[Function]} userChallenge={ @@ -496,10 +496,10 @@ exports[`ChallengeView component should be rendered correctly 1`] = ` } /> <mock-challengecard - cardHeight={460.79999999999995} + cardHeight={499.20000000000005} cardWidth={285} index={3} - indexSlider={3} + indexSlider={2} key="CHALLENGE0004" moveToSlide={[Function]} userChallenge={ @@ -647,10 +647,10 @@ exports[`ChallengeView component should be rendered correctly 1`] = ` } /> <mock-challengecard - cardHeight={460.79999999999995} + cardHeight={499.20000000000005} cardWidth={285} index={4} - indexSlider={3} + indexSlider={2} key="CHALLENGE0005" moveToSlide={[Function]} userChallenge={ @@ -798,10 +798,10 @@ exports[`ChallengeView component should be rendered correctly 1`] = ` } /> <mock-challengecard - cardHeight={460.79999999999995} + cardHeight={499.20000000000005} cardWidth={285} index={5} - indexSlider={3} + indexSlider={2} key="CHALLENGE0006" moveToSlide={[Function]} userChallenge={ diff --git a/src/components/Challenge/challengeCard.scss b/src/components/Challenge/challengeCard.scss index 8088fe285dec927eb17a824e23342866fcc29239..8e07734cfda7d76f59a96956cb1c93f7d11cfd32 100644 --- a/src/components/Challenge/challengeCard.scss +++ b/src/components/Challenge/challengeCard.scss @@ -8,6 +8,7 @@ color: white; display: flex; flex-direction: column; + height: 100%; &.active { transform: scale(1); } diff --git a/src/components/Challenge/challengeCardDone.scss b/src/components/Challenge/challengeCardDone.scss index 1b7fd3ba8d9e505f853c63afce38cf8ecf4a6718..c7eee6ea1cbf3ec288d35b78d7130bfc64a957cd 100644 --- a/src/components/Challenge/challengeCardDone.scss +++ b/src/components/Challenge/challengeCardDone.scss @@ -41,10 +41,19 @@ .statsResult { text-align: center; } - .review-btn { - padding: 0.625rem; - margin: 0; - border: 1px solid $grey-bright; + .buttons { + display: flex; + flex-direction: column; + gap: 8px; + width: 100%; + + button { + padding: 0.625rem; + margin: 0; + &.grey-border { + border: 1px solid $grey-bright; + } + } } } } diff --git a/src/components/Challenge/challengeCardOnGoing.scss b/src/components/Challenge/challengeCardOnGoing.scss index ee4236859799c6886e1fcd39d931126570c0a7e0..b992883cae5e9ca96bb56edb8add1b9288eebe3c 100644 --- a/src/components/Challenge/challengeCardOnGoing.scss +++ b/src/components/Challenge/challengeCardOnGoing.scss @@ -4,7 +4,7 @@ .cardContent { background: transparent; &.onGoing { - border: 1px solid #e0e0e0; + border: 1px solid $grey-bright; background: inherit !important; .challengeTitle { margin-top: 0; diff --git a/src/components/Challenge/challengeCardUnlocked.scss b/src/components/Challenge/challengeCardUnlocked.scss index 16e4bf2bbf4e99045335009969c4fd28afe40420..5199a12c6059f7c75609e133af38e1198639bb55 100644 --- a/src/components/Challenge/challengeCardUnlocked.scss +++ b/src/components/Challenge/challengeCardUnlocked.scss @@ -10,7 +10,6 @@ filter: drop-shadow(0px 4px 16px rgba(0, 0, 0, 0.55)); button.btn-duel-active { - margin: auto; padding: 1.2rem 1.5rem; } .challengeIcon { diff --git a/src/components/Challenge/challengeView.scss b/src/components/Challenge/challengeView.scss index b0cc2a8fab8d77ed19e2060f891d2840ea20abef..debbf935567cf6fa048cd62cf955bb21163922ea 100644 --- a/src/components/Challenge/challengeView.scss +++ b/src/components/Challenge/challengeView.scss @@ -23,6 +23,9 @@ .cardContent { margin: auto; cursor: pointer; + &.onGoing { + padding-top: 2.5rem; + } .title { font-weight: 400; text-align: center; diff --git a/src/components/Duel/duelUnlocked.scss b/src/components/Duel/duelUnlocked.scss index 077c1eed988fa2c8797b9610d87350a3b3eefd71..9f8d8899537a2d682affd398c963c5e67d9c9780 100644 --- a/src/components/Duel/duelUnlocked.scss +++ b/src/components/Duel/duelUnlocked.scss @@ -25,4 +25,10 @@ } .button-start { margin-top: 1rem; + width: 100%; + max-width: 175px; +} +button.btn-secondary-negative { + margin: 0; + padding: 0.5rem; } diff --git a/src/components/Quiz/quizFinish.scss b/src/components/Quiz/quizFinish.scss index 79da8a18aaa1ac8f67814f4f6f6e346799c8b76b..288da76b5e2f4fa7c32a9467eea698f95c7fa143 100644 --- a/src/components/Quiz/quizFinish.scss +++ b/src/components/Quiz/quizFinish.scss @@ -9,10 +9,12 @@ color: $white; background: $grey-linear-gradient-background; text-align: center; + display: flex; + flex-direction: column; + align-items: center; button.btn-secondary-negative { border-color: $grey-bright; - min-width: 15rem; } .button-start { margin-top: 3rem; diff --git a/src/enum/userChallenge.enum.ts b/src/enum/userChallenge.enum.ts index 70ac303228dcbaa4e2e7016d58504b0f9b3963fd..193179877e6bb5df6e8a34fb17222215b8446fd8 100644 --- a/src/enum/userChallenge.enum.ts +++ b/src/enum/userChallenge.enum.ts @@ -6,6 +6,7 @@ export enum UserChallengeUpdateFlag { DUEL_CONSUMPTION = 13, DUEL_WIN = 14, DUEL_LOSS = 15, + DUEL_RESET = 16, QUIZ = 20, QUIZ_START = 21, QUIZ_DONE = 22, diff --git a/src/locales/fr.json b/src/locales/fr.json index 5d0f18ff7655c963bf86420f45775254a76480f3..33bb09489a854a882f24fe45074d2874a6e8e396 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -269,7 +269,8 @@ "win": "Gagné", "lost": "Perdu", "final_defi": "sur le duel final", - "final_defi_view": "Revoir le duel final" + "final_defi_view": "Revoir le duel final", + "reset_defi": "Relancer le défi" }, "card_last": { "title": "Tous les défis sont terminés", diff --git a/src/services/challenge.service.ts b/src/services/challenge.service.ts index b9de549d948d7fc7019cc42bc7b339dcc2268459..4373e8a88f0c2109e75bd2d93ad2ba4ca4f871f9 100644 --- a/src/services/challenge.service.ts +++ b/src/services/challenge.service.ts @@ -673,6 +673,15 @@ export default class ChallengeService { success: UserChallengeSuccess.LOST, } break + case UserChallengeUpdateFlag.DUEL_RESET: + updatedDuel = await duelService.resetUserDuel(userChallenge.duel) + updatedUserChallenge = { + ...userChallenge, + state: UserChallengeState.DUEL, + duel: updatedDuel, + success: UserChallengeSuccess.ONGOING, + } + break case UserChallengeUpdateFlag.QUIZ_START: updatedQuiz = await quizService.startUserQuiz(userChallenge.quiz) updatedUserChallenge = { diff --git a/src/services/duel.service.spec.ts b/src/services/duel.service.spec.ts index dd4ea30f57b7c68da20d494d080a6fe153558c65..0a6260c98d10ca2c021a859c66f46a91631764da 100644 --- a/src/services/duel.service.spec.ts +++ b/src/services/duel.service.spec.ts @@ -120,6 +120,20 @@ describe('Duel service', () => { }) }) + describe('resetUserDuel method', () => { + it('should return the userDuel with unlocked state', async () => { + const result = await duelService.resetUserDuel(duelData) + const mockUpdatedDuel: UserDuel = { + ...duelData, + startDate: null, + state: UserDuelState.UNLOCKED, + threshold: 0, + userConsumption: 0, + } + expect(result).toEqual(mockUpdatedDuel) + }) + }) + describe('parseDuelEntityToDuel method', () => { it('should return the userDuel from a duelEntity', () => { const mockUpdatedDuel: UserDuel = { diff --git a/src/services/duel.service.ts b/src/services/duel.service.ts index 63e308e4ca42d94e4a769603e14ee699d86eef1e..d3f010ac5a611afb6e8268d1948641bee945e14b 100644 --- a/src/services/duel.service.ts +++ b/src/services/duel.service.ts @@ -269,6 +269,21 @@ export default class DuelService { return updatedUserDuel } + /** + * Return duel with updated state to UserDuelState.UNLOCKED + * @param {UserDuel} userDuel - userDuel to reset + * @returns {UserDuel} + */ + public async resetUserDuel(userDuel: UserDuel): Promise<UserDuel> { + return { + ...userDuel, + startDate: null, + state: UserDuelState.UNLOCKED, + threshold: 0, + userConsumption: 0, + } + } + /** * Return duel created from duel entity * @param {DuelEntity} duel - userDuel to update diff --git a/src/store/challenge/challenge.slice.ts b/src/store/challenge/challenge.slice.ts index eae360e05ee1019d0aa87b11f70174bfae4b7792..3699373ef4200539e63317137dd068a1adf2acdd 100644 --- a/src/store/challenge/challenge.slice.ts +++ b/src/store/challenge/challenge.slice.ts @@ -56,7 +56,10 @@ export const challengeSlice = createSlice({ const updatedList = [...state.userChallengeList] const findIndex = updatedList.findIndex(challenge => challenge.id === id) updatedList[findIndex] = action.payload - if (typeof updatedList[findIndex + 1] !== 'undefined') { + if ( + typeof updatedList[findIndex + 1] !== 'undefined' && + updatedList[findIndex + 1].state === UserChallengeState.LOCKED + ) { updatedList[findIndex + 1] = { ...updatedList[findIndex + 1], state: UserChallengeState.UNLOCKED, diff --git a/src/styles/components/_buttons.scss b/src/styles/components/_buttons.scss index 8735392e47590a5fb133a2fea430b4571b7e6974..8c91fc20715bea453dacf5aba30d39b082caefaa 100644 --- a/src/styles/components/_buttons.scss +++ b/src/styles/components/_buttons.scss @@ -20,16 +20,11 @@ button { } } } - &.btn-primary-negative { - @include button( - transparent, - $gold-shadow, - 1px solid $grey-dark, - transparent - ) { - background-color: rgba($grey-dark, 0.2); + &.btn-primary-challenge { + @include button($blue-light, black, 1px solid $blue-light, transparent) { + background-color: rgba($blue-light, 0.2); span:first-child { - color: rgba($gold-shadow, 0.7); + color: black; } } }