feat: adding actions and reducer
This commit is contained in:
@@ -1,9 +1,26 @@
|
||||
import {
|
||||
SET_GOAL,
|
||||
SET_CURRENT_JOB_TITLE,
|
||||
ADD_CAREER_INTEREST,
|
||||
REMOVE_CAREER_INTEREEST,
|
||||
} from './constants';
|
||||
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const setGoal = (payload) => ({
|
||||
type: SET_GOAL,
|
||||
payload,
|
||||
});
|
||||
|
||||
export const setCurrentJobTitle = (payload) => ({
|
||||
type: SET_CURRENT_JOB_TITLE,
|
||||
payload,
|
||||
});
|
||||
|
||||
export const addCareerInterest = (payload) => ({
|
||||
type: ADD_CAREER_INTEREST,
|
||||
payload,
|
||||
});
|
||||
|
||||
export const removeCareerInterest = (payload) => ({
|
||||
type: REMOVE_CAREER_INTEREEST,
|
||||
payload,
|
||||
});
|
||||
|
||||
@@ -1,2 +1,7 @@
|
||||
// eslint-disable-next-line import/prefer-default-export
|
||||
export const SET_GOAL = 'SET_GOAL';
|
||||
export const SET_CURRENT_JOB_TITLE = 'SET_CURRENT_JOB_TITLE';
|
||||
export const ADD_CAREER_INTEREST = 'ADD_CAREER_INTEREST';
|
||||
export const REMOVE_CAREER_INTEREEST = 'REMOVE_CAREER_INTEREEST';
|
||||
|
||||
export const STEP1 = 'select-your-preferences';
|
||||
export const STEP2 = 'review-your-results';
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import {
|
||||
SET_GOAL,
|
||||
SET_CURRENT_JOB_TITLE,
|
||||
ADD_CAREER_INTEREST,
|
||||
REMOVE_CAREER_INTEREEST,
|
||||
} from './constants';
|
||||
|
||||
export function skillsReducer(state, action) {
|
||||
@@ -9,6 +12,21 @@ export function skillsReducer(state, action) {
|
||||
...state,
|
||||
currentGoal: action.payload,
|
||||
};
|
||||
case SET_CURRENT_JOB_TITLE:
|
||||
return {
|
||||
...state,
|
||||
currentJobTitle: action.payload,
|
||||
};
|
||||
case ADD_CAREER_INTEREST:
|
||||
return {
|
||||
...state,
|
||||
careerInterests: [...state.careerInterests, action.payload],
|
||||
};
|
||||
case REMOVE_CAREER_INTEREEST:
|
||||
return {
|
||||
...state,
|
||||
careerInterests: state.careerInterests.filter(interest => interest !== action.payload),
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
@@ -16,6 +34,8 @@ export function skillsReducer(state, action) {
|
||||
|
||||
export const skillsInitialState = {
|
||||
currentGoal: '',
|
||||
currentJobTitle: '',
|
||||
careerInterests: [],
|
||||
};
|
||||
|
||||
export default skillsReducer;
|
||||
|
||||
@@ -1,20 +1,60 @@
|
||||
import { skillsReducer } from '../reducer';
|
||||
import { skillsReducer, skillsInitialState } from '../reducer';
|
||||
import {
|
||||
SET_GOAL,
|
||||
SET_CURRENT_JOB_TITLE,
|
||||
ADD_CAREER_INTEREST,
|
||||
REMOVE_CAREER_INTEREEST,
|
||||
} from '../constants';
|
||||
|
||||
describe('skillsReducer', () => {
|
||||
const testState = {
|
||||
currentGoal: '',
|
||||
};
|
||||
const testState = skillsInitialState;
|
||||
beforeEach(() => jest.resetModules());
|
||||
|
||||
it('does not remove present data when SET_GOAL action is dispatched', () => {
|
||||
const newSkillsPayload = 'test-goal';
|
||||
const returnedState = skillsReducer(testState, { type: SET_GOAL, payload: newSkillsPayload });
|
||||
const newGoalPayload = 'test-goal';
|
||||
const returnedState = skillsReducer(testState, { type: SET_GOAL, payload: newGoalPayload });
|
||||
const finalState = {
|
||||
...testState,
|
||||
currentGoal: 'test-goal',
|
||||
};
|
||||
expect(returnedState).toEqual(finalState);
|
||||
});
|
||||
|
||||
it('does not remove present data when SET_JOB_TITLE action is dispatched', () => {
|
||||
const newJobTitlePayload = 'test-job-title';
|
||||
const returnedState = skillsReducer(testState, { type: SET_CURRENT_JOB_TITLE, payload: newJobTitlePayload });
|
||||
const finalState = {
|
||||
...testState,
|
||||
currentJobTitle: 'test-job-title',
|
||||
};
|
||||
expect(returnedState).toEqual(finalState);
|
||||
});
|
||||
|
||||
it('adds a careerInterest when ADD_CAREER_INTEREST action is dispatched', () => {
|
||||
const newCareerInterestPayload = 'test-career-interest';
|
||||
const returnedState = skillsReducer(testState, { type: ADD_CAREER_INTEREST, payload: newCareerInterestPayload });
|
||||
const finalState = {
|
||||
...testState,
|
||||
careerInterests: [...testState.careerInterests, 'test-career-interest'],
|
||||
};
|
||||
expect(returnedState).toEqual(finalState);
|
||||
});
|
||||
|
||||
it('removes a careerInterest when REMOVE_CAREER_INTEREST action is dispatched', () => {
|
||||
const newCareerInterestPayload = 'test-career-interest';
|
||||
const testStateWithInterest = {
|
||||
...testState,
|
||||
careerInterests: [newCareerInterestPayload],
|
||||
};
|
||||
const returnedState = skillsReducer(
|
||||
testStateWithInterest,
|
||||
{ type: REMOVE_CAREER_INTEREEST, payload: newCareerInterestPayload },
|
||||
);
|
||||
const finalState = {
|
||||
...testStateWithInterest,
|
||||
// override the 'careerInterests` field and remove 'test-career-interest' from the array
|
||||
careerInterests: testStateWithInterest.careerInterests.filter(interest => interest !== newCareerInterestPayload),
|
||||
};
|
||||
expect(returnedState).toEqual(finalState);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import React, { useContext } from 'react';
|
||||
import {
|
||||
Button,
|
||||
} from '@edx/paragon';
|
||||
import {
|
||||
setGoal,
|
||||
setCurrentJobTitle,
|
||||
addCareerInterest,
|
||||
removeCareerInterest,
|
||||
} from '../data/actions';
|
||||
import { SkillsBuilderContext } from '../skills-builder-context';
|
||||
import { useAlgoliaSearch } from '../utils/hooks';
|
||||
|
||||
const SelectPreferences = () => {
|
||||
// TODO: Temporarily disable the no-unused-vars check, we'll see these later
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [algoliaClient, productSearchIndex, jobSearchIndex] = useAlgoliaSearch();
|
||||
const [{ currentGoal, currentJobTitle, careerInterests }, dispatch] = useContext(SkillsBuilderContext);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="p-4">
|
||||
<h3>Render Question 1</h3>
|
||||
<Button onClick={() => dispatch(setGoal('learn new things'))}>
|
||||
Answer question 1
|
||||
</Button>
|
||||
<p>Goal: {currentGoal}</p>
|
||||
</div>
|
||||
|
||||
{currentGoal && (
|
||||
<div className="p-4">
|
||||
<h3>Render question 2</h3>
|
||||
<Button onClick={() => dispatch(setCurrentJobTitle('Software Engineer'))}>
|
||||
Answer question 2
|
||||
</Button>
|
||||
<p>Current Job Title: {currentJobTitle}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{currentJobTitle && (
|
||||
<div className="p-4">
|
||||
<h3>Render Question 3</h3>
|
||||
<Button
|
||||
onClick={() => dispatch(addCareerInterest(`Joining the circus ${Math.random().toFixed(2)}`))}
|
||||
disabled={careerInterests.length >= 3}
|
||||
>
|
||||
Answer question 3
|
||||
</Button>
|
||||
<p>
|
||||
Career Interests (click to remove):
|
||||
{careerInterests.map(interest => (
|
||||
<Button onClick={() => dispatch(removeCareerInterest(interest))}>
|
||||
{interest}
|
||||
</Button>
|
||||
))}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectPreferences;
|
||||
@@ -1,79 +1,93 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import React, { useState, useContext } from 'react';
|
||||
import {
|
||||
ActionRow,
|
||||
Button,
|
||||
Container,
|
||||
Form,
|
||||
Stepper,
|
||||
ModalDialog,
|
||||
} from '@edx/paragon';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
import { useHistory } from 'react-router';
|
||||
import {
|
||||
setGoal,
|
||||
} from '../data/actions';
|
||||
STEP1,
|
||||
STEP2,
|
||||
} from '../data/constants';
|
||||
import messages from './messages';
|
||||
|
||||
import { SkillsBuilderContext } from '../skills-builder-context';
|
||||
import { SkillsBuilderHeader } from '../skills-builder-header';
|
||||
import SelectPreferences from './SelectPreferences';
|
||||
import ViewResults from './ViewResults';
|
||||
|
||||
import headerImage from '../images/headerImage.png';
|
||||
|
||||
import { useAlgoliaSearch } from '../utils/hooks';
|
||||
|
||||
const SkillsBuilderModal = () => {
|
||||
const [state, dispatch] = useContext(SkillsBuilderContext);
|
||||
const [learnerGoal, setLearnerGoal] = useState('');
|
||||
// TODO: Temporarily disable the no-unused-vars check, we'll see these later
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const [algoliaClient, productSearchIndex, jobSearchIndex] = useAlgoliaSearch();
|
||||
const [{ careerInterests }] = useContext(SkillsBuilderContext);
|
||||
const [currentStep, setCurrentStep] = useState(STEP1);
|
||||
|
||||
const history = useHistory();
|
||||
|
||||
const onCloseHandle = () => {
|
||||
window.history.back();
|
||||
history.goBack();
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalDialog
|
||||
title="Skills Builder"
|
||||
size="fullscreen"
|
||||
className="skills-builder-modal"
|
||||
isOpen
|
||||
onClose={onCloseHandle}
|
||||
>
|
||||
<ModalDialog.Hero>
|
||||
<ModalDialog.Hero.Background className="bg-primary-500">
|
||||
<img src={headerImage} alt="" className="h-100" />
|
||||
</ModalDialog.Hero.Background>
|
||||
<ModalDialog.Hero.Content>
|
||||
<SkillsBuilderHeader />
|
||||
</ModalDialog.Hero.Content>
|
||||
</ModalDialog.Hero>
|
||||
<ModalDialog.Body>
|
||||
<Container>
|
||||
<h3>Your current goal: {state.currentGoal}</h3>
|
||||
<br />
|
||||
<Form.Group controlId="currentLearnerGoal">
|
||||
<Form.Control
|
||||
type="text"
|
||||
floatingLabel="Goal"
|
||||
value={learnerGoal}
|
||||
onChange={(e) => setLearnerGoal(e.target.value)}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Button onClick={() => dispatch(setGoal(learnerGoal))}>
|
||||
Submit
|
||||
</Button>
|
||||
</Container>
|
||||
</ModalDialog.Body>
|
||||
<ModalDialog.Footer>
|
||||
<ActionRow>
|
||||
<Button variant="tertiary">
|
||||
<FormattedMessage {...messages.goBackButton} />
|
||||
</Button>
|
||||
<ActionRow.Spacer />
|
||||
<Button>
|
||||
<FormattedMessage {...messages.nextStepButton} />
|
||||
</Button>
|
||||
</ActionRow>
|
||||
</ModalDialog.Footer>
|
||||
</ModalDialog>
|
||||
<Stepper activeKey={currentStep}>
|
||||
<ModalDialog
|
||||
title="Skills Builder"
|
||||
size="fullscreen"
|
||||
className="skills-builder-modal"
|
||||
isOpen
|
||||
onClose={onCloseHandle}
|
||||
>
|
||||
<ModalDialog.Hero>
|
||||
<ModalDialog.Hero.Background className="bg-primary-500">
|
||||
<img src={headerImage} alt="" className="h-100" />
|
||||
</ModalDialog.Hero.Background>
|
||||
<ModalDialog.Hero.Content>
|
||||
<SkillsBuilderHeader />
|
||||
</ModalDialog.Hero.Content>
|
||||
</ModalDialog.Hero>
|
||||
|
||||
<ModalDialog.Body>
|
||||
<Container size="lg">
|
||||
<Stepper.Step eventKey={STEP1} title="Select preferences">
|
||||
<SelectPreferences />
|
||||
</Stepper.Step>
|
||||
|
||||
<Stepper.Step eventKey={STEP2} title="Review results">
|
||||
<ViewResults />
|
||||
</Stepper.Step>
|
||||
</Container>
|
||||
</ModalDialog.Body>
|
||||
|
||||
<ModalDialog.Footer>
|
||||
<Stepper.ActionRow eventKey={STEP1}>
|
||||
<Button variant="outline-primary">
|
||||
<FormattedMessage {...messages.goBackButton} />
|
||||
</Button>
|
||||
<Stepper.ActionRow.Spacer />
|
||||
<Button
|
||||
onClick={() => setCurrentStep(STEP2)}
|
||||
disabled={careerInterests.length === 0}
|
||||
>
|
||||
<FormattedMessage {...messages.nextStepButton} />
|
||||
</Button>
|
||||
</Stepper.ActionRow>
|
||||
<Stepper.ActionRow eventKey={STEP2}>
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
onClick={() => setCurrentStep(STEP1)}
|
||||
>
|
||||
<FormattedMessage {...messages.goBackButton} />
|
||||
</Button>
|
||||
<Stepper.ActionRow.Spacer />
|
||||
<Button onClick={onCloseHandle}>
|
||||
<FormattedMessage {...messages.exitButton} />
|
||||
</Button>
|
||||
</Stepper.ActionRow>
|
||||
</ModalDialog.Footer>
|
||||
</ModalDialog>
|
||||
</Stepper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
7
src/skills-builder/skills-builder-modal/ViewResults.jsx
Normal file
7
src/skills-builder/skills-builder-modal/ViewResults.jsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
const ViewResults = () => (
|
||||
<h3>Results will render on this step</h3>
|
||||
);
|
||||
|
||||
export default ViewResults;
|
||||
@@ -12,6 +12,11 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Next Step',
|
||||
description: 'Button that sends the user to the next step in the skills builder.',
|
||||
},
|
||||
exitButton: {
|
||||
id: 'exit.button',
|
||||
defaultMessage: 'Exit',
|
||||
description: 'Button that exits the Skills Builder.',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
Reference in New Issue
Block a user