Skip to content

Commit

Permalink
Merge pull request #3 from edx/mfrank/port-over-Skills-Builder
Browse files Browse the repository at this point in the history
feat: port over Skills Builder
  • Loading branch information
MaxFrank13 committed May 31, 2023
2 parents de8ff6d + c2eb537 commit 9b1d19e
Show file tree
Hide file tree
Showing 53 changed files with 2,418 additions and 32 deletions.
5 changes: 5 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ SITE_NAME=localhost
USER_INFO_COOKIE_NAME='edx-user-info'
APP_ID=''
MFE_CONFIG_API_URL=''
SEARCH_CATALOG_URL='http://localhost:18000/courses'
ALGOLIA_APP_ID=''
ALGOLIA_JOBS_INDEX_NAME=''
ALGOLIA_PRODUCT_INDEX_NAME=''
ALGOLIA_SEARCH_API_KEY=''
2 changes: 1 addition & 1 deletion catalog-info.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: "frontend-app-skills"
description: "A template for Open edX micro-frontend applications."
description: "Micro-frontend for skills related content. Contains views for the B2C Skills Builder."
links:
- url: "https://github.com/openedx/frontend-app-skills/blob/master/README.rst"
title: "README"
Expand Down
2 changes: 1 addition & 1 deletion module.config.js.example
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ module.exports = {
// { moduleName: '@edx/paragon', dir: '../paragon', dist: 'dist' },
// { moduleName: '@edx/frontend-platform', dir: '../frontend-platform', dist: 'dist' },
],
};
};
495 changes: 493 additions & 2 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@edx/frontend-app-skills",
"version": "0.1.0",
"description": "Frontend application template",
"description": "Micro-frontend for skills related content",
"repository": {
"type": "git",
"url": "git+https://github.com/openedx/frontend-app-skills.git"
Expand Down Expand Up @@ -43,10 +43,12 @@
"@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "0.2.0",
"algoliasearch": "4.17.0",
"core-js": "3.27.2",
"prop-types": "15.8.1",
"react": "16.14.0",
"react-dom": "16.14.0",
"react-instantsearch-hooks-web": "^6.40.1",
"react-redux": "7.2.9",
"react-router": "5.3.4",
"react-router-dom": "5.3.4",
Expand All @@ -57,6 +59,7 @@
"@edx/browserslist-config": "^1.1.1",
"@edx/frontend-build": "12.8.27",
"@edx/reactifex": "^2.1.1",
"@testing-library/react": "11.2.7",
"glob": "7.2.3",
"husky": "7.0.4",
"jest": "29.5.0"
Expand Down
12 changes: 0 additions & 12 deletions src/example/ExamplePage.jsx

This file was deleted.

5 changes: 0 additions & 5 deletions src/example/ExamplePage.test.jsx

This file was deleted.

Empty file removed src/example/data/.gitkeep
Empty file.
4 changes: 0 additions & 4 deletions src/example/data/README.rst

This file was deleted.

Empty file removed src/example/index.scss
Empty file.
19 changes: 15 additions & 4 deletions src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@ import 'core-js/stable';
import 'regenerator-runtime/runtime';

import {
APP_INIT_ERROR, APP_READY, subscribe, initialize,
APP_INIT_ERROR, APP_READY, subscribe, initialize, mergeConfig,
} from '@edx/frontend-platform';
import { AppProvider, ErrorPage } from '@edx/frontend-platform/react';
import { AppProvider, ErrorPage, PageRoute } from '@edx/frontend-platform/react';
import ReactDOM from 'react-dom';

import Header from '@edx/frontend-component-header';
import Footer from '@edx/frontend-component-footer';
import { SkillsBuilder } from './skills-builder';
import messages from './i18n';
import ExamplePage from './example/ExamplePage';

import './index.scss';

subscribe(APP_READY, () => {
ReactDOM.render(
<AppProvider>
<Header />
<ExamplePage />
<PageRoute path="/" component={SkillsBuilder} />
<Footer />
</AppProvider>,
document.getElementById('root'),
Expand All @@ -31,4 +31,15 @@ subscribe(APP_INIT_ERROR, (error) => {

initialize({
messages,
handlers: {
config: () => {
mergeConfig({
ALGOLIA_APP_ID: process.env.ALGOLIA_APP_ID || null,
ALGOLIA_JOBS_INDEX_NAME: process.env.ALGOLIA_JOBS_INDEX_NAME || null,
ALGOLIA_PRODUCT_INDEX_NAME: process.env.ALGOLIA_PRODUCT_INDEX_NAME || null,
ALGOLIA_SEARCH_API_KEY: process.env.ALGOLIA_SEARCH_API_KEY || null,
MARKETING_SITE_SEARCH_URL: process.env.SEARCH_CATALOG_URL || null,
}, 'App loadConfig override handler');
},
},
});
5 changes: 3 additions & 2 deletions src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
@import "@edx/paragon/scss/core/core.scss";
@import "@edx/brand/paragon/overrides.scss";

@import './example/index.scss';

@import "~@edx/frontend-component-header/dist/index";
@import "~@edx/frontend-component-footer/dist/footer";

@import './skills-builder/skills-builder-modal/skillsBuilderModal.scss';
@import './skills-builder/skills-builder-header/skillsBuilderHeader.scss';
11 changes: 11 additions & 0 deletions src/skills-builder/SkillsBuilder.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';
import { SkillsBuilderModal } from './skills-builder-modal';
import { SkillsBuilderProvider } from './skills-builder-context';

const SkillsBuilder = () => (
<SkillsBuilderProvider>
<SkillsBuilderModal />
</SkillsBuilderProvider>
);

export default SkillsBuilder;
26 changes: 26 additions & 0 deletions src/skills-builder/data/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
SET_GOAL,
SET_CURRENT_JOB_TITLE,
ADD_CAREER_INTEREST,
REMOVE_CAREER_INTEREST,
} from './constants';

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_INTEREST,
payload,
});
9 changes: 9 additions & 0 deletions src/skills-builder/data/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Actions for Skills Context
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_INTEREST = 'REMOVE_CAREER_INTEREST';

// Stepper keys
export const STEP1 = 'select-your-preferences';
export const STEP2 = 'review-your-results';
41 changes: 41 additions & 0 deletions src/skills-builder/data/reducer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {
SET_GOAL,
SET_CURRENT_JOB_TITLE,
ADD_CAREER_INTEREST,
REMOVE_CAREER_INTEREST,
} from './constants';

export function skillsReducer(state, action) {
switch (action.type) {
case SET_GOAL:
return {
...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_INTEREST:
return {
...state,
careerInterests: state.careerInterests.filter(interest => interest !== action.payload),
};
default:
return state;
}
}

export const skillsInitialState = {
currentGoal: '',
currentJobTitle: '',
careerInterests: [],
};

export default skillsReducer;
60 changes: 60 additions & 0 deletions src/skills-builder/data/test/reducer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { skillsReducer, skillsInitialState } from '../reducer';
import {
SET_GOAL,
SET_CURRENT_JOB_TITLE,
ADD_CAREER_INTEREST,
REMOVE_CAREER_INTEREST,
} from '../constants';

describe('skillsReducer', () => {
const testState = skillsInitialState;
beforeEach(() => jest.resetModules());

it('does not remove present data when SET_GOAL action is dispatched', () => {
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_INTEREST, 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);
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/skills-builder/images/edX-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/skills-builder/images/headerImage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/skills-builder/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line import/prefer-default-export
export { default as SkillsBuilder } from './SkillsBuilder';
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, { createContext, useReducer, useMemo } from 'react';
import PropTypes from 'prop-types';
import reducer, { skillsInitialState } from '../data/reducer';
import { useAlgoliaSearch } from '../utils/search';

export const SkillsBuilderContext = createContext();

export const SkillsBuilderProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, skillsInitialState);

const [searchClient, productSearchIndex, jobSearchIndex] = useAlgoliaSearch();

const value = useMemo(() => ({
state,
dispatch,
algolia: {
searchClient,
productSearchIndex,
jobSearchIndex,
},
}), [state, searchClient, productSearchIndex, jobSearchIndex]);

return (
<SkillsBuilderContext.Provider value={value}>
{children}
</SkillsBuilderContext.Provider>
);
};

SkillsBuilderProvider.propTypes = {
children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired,
};
2 changes: 2 additions & 0 deletions src/skills-builder/skills-builder-context/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line import/prefer-default-export
export { SkillsBuilderProvider, SkillsBuilderContext } from './SkillsBuilderProvider';
25 changes: 25 additions & 0 deletions src/skills-builder/skills-builder-header/SkillsBuilderHeader.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import edXLogo from '../images/edX-logo.svg';
import messages from './messages';

const SkillsBuilderHeader = () => {
const { formatMessage } = useIntl();

return (
<div className="d-flex">
<img src={edXLogo} alt="edx-logo" className="mt-2 h-50" />
<div className="ml-5 vertical-line" />
<div className="w-100 ml-5">
<h1 className="h1 text-warning-300">
{formatMessage(messages.skillsBuilderHeaderTitle)}
</h1>
<p className="h2 text-white">
{formatMessage(messages.skillsBuilderHeaderSubheading)}
</p>
</div>
</div>
);
};

export default SkillsBuilderHeader;
2 changes: 2 additions & 0 deletions src/skills-builder/skills-builder-header/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line import/prefer-default-export
export { default as SkillsBuilderHeader } from './SkillsBuilderHeader';
16 changes: 16 additions & 0 deletions src/skills-builder/skills-builder-header/messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { defineMessages } from '@edx/frontend-platform/i18n';

const messages = defineMessages({
skillsBuilderHeaderTitle: {
id: 'skills.builder.header.title',
defaultMessage: 'Skills Builder',
description: 'Title for the Skills Builder feature',
},
skillsBuilderHeaderSubheading: {
id: 'skills.builder.header.subheading',
defaultMessage: 'Let edX be your guide',
description: 'Subheading to the Skills Builder title in the header component',
},
});

export default messages;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.vertical-line {
border-left: 7px solid #D23228;
transform: rotate(13deg);
}
Loading

0 comments on commit 9b1d19e

Please sign in to comment.