├── .eslintignore ├── src ├── app.scss ├── pages │ ├── student │ │ ├── training │ │ │ ├── exercise │ │ │ │ ├── delivery │ │ │ │ │ ├── styles.scss │ │ │ │ │ ├── spec │ │ │ │ │ │ └── page.spec.tsx │ │ │ │ │ ├── components │ │ │ │ │ │ └── navigation.tsx │ │ │ │ │ └── page.tsx │ │ │ │ └── assessment │ │ │ │ │ ├── styles.scss │ │ │ │ │ └── components │ │ │ │ │ └── navigation.tsx │ │ │ ├── list │ │ │ │ ├── components │ │ │ │ │ ├── trainingRowStyles.scss │ │ │ │ │ ├── trainingTableStyles.scss │ │ │ │ │ ├── navigation.tsx │ │ │ │ │ └── trainingRow.tsx │ │ │ │ ├── pageContainer.tsx │ │ │ │ ├── actions │ │ │ │ │ └── summaryTrainingListRequest.ts │ │ │ │ └── page.tsx │ │ │ └── toc │ │ │ │ ├── pageContainer.ts │ │ │ │ ├── actions │ │ │ │ └── fetchTrainingToc.ts │ │ │ │ ├── components │ │ │ │ └── navigation.tsx │ │ │ │ └── page.tsx │ │ └── index.tsx │ ├── general │ │ ├── notFound │ │ │ ├── styles.scss │ │ │ ├── components │ │ │ │ ├── headerStyles.scss │ │ │ │ ├── header.tsx │ │ │ │ ├── body.tsx │ │ │ │ └── spec │ │ │ │ │ ├── header.spec.tsx │ │ │ │ │ └── body.spec.tsx │ │ │ └── page.tsx │ │ ├── login │ │ │ ├── components │ │ │ │ └── loginForm │ │ │ │ │ ├── loginForm.styles.scss │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── components │ │ │ │ │ ├── header.tsx │ │ │ │ │ └── spec │ │ │ │ │ │ └── header.spec.tsx │ │ │ │ │ ├── login.validation.ts │ │ │ │ │ ├── loginFormContainer.tsx │ │ │ │ │ └── loginForm.tsx │ │ │ ├── page.tsx │ │ │ ├── spec │ │ │ │ └── page.spec.tsx │ │ │ └── actions │ │ │ │ └── loginContentChanged.ts │ │ ├── index.tsx │ │ └── routes.tsx │ ├── admin │ │ ├── student │ │ │ ├── edit │ │ │ │ ├── index.ts │ │ │ │ ├── pageContainer.tsx │ │ │ │ ├── actions │ │ │ │ │ └── summaryStudentRequest.ts │ │ │ │ └── page.tsx │ │ │ └── list │ │ │ │ ├── components │ │ │ │ ├── studentRowStyles.scss │ │ │ │ ├── studentTableStyles.scss │ │ │ │ └── studentRow.tsx │ │ │ │ ├── pageContainer.tsx │ │ │ │ ├── actions │ │ │ │ └── summaryStudentListRequest.ts │ │ │ │ └── page.tsx │ │ ├── index.ts │ │ ├── dashboard │ │ │ ├── pageStyles.scss │ │ │ └── page.tsx │ │ ├── training │ │ │ ├── list │ │ │ │ ├── components │ │ │ │ │ ├── trainingRowStyles.scss │ │ │ │ │ ├── trainingTableStyles.scss │ │ │ │ │ ├── trainingRow.tsx │ │ │ │ │ └── trainingTable.tsx │ │ │ │ ├── pageContainer.tsx │ │ │ │ ├── actions │ │ │ │ │ └── summaryTrainingListRequest.ts │ │ │ │ └── page.tsx │ │ │ └── edit │ │ │ │ └── page.tsx │ │ └── routes.tsx │ └── trainer │ │ ├── evaluation │ │ ├── components │ │ │ ├── evaluationForm.scss │ │ │ ├── studentDeliveryList.scss │ │ │ ├── studentDeliveryListRow.scss │ │ │ ├── studentDeliveryList.tsx │ │ │ └── navigation.tsx │ │ └── page.tsx │ │ ├── training │ │ ├── edit │ │ │ ├── page.scss │ │ │ ├── components │ │ │ │ ├── toolbar │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── icons │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── icon.tsx │ │ │ │ │ │ ├── iconEnums.ts │ │ │ │ │ │ └── spec │ │ │ │ │ │ │ ├── iconEnums.spec.ts │ │ │ │ │ │ │ └── icon.spec.tsx │ │ │ │ │ ├── buttons │ │ │ │ │ │ ├── buttonStyles.scss │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── toolbarButton.tsx │ │ │ │ │ │ ├── toolbarLabeledButton.tsx │ │ │ │ │ │ ├── toolbarMarkdownButton.tsx │ │ │ │ │ │ └── toolbarLabeledMarkdownButton.tsx │ │ │ │ │ ├── groups │ │ │ │ │ │ ├── groupStyles.scss │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── listGroupComponent.tsx │ │ │ │ │ │ ├── fontGroupComponent.tsx │ │ │ │ │ │ └── linksGroupComponent.tsx │ │ │ │ │ ├── toolbarStyles.scss │ │ │ │ │ └── toolbar.tsx │ │ │ │ ├── textEditor │ │ │ │ │ └── index.tsx │ │ │ │ ├── panels │ │ │ │ │ ├── delivery │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── styles.scss │ │ │ │ │ │ ├── deliveryPanelComponent.tsx │ │ │ │ │ │ └── deliveryPanelContainer.ts │ │ │ │ │ ├── upload │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── uploadFilePanelComponent.tsx │ │ │ │ │ │ └── uploadFilePanelContainer.ts │ │ │ │ │ ├── evaluation │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── evaluationPanelContainer.ts │ │ │ │ │ │ └── evaluationPanelComponent.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ └── definitions.ts │ │ │ │ ├── markdownEntryConstants.ts │ │ │ │ ├── editorStyles.scss │ │ │ │ ├── navigation │ │ │ │ │ └── navigation.tsx │ │ │ │ ├── editorContainer.tsx │ │ │ │ └── spec │ │ │ │ │ └── markdownEntryContants.spec.ts │ │ │ ├── actions │ │ │ │ ├── toggleEditorPreview.ts │ │ │ │ ├── setActivePanel.ts │ │ │ │ ├── trainingContentChanged.ts │ │ │ │ ├── updateEditorCursor.ts │ │ │ │ ├── spec │ │ │ │ │ ├── toggleEditorPreview.spec.ts │ │ │ │ │ ├── setActivePanel.spec.ts │ │ │ │ │ ├── updateEditorCursor.spec.ts │ │ │ │ │ └── trainingContentChanged.spec.ts │ │ │ │ └── fetchTrainingContent.ts │ │ │ ├── pageContainer.tsx │ │ │ └── page.tsx │ │ └── list │ │ │ ├── spec │ │ │ ├── page.spec.tsx │ │ │ └── trainingListProvider.spec.tsx │ │ │ ├── components │ │ │ ├── spec │ │ │ │ ├── trainingRow.spec.tsx │ │ │ │ └── trainingTable.spec.tsx │ │ │ ├── trainingRowStyles.scss │ │ │ ├── trainingTableStyles.scss │ │ │ ├── navigation.tsx │ │ │ └── trainingRow.tsx │ │ │ ├── pageContainer.tsx │ │ │ ├── trainingListProvider.tsx │ │ │ ├── actions │ │ │ └── trainingActions.ts │ │ │ └── page.tsx │ │ ├── dashboard │ │ ├── pageStyles.scss │ │ ├── page.tsx │ │ ├── components │ │ │ └── navigation.tsx │ │ └── spec │ │ │ └── page.spec.tsx │ │ ├── index.tsx │ │ └── routes.tsx ├── content │ ├── sass │ │ ├── colors.scss │ │ ├── components │ │ │ └── navigation.scss │ │ ├── common.scss │ │ ├── styles.scss │ │ ├── animations │ │ │ └── cross-fade.scss │ │ └── html-elements │ │ │ └── progress.scss │ └── image │ │ ├── logo.png │ │ └── user.svg ├── rest-api │ ├── model │ │ └── general │ │ │ ├── index.ts │ │ │ └── user.ts │ ├── mappers │ │ └── general │ │ │ ├── index.ts │ │ │ └── mappers.ts │ ├── config.ts │ ├── index.ts │ ├── login │ │ ├── loginAPI.contract.ts │ │ ├── index.ts │ │ ├── loginAPI.double.ts │ │ ├── loginMockData.ts │ │ └── loginAPI.real.ts │ ├── helpers.ts │ ├── trainer │ │ └── trainerApi.ts │ ├── student │ │ ├── studentApi.ts │ │ └── spec │ │ │ └── studentApi.spec.ts │ ├── trainingMockData.ts │ ├── student.ts │ └── training.ts ├── common │ ├── validations │ │ ├── validationEnums.ts │ │ └── spec │ │ │ └── validationEnums.spec.ts │ ├── components │ │ ├── breadcrumb │ │ │ ├── index.ts │ │ │ ├── breadcrumb.scss │ │ │ ├── breadcrumb.tsx │ │ │ └── breadcrumbItem.tsx │ │ ├── panel │ │ │ ├── index.tsx │ │ │ ├── panel.tsx │ │ │ └── spec │ │ │ │ └── panel.spec.tsx │ │ ├── markdownViewer │ │ │ ├── syncScroll │ │ │ │ └── index.ts │ │ │ ├── index.tsx │ │ │ ├── render │ │ │ │ ├── index.ts │ │ │ │ └── markdownRenderOptions.ts │ │ │ ├── spec │ │ │ │ └── markdownViewerComponent.spec.tsx │ │ │ └── markdownViewerComponent.tsx │ │ ├── dashboard │ │ │ ├── dashboardStyles.scss │ │ │ ├── icons │ │ │ │ ├── index.ts │ │ │ │ └── spec │ │ │ │ │ └── index.spec.ts │ │ │ ├── index.ts │ │ │ ├── components │ │ │ │ ├── dashboardItemStyles.scss │ │ │ │ └── dashboardItem.tsx │ │ │ └── dashboard.tsx │ │ ├── form │ │ │ ├── index.ts │ │ │ ├── droppableFile │ │ │ │ └── droppableFile.scss │ │ │ ├── validation.tsx │ │ │ ├── select.tsx │ │ │ ├── textarea.tsx │ │ │ ├── input.tsx │ │ │ └── specs │ │ │ │ └── validation.spec.tsx │ │ ├── index.ts │ │ ├── progressBar │ │ │ ├── progressBarComponent.tsx │ │ │ ├── progressBarStyles.scss │ │ │ └── spec │ │ │ │ └── progressBar.spec.tsx │ │ ├── counterButton │ │ │ ├── counterButton.scss │ │ │ └── counterButton.tsx │ │ ├── header │ │ │ ├── header.scss │ │ │ └── header.tsx │ │ ├── footer │ │ │ ├── footer.scss │ │ │ └── footer.tsx │ │ ├── virtualized │ │ │ ├── tableRow.tsx │ │ │ └── tableCell.tsx │ │ └── subscriptionManager │ │ │ └── subscriptionManager.tsx │ ├── helper │ │ ├── percentage │ │ │ ├── getSizeByPercentage.ts │ │ │ └── spec │ │ │ │ └── getSizeByPercentage.spec.ts │ │ └── navigationHelper │ │ │ ├── index.ts │ │ │ └── spec │ │ │ └── navigationHelper.spec.ts │ ├── actionEnums │ │ ├── login │ │ │ ├── index.ts │ │ │ └── spec │ │ │ │ └── index.spec.ts │ │ ├── student │ │ │ ├── index.ts │ │ │ └── spec │ │ │ │ └── index.spec.ts │ │ ├── admin.ts │ │ ├── trainer │ │ │ ├── index.ts │ │ │ └── spec │ │ │ │ └── index.spec.ts │ │ └── spec │ │ │ └── admin.spec.ts │ ├── parse │ │ └── multilineTrim.ts │ └── routeEnums │ │ ├── student │ │ ├── index.ts │ │ └── spec │ │ │ └── index.spec.ts │ │ ├── admin │ │ ├── index.ts │ │ └── spec │ │ │ └── index.spec.ts │ │ └── trainer │ │ ├── index.ts │ │ └── spec │ │ └── index.spec.ts ├── model │ ├── trainer.ts │ ├── trainer │ │ ├── markdownEntry.ts │ │ ├── exercise.ts │ │ ├── editTrainingSummary.ts │ │ ├── deliveryEvaluation.ts │ │ └── spec │ │ │ └── editTrainingSummary.spec.ts │ ├── login │ │ ├── loginErrors.ts │ │ ├── loginCredentials.ts │ │ ├── loginResponse.ts │ │ └── spec │ │ │ ├── loginErrors.spec.ts │ │ │ ├── loginCredentials.spec.ts │ │ │ └── loginResponse.spec.ts │ ├── student │ │ ├── trainingToc.ts │ │ └── spec │ │ │ └── trainingToc.spec.ts │ ├── trainingSummary.ts │ ├── studentSummary.ts │ ├── userProfile.ts │ ├── student.ts │ ├── training.ts │ └── spec │ │ ├── studentSummary.spec.ts │ │ ├── trainingSummary.spec.ts │ │ ├── student.spec.ts │ │ ├── userProfile.spec.ts │ │ └── training.spec.ts ├── index.html ├── appContainer.ts ├── reducers │ ├── student │ │ ├── index.ts │ │ └── training │ │ │ ├── index.ts │ │ │ ├── toc │ │ │ └── trainingTOC.ts │ │ │ └── list │ │ │ └── trainingList.ts │ ├── trainer │ │ └── index.ts │ ├── adminTraining.ts │ ├── index.ts │ └── adminStudent.ts ├── store │ └── index.ts ├── history.ts ├── routes.tsx ├── index.tsx └── app.tsx ├── Procfile ├── .babelrc ├── env.config.js ├── .gitignore ├── server └── index.js ├── tsconfig.json ├── config ├── karma │ ├── karma.config.base.js │ ├── spec.bundle.js │ ├── karma.tsconfig.json │ ├── karma.config.test.js │ └── karma.config.coverage.js ├── helpers.js └── webpack │ ├── test │ ├── webpack.config.test.js │ ├── webpack.config.base.js │ └── webpack.config.coverage.js │ ├── app │ ├── webpack.config.prod.js │ └── webpack.config.dev.js │ └── webpack.config.common.js ├── .vscode └── settings.json ├── .travis.yml ├── LICENSE └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | /config/** 2 | -------------------------------------------------------------------------------- /src/app.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/student/training/exercise/delivery/styles.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/content/sass/colors.scss: -------------------------------------------------------------------------------- 1 | $dangerHeading: mistyrose; 2 | -------------------------------------------------------------------------------- /src/rest-api/model/general/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user'; 2 | -------------------------------------------------------------------------------- /src/rest-api/mappers/general/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mappers'; 2 | -------------------------------------------------------------------------------- /src/common/validations/validationEnums.ts: -------------------------------------------------------------------------------- 1 | export const validationsEnums = {}; 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: if-env NODE_ENV=production && npm run start:prod || npm run start:dev 2 | -------------------------------------------------------------------------------- /src/pages/general/notFound/styles.scss: -------------------------------------------------------------------------------- 1 | .pageError404 { 2 | margin-top: 6em; 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/student/training/exercise/delivery/spec/page.spec.tsx: -------------------------------------------------------------------------------- 1 | // TODO: write tests 2 | -------------------------------------------------------------------------------- /src/pages/admin/student/edit/index.ts: -------------------------------------------------------------------------------- 1 | export { EditStudentPageContainer } from './pageContainer'; 2 | -------------------------------------------------------------------------------- /src/content/image/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lemoncode/LeanMood/HEAD/src/content/image/logo.png -------------------------------------------------------------------------------- /src/pages/trainer/evaluation/components/evaluationForm.scss: -------------------------------------------------------------------------------- 1 | .submit-Btn { 2 | padding: 6px 48px; 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/admin/index.ts: -------------------------------------------------------------------------------- 1 | import { AdminRoutes } from './routes'; 2 | 3 | export { 4 | AdminRoutes 5 | } 6 | -------------------------------------------------------------------------------- /src/pages/trainer/evaluation/components/studentDeliveryList.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | margin-top: 30px; 3 | } 4 | -------------------------------------------------------------------------------- /src/rest-api/config.ts: -------------------------------------------------------------------------------- 1 | export const config = { 2 | useRealAPI: (process.env.LM_REST_ENV === 'real'), 3 | }; 4 | -------------------------------------------------------------------------------- /src/content/sass/components/navigation.scss: -------------------------------------------------------------------------------- 1 | .navigation-img { 2 | width: 32px; 3 | padding-right: 5px; 4 | } 5 | -------------------------------------------------------------------------------- /src/pages/general/login/components/loginForm/loginForm.styles.scss: -------------------------------------------------------------------------------- 1 | .form-container { 2 | margin-top: 40px; 3 | } 4 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/page.scss: -------------------------------------------------------------------------------- 1 | .editor { 2 | display: flex; 3 | flex-wrap: wrap; 4 | padding-top: 10px; 5 | } 6 | -------------------------------------------------------------------------------- /src/model/trainer.ts: -------------------------------------------------------------------------------- 1 | export class Trainer { 2 | public id: string; 3 | 4 | constructor() { 5 | this.id = ''; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/pages/admin/dashboard/pageStyles.scss: -------------------------------------------------------------------------------- 1 | .title { 2 | text-align: center; 3 | padding-bottom: 3em; 4 | padding-top: 1em; 5 | } 6 | -------------------------------------------------------------------------------- /src/common/components/breadcrumb/index.ts: -------------------------------------------------------------------------------- 1 | export { Breadcrumb } from './breadcrumb'; 2 | export { BreadcrumbItem } from './breadcrumbItem'; 3 | -------------------------------------------------------------------------------- /src/pages/trainer/dashboard/pageStyles.scss: -------------------------------------------------------------------------------- 1 | .title { 2 | text-align: center; 3 | padding-bottom: 3em; 4 | padding-top: 1em; 5 | } 6 | -------------------------------------------------------------------------------- /src/pages/trainer/training/list/spec/page.spec.tsx: -------------------------------------------------------------------------------- 1 | describe('Training Module: TrainingListPage', () => { 2 | xit('write tests'); 3 | }); 4 | -------------------------------------------------------------------------------- /src/common/helper/percentage/getSizeByPercentage.ts: -------------------------------------------------------------------------------- 1 | export const getSizeByPercentage = (initialSize, percentage) => percentage * initialSize / 100; 2 | -------------------------------------------------------------------------------- /src/common/components/panel/index.tsx: -------------------------------------------------------------------------------- 1 | import { PanelComponent, PanelItem } from './panel'; 2 | 3 | export { 4 | PanelComponent, 5 | PanelItem, 6 | }; 7 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/toolbar/index.ts: -------------------------------------------------------------------------------- 1 | import {ToolbarComponent} from './toolbar'; 2 | 3 | export { 4 | ToolbarComponent, 5 | }; 6 | -------------------------------------------------------------------------------- /src/model/trainer/markdownEntry.ts: -------------------------------------------------------------------------------- 1 | export interface IMarkdownEntry { 2 | mdCaret: string; 3 | caretCursorPosition: number; 4 | panelId?: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/content/sass/common.scss: -------------------------------------------------------------------------------- 1 | @mixin resetDefaultAppearance { 2 | -webkit-appearance: none; 3 | -moz-appearance: none; 4 | appearance: none; 5 | } 6 | -------------------------------------------------------------------------------- /src/pages/trainer/training/list/components/spec/trainingRow.spec.tsx: -------------------------------------------------------------------------------- 1 | describe('Training Module: TrainingRowComponent', () => { 2 | xit('write tests'); 3 | }); 4 | -------------------------------------------------------------------------------- /src/pages/trainer/training/list/components/spec/trainingTable.spec.tsx: -------------------------------------------------------------------------------- 1 | describe('Training Module: TrainingTableComponent', () => { 2 | xit('write tests'); 3 | }); 4 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/textEditor/index.tsx: -------------------------------------------------------------------------------- 1 | import { TextEditorComponent } from './textEditor'; 2 | 3 | export { 4 | TextEditorComponent 5 | }; 6 | -------------------------------------------------------------------------------- /src/pages/general/notFound/components/headerStyles.scss: -------------------------------------------------------------------------------- 1 | @import "~globalStyles/colors.scss"; 2 | 3 | .dangerHeading { 4 | background-color: $dangerHeading!important; 5 | } 6 | -------------------------------------------------------------------------------- /src/pages/general/login/components/loginForm/index.ts: -------------------------------------------------------------------------------- 1 | import {LoginFormContainerComponent} from './loginFormContainer'; 2 | 3 | export { 4 | LoginFormContainerComponent 5 | }; 6 | -------------------------------------------------------------------------------- /src/common/components/breadcrumb/breadcrumb.scss: -------------------------------------------------------------------------------- 1 | .without-separator { 2 | &:before { 3 | content: none !important; 4 | } 5 | } 6 | 7 | .right-item { 8 | float: right; 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/toolbar/icons/index.ts: -------------------------------------------------------------------------------- 1 | import {Icon} from './icon'; 2 | import {iconEnums} from './iconEnums'; 3 | 4 | export { 5 | Icon, 6 | iconEnums, 7 | } 8 | -------------------------------------------------------------------------------- /src/common/components/markdownViewer/syncScroll/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | SOURCE_LINE_CLASSNAME, 3 | SOURCE_LINE_ATTRIBUTE, 4 | mapLineToOffset, 5 | mapOffsetToLine, 6 | } from './synchronizeScroll'; 7 | -------------------------------------------------------------------------------- /src/model/trainer/exercise.ts: -------------------------------------------------------------------------------- 1 | export class Exercise { 2 | public id: number; 3 | public name: string; 4 | 5 | constructor() { 6 | this.id = NaN; 7 | this.name = ''; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false 7 | } 8 | ] 9 | ], 10 | "plugins": [ 11 | "react-hot-loader/babel" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /src/common/components/markdownViewer/index.tsx: -------------------------------------------------------------------------------- 1 | export { MarkDownViewerComponent } from './markdownViewerComponent'; 2 | export { mapOffsetToLine, mapLineToOffset, SOURCE_LINE_CLASSNAME } from './syncScroll'; 3 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/panels/delivery/index.ts: -------------------------------------------------------------------------------- 1 | export { DeliveryPanelComponent } from './deliveryPanelComponent'; 2 | export { DeliveryPanelContainer } from './deliveryPanelContainer'; 3 | -------------------------------------------------------------------------------- /src/common/components/dashboard/dashboardStyles.scss: -------------------------------------------------------------------------------- 1 | .dashboard { 2 | display: flex; 3 | flex-direction: column; 4 | } 5 | 6 | .dashboardItems { 7 | display: flex; 8 | flex-flow: row wrap; 9 | } 10 | -------------------------------------------------------------------------------- /src/model/login/loginErrors.ts: -------------------------------------------------------------------------------- 1 | import {FieldValidationResult} from 'lc-form-validation'; 2 | 3 | export interface ILoginErrors { 4 | login: FieldValidationResult; 5 | password: FieldValidationResult; 6 | }; 7 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/panels/upload/index.ts: -------------------------------------------------------------------------------- 1 | export { UploadFilePanelComponent } from './uploadFilePanelComponent'; 2 | export { UploadFilePanelContainer } from './uploadFilePanelContainer'; 3 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/panels/evaluation/index.ts: -------------------------------------------------------------------------------- 1 | export { EvaluationPanelContainer } from './evaluationPanelContainer'; 2 | export { EvaluationPanelComponent } from './evaluationPanelComponent'; 3 | -------------------------------------------------------------------------------- /src/pages/admin/student/list/components/studentRowStyles.scss: -------------------------------------------------------------------------------- 1 | .row-striped:nth-child(odd) { 2 | background-color: #f9f9f9; 3 | } 4 | 5 | .btn-group { 6 | a, button { 7 | margin-right: 5px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/admin/student/list/components/studentTableStyles.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | text-transform: none; 3 | } 4 | 5 | .row { 6 | border-bottom: 2px solid #ddd; 7 | } 8 | 9 | .table { 10 | outline: none; 11 | } 12 | -------------------------------------------------------------------------------- /src/pages/admin/training/list/components/trainingRowStyles.scss: -------------------------------------------------------------------------------- 1 | .row-striped:nth-child(odd) { 2 | background-color: #f9f9f9; 3 | } 4 | 5 | .btn-group { 6 | a, button { 7 | margin-right: 5px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/admin/training/list/components/trainingTableStyles.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | text-transform: none; 3 | } 4 | 5 | .row { 6 | border-bottom: 2px solid #ddd; 7 | } 8 | 9 | .table { 10 | outline: none; 11 | } 12 | -------------------------------------------------------------------------------- /src/pages/student/training/list/components/trainingRowStyles.scss: -------------------------------------------------------------------------------- 1 | .row-striped:nth-child(odd) { 2 | background-color: #f9f9f9; 3 | } 4 | 5 | .btn-group { 6 | a, button { 7 | margin-right: 5px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/student/training/list/components/trainingTableStyles.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | text-transform: none; 3 | } 4 | 5 | .row { 6 | border-bottom: 2px solid #ddd; 7 | } 8 | 9 | .table { 10 | outline: none; 11 | } 12 | -------------------------------------------------------------------------------- /src/pages/trainer/training/list/components/trainingRowStyles.scss: -------------------------------------------------------------------------------- 1 | .row-striped:nth-child(odd) { 2 | background-color: #f9f9f9; 3 | } 4 | 5 | .btn-group { 6 | a, button { 7 | margin-right: 5px; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/trainer/training/list/components/trainingTableStyles.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | text-transform: none; 3 | } 4 | 5 | .row { 6 | border-bottom: 2px solid #ddd; 7 | } 8 | 9 | .table { 10 | outline: none; 11 | } 12 | -------------------------------------------------------------------------------- /src/model/login/loginCredentials.ts: -------------------------------------------------------------------------------- 1 | export class LoginCredentials { 2 | public login: string; 3 | public password: string; 4 | 5 | constructor() { 6 | this.login = ''; 7 | this.password = ''; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/model/trainer/editTrainingSummary.ts: -------------------------------------------------------------------------------- 1 | export class EditTrainingSummary { 2 | public id: number; 3 | public content: string; 4 | 5 | constructor() { 6 | this.id = -1; 7 | this.content = ''; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/student/training/exercise/assessment/styles.scss: -------------------------------------------------------------------------------- 1 | .comments-summary { 2 | margin-top: 3em; 3 | } 4 | 5 | .comment { 6 | margin-top: 2em; 7 | } 8 | 9 | .assessmentPercentage { 10 | margin-top: 40px; 11 | } 12 | -------------------------------------------------------------------------------- /src/common/actionEnums/login/index.ts: -------------------------------------------------------------------------------- 1 | export const loginActionEnums = { 2 | LOGIN_CONTENT_CHANGED: 'LOGIN_CONTENT_CHANGED', 3 | LOGIN_REQUEST_SUCCESS: 'LOGIN_REQUEST_SUCCESS', 4 | LOGIN_REQUEST_ERROR: 'LOGIN_REQUEST_ERROR', 5 | }; 6 | -------------------------------------------------------------------------------- /src/common/components/dashboard/icons/index.ts: -------------------------------------------------------------------------------- 1 | export const dashboardIcons = { 2 | students: 'fa fa-users fa-5x', 3 | trainings: 'fa fa-files-o fa-5x', 4 | evaluation: 'fa fa-star-half-o fa-5x', 5 | training: 'fa fa-file-o fa-5x', 6 | }; 7 | -------------------------------------------------------------------------------- /src/rest-api/index.ts: -------------------------------------------------------------------------------- 1 | import { studentApi } from './student'; 2 | import { trainingApi } from './training'; 3 | import {trainerApi} from './trainer/trainerApi'; 4 | 5 | export { 6 | studentApi, 7 | trainingApi, 8 | trainerApi, 9 | } 10 | -------------------------------------------------------------------------------- /env.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | LM_NODE_ENV: process.env.NODE_ENV || 'development', 3 | LM_PORT: process.env.PORT || 8080, 4 | LM_REST_ENV: process.env.REST_ENV, 5 | BASE_API_URL: process.env.BASE_API_URL || 'http://localhost:5000/api' 6 | } 7 | -------------------------------------------------------------------------------- /src/common/components/markdownViewer/render/index.ts: -------------------------------------------------------------------------------- 1 | export { CreateMarkdownRender, Mdr, MdrFactory, MdrSetup } from './markdownRender'; 2 | export { MdrOptions } from './markdownRenderOptions'; 3 | export { MdrCodeStyle } from './markdownRenderCodeStyle'; 4 | -------------------------------------------------------------------------------- /src/pages/general/index.tsx: -------------------------------------------------------------------------------- 1 | import { LoginPage } from './login/page'; 2 | import { NotFoundPage } from './notFound/page'; 3 | import { GeneralRoutes } from './routes'; 4 | 5 | export { 6 | LoginPage, 7 | NotFoundPage, 8 | GeneralRoutes 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/panels/delivery/styles.scss: -------------------------------------------------------------------------------- 1 | .title { 2 | text-align: left !important; 3 | } 4 | 5 | .btn-container { 6 | float: none; 7 | margin: auto; 8 | } 9 | 10 | .textarea { 11 | resize: vertical; 12 | } 13 | -------------------------------------------------------------------------------- /src/model/student/trainingToc.ts: -------------------------------------------------------------------------------- 1 | export class TrainingTOC { 2 | public id: string; 3 | public name: string; 4 | public content = ''; 5 | 6 | constructor() { 7 | this.id = ''; 8 | this.name = ''; 9 | this.content = ''; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/actions/toggleEditorPreview.ts: -------------------------------------------------------------------------------- 1 | import {trainerActionEnums} from '../../../../../common/actionEnums/trainer'; 2 | 3 | export const toggleEditorPreviewAction = () => ({ 4 | type: trainerActionEnums.TOGGLE_EDITOR_PREVIEW, 5 | }); 6 | -------------------------------------------------------------------------------- /src/rest-api/login/loginAPI.contract.ts: -------------------------------------------------------------------------------- 1 | import { LoginCredentials } from '../../model/login/loginCredentials'; 2 | import { LoginResponse } from '../../model/login/loginResponse'; 3 | 4 | export type LoginFunction = (loginInfo: LoginCredentials) => Promise; 5 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/actions/setActivePanel.ts: -------------------------------------------------------------------------------- 1 | import {trainerActionEnums} from '../../../../../common/actionEnums/trainer'; 2 | 3 | export const setActivePanelAction = (panel: string) => ({ 4 | type: trainerActionEnums.SET_ACTIVE_PANEL, 5 | payload: panel, 6 | }); 7 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/toolbar/icons/icon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface IProps { 4 | icon: string; 5 | } 6 | 7 | export const Icon = (props: IProps) => { 8 | return ( 9 | 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /src/common/validations/spec/validationEnums.spec.ts: -------------------------------------------------------------------------------- 1 | import {validationsEnums} from '../validationEnums'; 2 | 3 | describe('validationEnums', () => { 4 | it('should be an object', () => { 5 | // Assert 6 | expect(validationsEnums).to.be.an('object').not.null; 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist/ 4 | coverage/ 5 | test/coverage/ 6 | typings/ 7 | *.orig 8 | .idea/ 9 | */src/**/*.js 10 | */src/**/*.js.map 11 | /src/**/*.css.d.ts 12 | /src/**/*.scss.d.ts 13 | yarn.lock 14 | *.log 15 | .awcache/ 16 | public/ 17 | package-lock.json 18 | -------------------------------------------------------------------------------- /src/common/helper/navigationHelper/index.ts: -------------------------------------------------------------------------------- 1 | import { history } from '../../../history'; 2 | 3 | class NavigationHelper { 4 | public navigateToPath = (path: string) => { 5 | history.push(`${path}`); 6 | } 7 | } 8 | 9 | export const navigationHelper = new NavigationHelper(); 10 | -------------------------------------------------------------------------------- /src/content/sass/styles.scss: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | 5 | body { 6 | min-height: 100%; 7 | display: flex; 8 | } 9 | 10 | body > div, [data-reactroot] { 11 | display: flex; 12 | flex: 1; 13 | } 14 | 15 | [data-reactroot] { 16 | flex-direction: column; 17 | } 18 | -------------------------------------------------------------------------------- /src/common/actionEnums/student/index.ts: -------------------------------------------------------------------------------- 1 | const prefix = 'STUDENT_MODULE'; 2 | export const studentActionEnums = { 3 | FETCH_TRAINING_TOC_COMPLETED: `${prefix}/FETCH_TRAINING_TOC_COMPLETED`, 4 | GET_SUMMARY_TRAINING_REQUEST_COMPLETED: `${prefix}/GET_SUMMARY_TRAINING_REQUEST_COMPLETED`, 5 | }; 6 | -------------------------------------------------------------------------------- /src/common/components/dashboard/index.ts: -------------------------------------------------------------------------------- 1 | import {DashboardComponent} from './dashboard'; 2 | import {IDashboardItem} from './components/dashboardItem'; 3 | import {dashboardIcons} from './icons'; 4 | 5 | export { 6 | DashboardComponent, 7 | IDashboardItem, 8 | dashboardIcons, 9 | } 10 | -------------------------------------------------------------------------------- /src/common/components/form/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | InputComponent, 3 | CommonInputProps, 4 | InputComponentProps, 5 | } from './input'; 6 | export { 7 | TextAreaComponent, 8 | TextAreaComponentProps, 9 | } from './textarea'; 10 | export { 11 | SelectComponent, 12 | } from './select'; 13 | -------------------------------------------------------------------------------- /src/pages/general/login/page.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router'; 3 | import { LoginFormContainerComponent } from './components/loginForm'; 4 | 5 | export const LoginPage = () => { 6 | return ( 7 | 8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /src/common/parse/multilineTrim.ts: -------------------------------------------------------------------------------- 1 | export const multilineTrim = (input: string) => { 2 | return input 3 | // Transform new lines into array 4 | .split(/(?:\r\n|\r|\n)/g) 5 | // Remove extra spaces 6 | .map((line) => line.trim()) 7 | // Return a string 8 | .join(''); 9 | }; 10 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/actions/trainingContentChanged.ts: -------------------------------------------------------------------------------- 1 | import {trainerActionEnums} from '../../../../../common/actionEnums/trainer'; 2 | 3 | export const trainingContentChangedAction = (content: string) => ({ 4 | type: trainerActionEnums.TRAINING_CONTENT_CHANGED, 5 | payload: content, 6 | }); 7 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | LeanMood 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /src/model/login/loginResponse.ts: -------------------------------------------------------------------------------- 1 | import { UserProfile } from '../userProfile'; 2 | 3 | export class LoginResponse { 4 | public succeded: boolean; 5 | public userProfile: UserProfile; 6 | 7 | constructor() { 8 | this.succeded = false; 9 | this.userProfile = new UserProfile(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/appContainer.ts: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { App } from './app'; 3 | import { IAppState } from './reducers'; 4 | 5 | const mapStateToProps = (state: IAppState) => ({ 6 | userProfile: state.login.userProfile, 7 | }); 8 | 9 | export const AppContainer = connect(mapStateToProps)(App); 10 | -------------------------------------------------------------------------------- /src/pages/student/index.tsx: -------------------------------------------------------------------------------- 1 | import { TrainingTOCPageContainer } from './training/toc/pageContainer'; 2 | import { StudentsRoutes } from './routes'; 3 | import { TrainingListPage } from './training/list/page'; 4 | 5 | export { 6 | TrainingListPage, 7 | TrainingTOCPageContainer, 8 | StudentsRoutes, 9 | } 10 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/actions/updateEditorCursor.ts: -------------------------------------------------------------------------------- 1 | import {trainerActionEnums} from '../../../../../common/actionEnums/trainer'; 2 | 3 | export const updateEditorCursorAction = (cursorStartPosition: number) => ({ 4 | type: trainerActionEnums.UPDATE_EDITOR_CURSOR, 5 | payload: cursorStartPosition, 6 | }); 7 | -------------------------------------------------------------------------------- /src/rest-api/model/general/user.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | _id: string; 3 | email: string; 4 | name: string; 5 | lastName: string; 6 | avatar: string; 7 | role: string; 8 | trainings: UserTraining[]; 9 | } 10 | 11 | interface UserTraining { 12 | role: string; 13 | trainingId: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/model/trainingSummary.ts: -------------------------------------------------------------------------------- 1 | export class TrainingSummary { 2 | public id: string; 3 | public name: string; 4 | public isActive: boolean; 5 | public start: Date; 6 | public end: Date; 7 | 8 | constructor() { 9 | this.id = '-1'; 10 | this.name = ''; 11 | this.isActive = false; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/reducers/student/index.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import {trainingReducer as training, TrainingState} from './training/'; 3 | 4 | export interface StudentState { 5 | training: TrainingState; 6 | } 7 | 8 | export const studentReducer = combineReducers({ 9 | training, 10 | }); 11 | -------------------------------------------------------------------------------- /src/reducers/trainer/index.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { TrainingState, trainingReducer as training } from './training'; 3 | 4 | export interface TrainerState { 5 | training: TrainingState; 6 | } 7 | 8 | export const trainerReducer = combineReducers({ 9 | training, 10 | }); 11 | -------------------------------------------------------------------------------- /src/rest-api/login/index.ts: -------------------------------------------------------------------------------- 1 | import * as apiDouble from './loginAPI.double'; 2 | import * as apiReal from './loginAPI.real'; 3 | import { LoginFunction } from './loginAPI.contract'; 4 | import { config } from '../config'; 5 | 6 | export const login: LoginFunction = (config.useRealAPI) ? 7 | apiReal.login : 8 | apiDouble.login; 9 | -------------------------------------------------------------------------------- /src/model/studentSummary.ts: -------------------------------------------------------------------------------- 1 | 2 | export class StudentSummary { 3 | public fullname: string; 4 | public email: string; 5 | public id: string; 6 | public isActive: boolean; 7 | 8 | constructor() { 9 | this.id = '-1'; 10 | this.fullname = ''; 11 | this.email = ''; 12 | this.isActive = false; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/toolbar/buttons/buttonStyles.scss: -------------------------------------------------------------------------------- 1 | .column { 2 | display: flex; 3 | flex-direction: row; 4 | } 5 | 6 | .command-label { 7 | margin-left: 0.4rem; 8 | } 9 | 10 | .command-button { 11 | width: 50px; 12 | } 13 | 14 | .label-wrapper { 15 | align-self: center; 16 | width: 100px; 17 | } -------------------------------------------------------------------------------- /src/common/actionEnums/admin.ts: -------------------------------------------------------------------------------- 1 | 2 | export const adminActionEnums = { 3 | GET_SUMMARY_STUDENT_REQUEST_COMPLETED: 'GET_SUMMARY_STUDENT_REQUEST_COMPLETED', 4 | GET_SUMMARY_TRAINING_REQUEST_COMPLETED: 'GET_SUMMARY_TRAINING_REQUEST_COMPLETED', 5 | GET_SUMMARY_STUDENT_BY_ID_REQUEST_COMPLETED: 'GET_SUMMARY_STUDENT_BY_ID_REQUEST_COMPLETED', 6 | }; 7 | -------------------------------------------------------------------------------- /src/common/components/index.ts: -------------------------------------------------------------------------------- 1 | export { MarkDownViewerComponent } from './markdownViewer'; 2 | export { PanelComponent, PanelItem } from './panel'; 3 | export { Breadcrumb, BreadcrumbItem } from './breadcrumb'; 4 | export { CounterButton } from './counterButton/counterButton'; 5 | export { SubscriptionManager } from './subscriptionManager/subscriptionManager'; 6 | -------------------------------------------------------------------------------- /src/common/components/progressBar/progressBarComponent.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface IProps { 4 | current: number; 5 | max: number; 6 | } 7 | 8 | export const ProgressBarComponent = (props: IProps) => { 9 | return ( 10 | 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /src/pages/trainer/evaluation/components/studentDeliveryListRow.scss: -------------------------------------------------------------------------------- 1 | .row { 2 | margin-bottom: 20px; 3 | display: flex; 4 | } 5 | 6 | .col { 7 | display: flex; 8 | flex-direction: column; 9 | align-items: center; 10 | justify-content: center; 11 | flex-grow: 1; 12 | flex-basis: 0; 13 | 14 | img { 15 | width: 128px; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/trainer/index.tsx: -------------------------------------------------------------------------------- 1 | import { DashboardPage } from './dashboard/page'; 2 | import { EvaluationPage } from './evaluation/page'; 3 | import { TrainingRoutes } from './routes'; 4 | import { TrainingListPage } from './training/list/page'; 5 | 6 | export { 7 | DashboardPage, 8 | EvaluationPage, 9 | TrainingListPage, 10 | TrainingRoutes, 11 | } 12 | -------------------------------------------------------------------------------- /src/model/userProfile.ts: -------------------------------------------------------------------------------- 1 | export class UserProfile { 2 | public id: string; 3 | public fullname: string; 4 | public role: string; 5 | public email: string; 6 | public avatar: string; 7 | 8 | constructor() { 9 | this.id = ''; 10 | this.fullname = ''; 11 | this.role = ''; 12 | this.email = ''; 13 | this.avatar = ''; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var env = require('../env.config'); 4 | 5 | var app = express(); 6 | var publicPath = path.resolve(__dirname, '../public'); 7 | 8 | app.use(express.static(publicPath)); 9 | app.listen(env.LM_PORT, function() { 10 | console.log('Server running on port ' + env.LM_PORT); 11 | }); 12 | -------------------------------------------------------------------------------- /src/model/student.ts: -------------------------------------------------------------------------------- 1 | 2 | export class Student { 3 | 4 | public id: string; 5 | public fullname: string; 6 | public email: string; 7 | public phoneNumber: string; 8 | public isActive: boolean; 9 | 10 | constructor() { 11 | this.id = '-1'; 12 | this.fullname = ''; 13 | this.email = ''; 14 | this.phoneNumber = ''; 15 | this.isActive = true; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/general/login/components/loginForm/components/header.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export const HeaderComponent = () => { 4 | return ( 5 |
6 |

7 |

Please sign in

8 |

(login: admin | trainer | student / pwd: test)

9 |

10 |
11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/toolbar/buttons/index.ts: -------------------------------------------------------------------------------- 1 | import { ToolbarMarkdownButton } from './toolbarMarkdownButton'; 2 | import { ToolbarLabeledMarkdownButton } from './toolbarLabeledMarkdownButton'; 3 | import { ToolbarLabeledButton } from './toolbarLabeledButton'; 4 | 5 | export { 6 | ToolbarMarkdownButton, 7 | ToolbarLabeledMarkdownButton, 8 | ToolbarLabeledButton 9 | } 10 | -------------------------------------------------------------------------------- /src/common/components/breadcrumb/breadcrumb.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface Props { 4 | className?: string; 5 | } 6 | 7 | export const Breadcrumb: React.StatelessComponent = ({className, children}) => { 8 | return ( 9 |
    10 | {children} 11 |
12 | ); 13 | }; 14 | 15 | Breadcrumb.displayName = 'Breadcrumb'; 16 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { reducers } from '../reducers'; 2 | import { createStore, compose, applyMiddleware } from 'redux'; 3 | import reduxThunk from 'redux-thunk'; 4 | 5 | export const store = createStore( 6 | reducers, 7 | compose( 8 | applyMiddleware(reduxThunk), 9 | /* tslint:disable-next-line */ 10 | window["devToolsExtension"] ? window["devToolsExtension"]() : (f) => f, 11 | ), 12 | ); 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "es6", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "sourceMap": true, 9 | "jsx": "react", 10 | "noLib": false, 11 | "suppressImplicitAnyIndexErrors": true 12 | }, 13 | "compileOnSave": false, 14 | "exclude": [ 15 | "node_modules" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/model/trainer/deliveryEvaluation.ts: -------------------------------------------------------------------------------- 1 | export interface ExerciseEvaluation { 2 | id: number; 3 | name: string; 4 | studentDelivery: StudentDelivery[]; 5 | } 6 | 7 | export interface StudentDelivery { 8 | id: number; 9 | isDelivered: boolean; 10 | deliveryDate: Date; 11 | link: string; 12 | grade: number; 13 | student: { 14 | id: number; 15 | avatar: string; 16 | fullname: string; 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/toolbar/groups/groupStyles.scss: -------------------------------------------------------------------------------- 1 | .horizontal-container { 2 | display:flex; 3 | flex-direction: row; 4 | } 5 | 6 | .vertical-container { 7 | display:flex; 8 | flex-direction: column; 9 | padding-left: 5px; 10 | } 11 | 12 | .item-font { 13 | padding-right: 5px; 14 | } 15 | 16 | .item-links { 17 | margin-bottom: 5px; 18 | } 19 | 20 | .command-row-up { 21 | margin-bottom: 5px; 22 | } -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/panels/index.ts: -------------------------------------------------------------------------------- 1 | import { panelIds, panelList } from './definitions'; 2 | import { UploadFilePanelComponent } from './upload'; 3 | import { EvaluationPanelContainer } from './evaluation'; 4 | import { DeliveryPanelComponent } from './delivery'; 5 | 6 | export { 7 | panelIds, 8 | panelList, 9 | UploadFilePanelComponent, 10 | EvaluationPanelContainer, 11 | DeliveryPanelComponent, 12 | } 13 | -------------------------------------------------------------------------------- /src/rest-api/helpers.ts: -------------------------------------------------------------------------------- 1 | const methodBase = { 2 | mode: 'cors' as RequestMode, 3 | credentials: 'include' as RequestCredentials, 4 | headers: new Headers({ 5 | 'Accept': 'application/json', 6 | 'Content-Type': 'application/json', 7 | }), 8 | }; 9 | 10 | export const formatURL = (url) => (`${process.env.BASE_API_URL}${url}`); 11 | 12 | export const post: Partial = { 13 | ...methodBase, 14 | method: 'POST', 15 | }; 16 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/toolbar/groups/index.ts: -------------------------------------------------------------------------------- 1 | import { FontGroupComponent } from './fontGroupComponent'; 2 | import { LinksGroupComponent } from './linksGroupComponent'; 3 | import { ListGroupComponent } from './listGroupComponent'; 4 | import { CommandGroupComponent } from './commandsGroupComponent'; 5 | 6 | export { 7 | FontGroupComponent, 8 | LinksGroupComponent, 9 | ListGroupComponent, 10 | CommandGroupComponent 11 | }; 12 | -------------------------------------------------------------------------------- /src/common/components/dashboard/components/dashboardItemStyles.scss: -------------------------------------------------------------------------------- 1 | .item { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | padding: 50px; 6 | border: none; 7 | } 8 | 9 | .name { 10 | margin-top: 20px; 11 | } 12 | 13 | @media (max-width:1200px) { 14 | .item { 15 | flex-basis: 50%!important; 16 | } 17 | } 18 | 19 | @media (max-width:768px) { 20 | .item { 21 | flex-basis: 100%!important; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /config/karma/karma.config.base.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | basePath: '', 3 | frameworks: ['mocha', 'sinon-chai'], 4 | files: [ 5 | './spec.bundle.js', 6 | ], 7 | webpackMiddleware: { 8 | noInfo: true, 9 | }, 10 | reporters: ['mocha'], 11 | mochaReporter: { 12 | showDiff: true, 13 | }, 14 | port: 9876, 15 | colors: true, 16 | autoWatch: true, 17 | browsers: ['Chrome'], 18 | singleRun: true, 19 | concurrency: Infinity, 20 | }; 21 | -------------------------------------------------------------------------------- /src/rest-api/mappers/general/mappers.ts: -------------------------------------------------------------------------------- 1 | import { User } from '../../model/general'; 2 | import { UserProfile } from '../../../model/userProfile'; 3 | 4 | export const mapUserToStateModel = (user: User): UserProfile => ( 5 | Boolean(user) ? 6 | { 7 | id: user._id, 8 | email: user.email, 9 | fullname: `${user.name} ${user.lastName}`, 10 | role: user.role, 11 | avatar: user.avatar, 12 | } : 13 | new UserProfile() 14 | ); 15 | -------------------------------------------------------------------------------- /src/common/routeEnums/student/index.ts: -------------------------------------------------------------------------------- 1 | const defaultPath = '/student'; 2 | const trainingPath = `${defaultPath}/training`; 3 | const trainingResourcePath = `${trainingPath}/:trainingId`; 4 | 5 | export const studentRouteEnums = { 6 | default: defaultPath, 7 | training: { 8 | base: trainingPath, 9 | list: `${trainingPath}/list`, 10 | id: { 11 | base: trainingResourcePath, 12 | toc: `${trainingResourcePath}/toc`, 13 | }, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /src/common/components/form/droppableFile/droppableFile.scss: -------------------------------------------------------------------------------- 1 | .droppable { 2 | margin-top: 5px; 3 | margin-bottom: 15px; 4 | border: 2px dashed #ccc; 5 | height: 200px; 6 | display: flex; 7 | justify-content: center; 8 | flex-direction: column; 9 | cursor: pointer; 10 | color: #ccc; 11 | } 12 | 13 | .icon { 14 | margin-top: auto; 15 | font-size: 10rem; 16 | } 17 | 18 | .text { 19 | margin-bottom: auto; 20 | margin-top: 20px; 21 | font-size: 1.2em; 22 | } 23 | -------------------------------------------------------------------------------- /config/karma/spec.bundle.js: -------------------------------------------------------------------------------- 1 | // require all modules ending in ".spec" from the 2 | // current directory and all subdirectories 3 | 4 | var testsContext = require.context('../../src', true, /.spec$/); 5 | testsContext.keys().forEach(testsContext); 6 | 7 | //require all `project/src/components/**/index.js` 8 | //Just to get all files analyzed (codecoverage) 9 | const componentsContext = require.context('../../src', true, /.ts$/); 10 | componentsContext.keys().forEach(componentsContext); 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Configuration that override The default and user settings. 2 | { 3 | // The number of spaces to which a tabulation equals. This value is invalid depending on the contents of the file when `editor.detectIndentation` is enabled. 4 | "editor.tabSize": 2, 5 | // When you open a file, `editor.tab Size` and `editor.insertSpaces` are detected based on the contents of the file. 6 | "editor.detectIndentation": false, 7 | "typescript.tsdk": "./node_modules/typescript/lib" 8 | } 9 | -------------------------------------------------------------------------------- /src/common/components/counterButton/counterButton.scss: -------------------------------------------------------------------------------- 1 | .counter-wrapper { 2 | display: inline-block; 3 | } 4 | 5 | .btn-counter { 6 | cursor: default; 7 | padding: 3px 10px; 8 | border: 1px solid #ccc; 9 | background-color: #fff; 10 | display: flex; 11 | align-items: center; 12 | 13 | &, &:focus, &:active { 14 | outline: none; 15 | } 16 | 17 | img { 18 | max-width: 16px; 19 | } 20 | 21 | &:not(.btn-default):active { 22 | box-shadow: none; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | language: node_js 4 | node_js: 5 | - "6.10.3" 6 | 7 | before_install: 8 | - export CHROME_BIN=/usr/bin/google-chrome 9 | - export DISPLAY=:99.0 10 | - sh -e /etc/init.d/xvfb start 11 | - sudo apt-get update 12 | - sudo apt-get install -y libappindicator1 fonts-liberation 13 | - wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb 14 | - sudo dpkg -i google-chrome*.deb 15 | script: 16 | - npm install && npm test 17 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/panels/upload/uploadFilePanelComponent.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { UploadFileFormComponent } from './uploadFileForm'; 3 | 4 | interface Props { 5 | togglePanel(): void; 6 | } 7 | 8 | export const UploadFilePanelComponent: React.StatelessComponent = (props) => { 9 | return ( 10 |
11 |

Upload file

12 | 13 |
14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/content/sass/animations/cross-fade.scss: -------------------------------------------------------------------------------- 1 | :global(.cross-fade-leave) { 2 | opacity: 1; 3 | } 4 | 5 | :global(.cross-fade-leave.cross-fade-leave-active) { 6 | opacity: 0; 7 | transition: opacity 150ms ease-in; 8 | } 9 | 10 | :global(.cross-fade-enter) { 11 | opacity: 0; 12 | } 13 | 14 | :global(.cross-fade-enter.cross-fade-enter-active) { 15 | opacity: 1; 16 | transition: opacity 150ms ease-in; 17 | } 18 | 19 | :global(.cross-fade-height) { 20 | transition: height 150ms ease-in-out; 21 | } 22 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/panels/upload/uploadFilePanelContainer.ts: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { UploadFilePanelComponent } from './uploadFilePanelComponent'; 3 | import { setActivePanelAction } from '../../../actions/setActivePanel'; 4 | 5 | const mapDispatchToProps = (dispatch) => ({ 6 | togglePanel: () => { dispatch(setActivePanelAction('')); }, 7 | }); 8 | 9 | export const UploadFilePanelContainer = connect(null, mapDispatchToProps)(UploadFilePanelComponent); 10 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/toolbar/toolbarStyles.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | display:flex; 3 | flex-direction: row; 4 | background-color: white; 5 | margin-top: 10px; 6 | flex-basis: 100%; 7 | } 8 | 9 | .vertical { 10 | display:flex; 11 | flex-direction: column; 12 | } 13 | 14 | .item-links { 15 | border-left: 1px solid rgb(227, 227, 227); 16 | border-right: 1px solid rgb(227, 227, 227); 17 | box-sizing: border-box; 18 | padding-right: 5px; 19 | padding-left: 5px; 20 | } 21 | -------------------------------------------------------------------------------- /src/pages/general/notFound/components/header.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | const classNames: any = require('./headerStyles.scss'); 3 | 4 | export const NotFoundHeader = () => { 5 | return ( 6 |
7 |

8 |

11 |
12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /src/pages/general/routes.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { IndexRoute, Route } from 'react-router'; 3 | import { LoginPage } from './login/page'; 4 | 5 | // http://stackoverflow.com/questions/35048738/react-router-import-routes 6 | // AssembliesRoutes.js 7 | // http://randycoulman.com/blog/2016/02/02/composing-routes-in-react-router/ 8 | export const GeneralRoutes = ( 9 |
10 | 11 | 12 |
13 | ); 14 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/panels/evaluation/evaluationPanelContainer.ts: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { EvaluationPanelComponent } from './evaluationPanelComponent'; 3 | import { setActivePanelAction } from '../../../actions/setActivePanel'; 4 | 5 | const mapDispatchToProps = (dispatch) => ({ 6 | togglePanel: () => { dispatch(setActivePanelAction('')); }, 7 | }); 8 | 9 | export const EvaluationPanelContainer = connect(null, mapDispatchToProps)(EvaluationPanelComponent); 10 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/toolbar/icons/iconEnums.ts: -------------------------------------------------------------------------------- 1 | export const iconEnums = { 2 | bold: 'fa fa-bold', 3 | bulletedList: 'fa fa-list', 4 | code: 'fa fa-code', 5 | delivery: 'fa fa-file-archive-o', 6 | evaluation: 'fa fa-graduation-cap', 7 | header: 'fa fa-header', 8 | image: 'fa fa-picture-o', 9 | italic: 'fa fa-italic', 10 | link: 'fa fa-link', 11 | numberedList: 'fa fa-list-ol', 12 | preview: 'fa fa-eye', 13 | quote: 'fa fa-quote-right', 14 | upload: 'fa fa-cloud-upload', 15 | }; 16 | -------------------------------------------------------------------------------- /src/pages/general/login/components/loginForm/login.validation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createFormValidation, 3 | ValidationConstraints, 4 | Validators, 5 | } from 'lc-form-validation'; 6 | 7 | const loginValidationConstraints: ValidationConstraints = { 8 | fields: { 9 | login: [ 10 | { validator: Validators.required }, 11 | ], 12 | password: [ 13 | { validator: Validators.required }, 14 | ], 15 | }, 16 | }; 17 | 18 | export const loginFormValidation = createFormValidation(loginValidationConstraints); 19 | -------------------------------------------------------------------------------- /src/content/image/user.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/common/routeEnums/admin/index.ts: -------------------------------------------------------------------------------- 1 | const defaultRoute = '/admin'; 2 | const studentRoute = `${defaultRoute}/student`; 3 | const trainingRoute = `${defaultRoute}/training`; 4 | const studentByIdRoute = `${studentRoute}/:id`; 5 | 6 | export const adminRouteEnums = { 7 | default: defaultRoute, 8 | student: { 9 | base: studentRoute, 10 | list: `${studentRoute}/list`, 11 | edit: `${studentByIdRoute}/edit`, 12 | }, 13 | training: { 14 | list: `${trainingRoute}/list`, 15 | edit: `${trainingRoute}/edit`, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /src/common/components/progressBar/progressBarStyles.scss: -------------------------------------------------------------------------------- 1 | @import "~globalStyles/common.scss"; 2 | @import "~globalStyles/html-elements/progress.scss"; 3 | 4 | $barBackgroundColor: white; 5 | $barValueColor: lightblue; 6 | $borderColor: lightgrey; 7 | $borderRadius: 5px; 8 | 9 | @include progressBackgroundBarStyles($barBackgroundColor, $borderColor, $borderRadius); 10 | @include progressValueBarStyles($barValueColor, $borderRadius); 11 | 12 | progress[value] { 13 | @include resetDefaultAppearance; 14 | width: 250px; 15 | height: 20px; 16 | } 17 | -------------------------------------------------------------------------------- /src/reducers/student/training/index.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { trainingTOCReducer as toc } from './toc/trainingTOC'; 3 | import { TrainingTOC } from '../../../model/student/trainingToc'; 4 | import { TrainingSummary } from '../../../model/trainingSummary'; 5 | import { trainingListReducer as list } from './list/trainingList'; 6 | 7 | export interface TrainingState { 8 | list: TrainingSummary[]; 9 | toc: TrainingTOC; 10 | } 11 | 12 | export const trainingReducer = combineReducers({ 13 | toc, 14 | list, 15 | }); 16 | -------------------------------------------------------------------------------- /src/model/login/spec/loginErrors.spec.ts: -------------------------------------------------------------------------------- 1 | import {FieldValidationResult} from 'lc-form-validation'; 2 | import { ILoginErrors } from '../loginErrors'; 3 | 4 | describe('ILoginErrors', () => { 5 | it('Is instantiated and exists', () => { 6 | // Arrange 7 | const loginErrors: ILoginErrors = { 8 | login: new FieldValidationResult(), 9 | password: new FieldValidationResult(), 10 | }; 11 | 12 | // Assert 13 | expect(loginErrors).not.to.be.undefined; 14 | expect(loginErrors).not.to.be.null; 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/common/actionEnums/trainer/index.ts: -------------------------------------------------------------------------------- 1 | const prefix = 'TRAINER_MODULE'; 2 | 3 | export const trainerActionEnums = { 4 | GET_TRAINING_CONTENT_REQUEST_COMPLETED: `${prefix}/GET_TRAINING_CONTENT_REQUEST_COMPLETED`, 5 | TRAINING_CONTENT_CHANGED: `${prefix}/TRAINING_CONTENT_CHANGED`, 6 | UPDATE_EDITOR_CURSOR: `${prefix}/UPDATE_EDITOR_CURSOR`, 7 | TOGGLE_EDITOR_PREVIEW: `${prefix}/TOGGLE_EDITOR_PREVIEW`, 8 | SET_ACTIVE_PANEL: `${prefix}/SET_ACTIVE_PANEL`, 9 | GET_SUMMARY_TRAINING_REQUEST_COMPLETED: `${prefix}/GET_SUMMARY_TRAINING_REQUEST_COMPLETED`, 10 | }; 11 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/panels/delivery/deliveryPanelComponent.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { DeliveryFormComponent } from './deliveryForm'; 3 | 4 | interface Props { 5 | togglePanel(): void; 6 | } 7 | 8 | export const DeliveryPanelComponent: React.StatelessComponent = (props) => { 9 | return ( 10 |
11 |

Add delivery

12 | 13 |
14 | ); 15 | }; 16 | 17 | DeliveryPanelComponent.displayName = 'DeliveryPanelComponent'; 18 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/panels/delivery/deliveryPanelContainer.ts: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { DeliveryPanelComponent } from './deliveryPanelComponent'; 3 | import { IAppState } from '../../../../../../../reducers/index'; 4 | import { setActivePanelAction } from '../../../actions/setActivePanel'; 5 | 6 | const mapDispatchToProps = (dispatch) => ({ 7 | togglePanel: () => { dispatch(setActivePanelAction('')); }, 8 | }); 9 | 10 | export const DeliveryPanelContainer = connect(null, mapDispatchToProps)(DeliveryPanelComponent); 11 | -------------------------------------------------------------------------------- /src/common/components/header/header.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | width: 100%; 3 | padding: 10px 20px; 4 | background-color: #333; 5 | display: flex; 6 | align-items: center; 7 | } 8 | 9 | .avatar { 10 | width: 64px; 11 | margin-right: 10px; 12 | border: 1px solid transparent; 13 | border-radius: 50%; 14 | } 15 | 16 | .info { 17 | display: flex; 18 | align-items: center; 19 | color: #f5f5f5; 20 | font-weight: 500; 21 | } 22 | 23 | .actions { 24 | margin-left: auto; 25 | cursor: pointer; 26 | } 27 | 28 | .btn-icon { 29 | vertical-align: text-top; 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/model/training.ts: -------------------------------------------------------------------------------- 1 | import { Student } from './student'; 2 | import { Trainer } from './trainer'; 3 | 4 | export class Training { 5 | public id: string; 6 | public name: string; 7 | public isActive: boolean; 8 | public start: Date; 9 | public end: Date; 10 | public trainers: Trainer[]; 11 | public students: Student[]; 12 | 13 | constructor() { 14 | this.id = '-1'; 15 | this.name = ''; 16 | this.isActive = false; 17 | this.start = new Date(); 18 | this.end = new Date(); 19 | this.trainers = []; 20 | this.students = []; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/rest-api/trainer/trainerApi.ts: -------------------------------------------------------------------------------- 1 | import {trainingContentMockData} from './trainerMockData'; 2 | 3 | class TrainerApi { 4 | public getTrainingConentByTrainingId(id: number): Promise { 5 | let content: string; 6 | 7 | const trainingContent = trainingContentMockData.filter((trainingMockData) => { 8 | return trainingMockData.id === id; 9 | }); 10 | 11 | if (trainingContent) { 12 | content = trainingContent[0].content; 13 | } 14 | 15 | return Promise.resolve(content); 16 | } 17 | } 18 | 19 | export const trainerApi = new TrainerApi(); 20 | -------------------------------------------------------------------------------- /src/common/routeEnums/trainer/index.ts: -------------------------------------------------------------------------------- 1 | const defaultPath = '/trainer'; 2 | const trainingPath = `${defaultPath}/training`; 3 | const trainingResourcePath = `${trainingPath}/:trainingId`; 4 | 5 | export const trainerRouteEnums = { 6 | default: defaultPath, 7 | training: { 8 | base: trainingPath, 9 | list: `${trainingPath}/list`, 10 | byId: { 11 | base: trainingResourcePath, 12 | dashboard: `${trainingResourcePath}/dashboard`, 13 | evaluation: `${trainingResourcePath}/evaluation`, 14 | edit: `${trainingResourcePath}/edit`, 15 | }, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /src/pages/trainer/training/list/pageContainer.tsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { IAppState } from '../../../../reducers'; 3 | import { fetchTrainingList } from './actions/trainingActions'; 4 | import { TrainingListPage } from './page'; 5 | 6 | const mapStateToProps = (state: IAppState) => ({ 7 | trainingList: state.trainer.training.list, 8 | trainerId: state.login.userProfile.id, 9 | }); 10 | 11 | const mapDispatchToProps = (dispatch) => ({ 12 | }); 13 | 14 | export const TrainingListPageContainer = connect(mapStateToProps, mapDispatchToProps)(TrainingListPage); 15 | -------------------------------------------------------------------------------- /src/history.ts: -------------------------------------------------------------------------------- 1 | import { syncHistoryWithStore } from 'react-router-redux'; 2 | import { browserHistory, hashHistory } from 'react-router'; 3 | import { store } from './store'; 4 | 5 | // Change here history type: hash vs browser. This way we can 6 | // keep track of the selected type of history programmatically. 7 | const selectedHistoryType = hashHistory; 8 | 9 | export const isHashHistory = () => selectedHistoryType === hashHistory; 10 | export const isBrownserHistory = () => selectedHistoryType === browserHistory; 11 | export const history = syncHistoryWithStore(selectedHistoryType, store); 12 | -------------------------------------------------------------------------------- /config/helpers.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const rootPath = path.resolve(__dirname, '..'); 4 | 5 | exports.resolveFromRootPath = (...args) => path.join(rootPath, ...args); 6 | 7 | exports.sortChunks = function (chunks) { 8 | return function sort(left, right) { 9 | var leftIndex = chunks.indexOf(left.names[0]); 10 | var rightindex = chunks.indexOf(right.names[0]); 11 | 12 | if (leftIndex < 0 || rightindex < 0) { 13 | throw "unknown package"; 14 | } 15 | 16 | if (leftIndex > rightindex) { 17 | return 1; 18 | } 19 | 20 | return -1; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/common/components/dashboard/icons/spec/index.spec.ts: -------------------------------------------------------------------------------- 1 | import {dashboardIcons} from '../index'; 2 | 3 | describe('dashboardIcons', () => { 4 | it('should be defined', () => { 5 | expect(dashboardIcons).not.to.be.undefined; 6 | }); 7 | 8 | it('should have keys defined', () => { 9 | expect(dashboardIcons.students).to.be.equal('fa fa-users fa-5x'); 10 | expect(dashboardIcons.trainings).to.be.equal('fa fa-files-o fa-5x'); 11 | expect(dashboardIcons.evaluation).to.be.equal('fa fa-star-half-o fa-5x'); 12 | expect(dashboardIcons.training).to.be.equal('fa fa-file-o fa-5x'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /config/webpack/test/webpack.config.test.js: -------------------------------------------------------------------------------- 1 | const base = require('./webpack.config.base'); 2 | const merge = require('webpack-merge'); 3 | const helpers = require('../../helpers'); 4 | 5 | module.exports = merge(base, { 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.tsx?$/, 10 | exclude: /node_modules/, 11 | loader: 'awesome-typescript-loader', 12 | options: { 13 | useBabel: true, 14 | useCache: true, 15 | configFileName: helpers.resolveFromRootPath('config', 'karma', 'karma.tsconfig.json'), 16 | }, 17 | }, 18 | ] 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /src/pages/admin/training/edit/page.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Link} from 'react-router'; 3 | import {adminRouteEnums} from '../../../../common/routeEnums/admin'; 4 | 5 | export class EditTrainingPage extends React.Component<{}, {}> { 6 | public render() { 7 | return ( 8 |
9 | Edit Training Page: 10 |
11 |
12 | Back to training list 13 | Back to Dashboard 14 |
15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/common/actionEnums/login/spec/index.spec.ts: -------------------------------------------------------------------------------- 1 | import {loginActionEnums} from '../index'; 2 | 3 | describe('loginActionEnums', () => { 4 | it('should be defined', () => { 5 | // Assert 6 | expect(loginActionEnums).to.be.not.undefined; 7 | }); 8 | 9 | it('should have keys defined and field / value match', () => { 10 | // Assert 11 | expect(loginActionEnums.LOGIN_CONTENT_CHANGED).to.equals('LOGIN_CONTENT_CHANGED'); 12 | expect(loginActionEnums.LOGIN_REQUEST_SUCCESS).to.equals('LOGIN_REQUEST_SUCCESS'); 13 | expect(loginActionEnums.LOGIN_REQUEST_ERROR).to.equals('LOGIN_REQUEST_ERROR'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/common/actionEnums/student/spec/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { studentActionEnums } from '../'; 2 | 3 | describe('studentActionEnums', () => { 4 | it('should be an object', () => { 5 | // Assert 6 | expect(studentActionEnums).to.be.an('object').not.null; 7 | }); 8 | 9 | it('should have keys defined', () => { 10 | // Assert 11 | expect(studentActionEnums.FETCH_TRAINING_TOC_COMPLETED).to.be.equals('STUDENT_MODULE/FETCH_TRAINING_TOC_COMPLETED'); 12 | expect(studentActionEnums.GET_SUMMARY_TRAINING_REQUEST_COMPLETED) 13 | .to.be.equals('STUDENT_MODULE/GET_SUMMARY_TRAINING_REQUEST_COMPLETED'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/routes.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Route } from 'react-router'; 3 | 4 | import { AppContainer } from './appContainer'; 5 | import { AdminRoutes } from './pages/admin'; 6 | import { GeneralRoutes, NotFoundPage } from './pages/general'; 7 | import { StudentsRoutes } from './pages/student'; 8 | import { TrainingRoutes } from './pages/trainer'; 9 | 10 | export const routes = ( 11 | 12 | 13 | {GeneralRoutes} 14 | {AdminRoutes} 15 | {StudentsRoutes} 16 | {TrainingRoutes} 17 | 18 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /config/karma/karma.tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "sourceMap": false, 9 | "inlineSourceMap": true, 10 | "jsx": "react", 11 | "noLib": false, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "types": [ 14 | "karma-chai-sinon", 15 | "webpack-env", 16 | "node" 17 | ], 18 | "typeRoots": [ 19 | "../../node_modules/@types" 20 | ] 21 | }, 22 | "compileOnSave": false, 23 | "exclude": [ 24 | "node_modules" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/pages/trainer/evaluation/page.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router'; 3 | import { trainerRouteEnums } from '../../../common/routeEnums/trainer'; 4 | import { EvaluationFormComponent } from './components/evaluationForm'; 5 | import { StudentDeliveryListComponent } from './components/studentDeliveryList'; 6 | import { NavigationBar } from './components/navigation'; 7 | 8 | export class EvaluationPage extends React.Component<{}, {}> { 9 | public render() { 10 | return ( 11 |
12 | 13 | 14 |
15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/pages/trainer/training/list/trainingListProvider.tsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { IAppState } from '../../../../reducers'; 3 | import { SubscriptionManager } from '../../../../common/components'; 4 | import { fetchTrainingList } from './actions/trainingActions'; 5 | 6 | const mapStateToProps = (state: IAppState) => ({ 7 | payload: state.login.userProfile.id, 8 | }); 9 | 10 | const mapDispatchToProps = (dispatch) => ({ 11 | subscribe: (userId) => dispatch(fetchTrainingList(userId)), 12 | }); 13 | 14 | export const TrainingListProvider = connect( 15 | mapStateToProps, 16 | mapDispatchToProps, 17 | )(SubscriptionManager); 18 | -------------------------------------------------------------------------------- /src/common/components/footer/footer.scss: -------------------------------------------------------------------------------- 1 | .footer { 2 | margin-top: auto; 3 | } 4 | 5 | .separator { 6 | max-width: 95%; 7 | } 8 | 9 | .content { 10 | padding: 0 20px 20px; 11 | display: flex; 12 | justify-content: space-between; 13 | } 14 | 15 | .list { 16 | display: flex; 17 | flex-basis: calc(50% - 24px); 18 | 19 | li { 20 | padding: 0 6px; 21 | color: #999; 22 | flex-basis: 0; 23 | 24 | @media (min-width: 767px) { 25 | flex-basis: auto; 26 | } 27 | } 28 | 29 | &:last-child { 30 | justify-content: flex-end; 31 | } 32 | } 33 | 34 | .logo { 35 | max-width: 24px; 36 | align-self: flex-start; 37 | } 38 | -------------------------------------------------------------------------------- /src/common/components/form/validation.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface IProps { 4 | error?: string; 5 | } 6 | 7 | export class ValidationComponent extends React.Component { 8 | public render() { 9 | let wrapperClass: string = 'form-group clearfix'; 10 | if (this.props.error && this.props.error.length > 0) { 11 | wrapperClass = `${wrapperClass} has-error`; 12 | } 13 | 14 | return ( 15 |
16 | {this.props.children} 17 |
18 | {this.props.error} 19 |
20 |
21 | ); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/pages/admin/student/list/pageContainer.tsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { IAppState } from '../../../../reducers'; 3 | import { summaryStudentListRequestStarted } from './actions/summaryStudentListRequest'; 4 | import { ListStudentPage } from './page'; 5 | 6 | const mapStateToProps = (state: IAppState) => ({ 7 | studentList: state.adminStudent.studentSummaryList, 8 | }); 9 | 10 | const mapDispatchToProps = (dispatch) => ({ 11 | fetchStudents: () => dispatch(summaryStudentListRequestStarted()), 12 | }); 13 | 14 | export const ListStudentPageContainer = connect( 15 | mapStateToProps, 16 | mapDispatchToProps, 17 | )(ListStudentPage); 18 | -------------------------------------------------------------------------------- /src/reducers/student/training/toc/trainingTOC.ts: -------------------------------------------------------------------------------- 1 | import { studentActionEnums } from '../../../../common/actionEnums/student/'; 2 | import { TrainingTOC } from '../../../../model/student/trainingToc'; 3 | 4 | export const trainingTOCReducer = (state = new TrainingTOC(), action) => { 5 | switch (action.type) { 6 | case studentActionEnums.FETCH_TRAINING_TOC_COMPLETED: 7 | return handleFetchTrainingTOCCompleted(state, action.payload); 8 | default: 9 | return state; 10 | } 11 | }; 12 | 13 | const handleFetchTrainingTOCCompleted = (state: TrainingTOC, trainingTOC: TrainingTOC): TrainingTOC => ({ 14 | ...state, 15 | ...trainingTOC, 16 | }); 17 | -------------------------------------------------------------------------------- /config/webpack/app/webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const merge = require('webpack-merge'); 3 | const base = require('./webpack.config.base'); 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | const helpers = require('../../helpers'); 6 | 7 | module.exports = merge(base, { 8 | devtool: 'cheap-module-source-map', 9 | output: { 10 | path: helpers.resolveFromRootPath('public'), 11 | filename: '[chunkhash].[name].js', 12 | }, 13 | plugins: [ 14 | new ExtractTextPlugin({ 15 | filename: '[chunkhash].[name].css', 16 | disable: false, 17 | allChunks: true, 18 | }), 19 | ], 20 | }); 21 | -------------------------------------------------------------------------------- /src/pages/general/notFound/page.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {NotFoundBody} from './components/body'; 3 | import {NotFoundHeader} from './components/header'; 4 | const classNames: any = require('./styles.scss'); 5 | 6 | export class NotFoundPage extends React.Component<{}, {}> { 7 | public render() { 8 | return ( 9 |
10 |
11 |
12 | 13 | 14 |
15 |
16 |
17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { AppContainer } from 'react-hot-loader'; 4 | import { hashHistory } from 'react-router'; 5 | import { syncHistoryWithStore } from 'react-router-redux'; 6 | import { store } from './store'; 7 | import { AppProvider } from './provider'; 8 | 9 | const render = (Component) => { 10 | ReactDOM.render( 11 | 12 | 13 | , 14 | document.getElementById('root'), 15 | ); 16 | }; 17 | 18 | render(AppProvider); 19 | 20 | if (module.hot) { 21 | module.hot.accept('./provider', () => { 22 | render(AppProvider); 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /src/pages/admin/training/list/pageContainer.tsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { IAppState } from '../../../../reducers'; 3 | import { summaryTrainingListRequestStarted } from './actions/summaryTrainingListRequest'; 4 | import { ListTrainingPage } from './page'; 5 | 6 | const mapStateToProps = (state: IAppState) => ({ 7 | trainingList : state.adminTraining.trainingSummaryList, 8 | }); 9 | 10 | const mapDispatchToProps = (dispatch) => ({ 11 | fetchTrainings : () => dispatch(summaryTrainingListRequestStarted()), 12 | }); 13 | 14 | export const ListTrainingPageContainer = connect( 15 | mapStateToProps, 16 | mapDispatchToProps, 17 | )(ListTrainingPage); 18 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/actions/spec/toggleEditorPreview.spec.ts: -------------------------------------------------------------------------------- 1 | import {trainerActionEnums} from '../../../../../../common/actionEnums/trainer'; 2 | import {toggleEditorPreviewAction} from '../toggleEditorPreview'; 3 | 4 | describe('trainingContentChangedAction', () => { 5 | it('should be defined', () => { 6 | // Assert 7 | expect(toggleEditorPreviewAction).not.to.be.undefined; 8 | }); 9 | 10 | it('returns expected type equals TRAINING_CONTENT_CHANGED', () => { 11 | // Act 12 | const actionResult = toggleEditorPreviewAction(); 13 | 14 | // Assert 15 | expect(actionResult.type).to.equal(trainerActionEnums.TOGGLE_EDITOR_PREVIEW); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/pageContainer.tsx: -------------------------------------------------------------------------------- 1 | import {connect} from 'react-redux'; 2 | import {IAppState} from '../../../../reducers'; 3 | import {EditTrainingPage} from './page'; 4 | import {fetchTrainingContentStarted} from './actions/fetchTrainingContent'; 5 | 6 | const mapStateToProps = (state: IAppState, ownProps) => ({ 7 | trainingId: Number(ownProps.params.trainingId) || 0, 8 | }); 9 | 10 | const mapDispatchToProps = (dispatch) => ({ 11 | fetchTrainingContent: (trainingId: number) => dispatch(fetchTrainingContentStarted(trainingId)), 12 | }); 13 | 14 | export const EditTrainingContainerPage = connect( 15 | mapStateToProps, 16 | mapDispatchToProps, 17 | )(EditTrainingPage); 18 | -------------------------------------------------------------------------------- /src/common/routeEnums/admin/spec/index.spec.ts: -------------------------------------------------------------------------------- 1 | import {adminRouteEnums} from '../index'; 2 | 3 | describe('adminRouteEnums', () => { 4 | it('should be defined', () => { 5 | expect(adminRouteEnums).not.to.be.undefined; 6 | }); 7 | 8 | it('should have keys defined', () => { 9 | expect(adminRouteEnums.default).to.be.equals('/admin'); 10 | expect(adminRouteEnums.student.list).to.be.equals('/admin/student/list'); 11 | expect(adminRouteEnums.student.edit).to.be.equals('/admin/student/:id/edit'); 12 | expect(adminRouteEnums.training.list).to.be.equals('/admin/training/list'); 13 | expect(adminRouteEnums.training.edit).to.be.equals('/admin/training/edit'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/reducers/student/training/list/trainingList.ts: -------------------------------------------------------------------------------- 1 | import { studentActionEnums } from './../../../../common/actionEnums/student'; 2 | import { TrainingSummary } from './../../../../model/trainingSummary'; 3 | 4 | export const trainingListReducer = (state: TrainingSummary[] = [], action) => { 5 | switch (action.type) { 6 | case studentActionEnums.GET_SUMMARY_TRAINING_REQUEST_COMPLETED: 7 | return handleGetSummaryTrainingRequestCompleted(state, action.payload); 8 | default: 9 | return state; 10 | } 11 | }; 12 | 13 | const handleGetSummaryTrainingRequestCompleted = (state: TrainingSummary[], payload: TrainingSummary[]) => { 14 | return [ 15 | ...payload, 16 | ]; 17 | }; 18 | -------------------------------------------------------------------------------- /src/rest-api/student/studentApi.ts: -------------------------------------------------------------------------------- 1 | import { TrainingTOC } from '../../model/student/trainingToc'; 2 | import { trainingTOCMockData } from './trainingTocMockData'; 3 | 4 | class StudentAPI { 5 | private trainingList: TrainingTOC[]; 6 | 7 | constructor() { 8 | this.trainingList = trainingTOCMockData; 9 | } 10 | 11 | public setMockedTrainings(newTrainingList) { 12 | this.trainingList = newTrainingList; 13 | } 14 | 15 | public getTOCByTraining(id: string): Promise { 16 | const trainingTOC = this.trainingList.find((training) => training.id === id); 17 | return Promise.resolve(trainingTOC); 18 | } 19 | } 20 | 21 | export const studentAPI = new StudentAPI(); 22 | -------------------------------------------------------------------------------- /config/karma/karma.config.test.js: -------------------------------------------------------------------------------- 1 | const base = require('./karma.config.base'); 2 | const webpackConfig = require('../webpack/test/webpack.config.test'); 3 | 4 | module.exports = (config) => { 5 | const karmaConfig = Object.assign({}, base, { 6 | preprocessors: { 7 | './spec.bundle.js': ['webpack', 'sourcemap'], 8 | }, 9 | webpack: webpackConfig, 10 | logLevel: config.LOG_INFO, 11 | }); 12 | 13 | if (process.env.TRAVIS) { 14 | karmaConfig.customLaunchers = { 15 | Chrome_travis_ci: { 16 | base: 'Chrome', 17 | flags: ['--no-sandbox'] 18 | } 19 | }; 20 | karmaConfig.browsers = ['Chrome_travis_ci']; 21 | } 22 | 23 | config.set(karmaConfig); 24 | }; 25 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/actions/fetchTrainingContent.ts: -------------------------------------------------------------------------------- 1 | import {trainerActionEnums} from '../../../../../common/actionEnums/trainer'; 2 | import {trainerApi} from '../../../../../rest-api'; 3 | 4 | export const fetchTrainingContentStarted = (trainingId: number) => { 5 | return (dispatcher) => { 6 | const promise = trainerApi.getTrainingConentByTrainingId(trainingId); 7 | 8 | promise.then( 9 | (data) => dispatcher(fetchTrainingContentCompleted(data)), 10 | ); 11 | 12 | return promise; 13 | }; 14 | }; 15 | 16 | export const fetchTrainingContentCompleted = (content: string) => ({ 17 | type: trainerActionEnums.GET_TRAINING_CONTENT_REQUEST_COMPLETED, 18 | payload: content, 19 | }); 20 | -------------------------------------------------------------------------------- /src/pages/student/training/toc/pageContainer.ts: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { fetchTrainingTOCStarted } from './actions/fetchTrainingToc'; 3 | import { IAppState } from '../../../../reducers'; 4 | import { TrainingTOCPage } from './page'; 5 | 6 | const mapStateToProps = (state: IAppState, ownProps) => ({ 7 | trainingId: ownProps.params.trainingId, 8 | trainingTOC: state.student.training.toc, 9 | }); 10 | 11 | const mapDispatchToProps = (dispatch) => ({ 12 | fetchTrainingTOC: (trainingId: string) => { 13 | dispatch(fetchTrainingTOCStarted(trainingId)); 14 | }, 15 | }); 16 | 17 | export const TrainingTOCPageContainer = connect( 18 | mapStateToProps, 19 | mapDispatchToProps, 20 | )(TrainingTOCPage); 21 | -------------------------------------------------------------------------------- /src/pages/admin/student/edit/pageContainer.tsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { IAppState } from '../../../../reducers'; 3 | import { EditStudentPage } from './page'; 4 | import { summaryStudentByIdRequestStarted } from './actions/summaryStudentRequest'; 5 | 6 | const mapStateToProps = (state: IAppState, ownProps) => ({ 7 | studentId: ownProps.params.id.toString(), 8 | student: state.adminStudent.editingStudentSummary, 9 | }); 10 | 11 | const mapDispatchToProps = (dispatch) => ({ 12 | getStudent: (studentId: string) => dispatch(summaryStudentByIdRequestStarted(studentId)), 13 | }); 14 | 15 | export const EditStudentPageContainer = connect( 16 | mapStateToProps, 17 | mapDispatchToProps, 18 | )(EditStudentPage); 19 | -------------------------------------------------------------------------------- /src/pages/student/training/list/pageContainer.tsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { IAppState } from '../../../../reducers'; 3 | import { summaryTrainingListRequestStarted } from './actions/summaryTrainingListRequest'; 4 | import { TrainingListPage } from './page'; 5 | 6 | const mapStateToProps = (state: IAppState) => ({ 7 | trainingList : state.student.training.list, 8 | studentId: state.login.userProfile.id, 9 | }); 10 | 11 | const mapDispatchToProps = (dispatch) => ({ 12 | fetchTrainings : (studentId) => dispatch(summaryTrainingListRequestStarted(studentId)), 13 | }); 14 | 15 | export const ListTrainingPageContainer = connect( 16 | mapStateToProps, 17 | mapDispatchToProps, 18 | )(TrainingListPage); 19 | -------------------------------------------------------------------------------- /config/karma/karma.config.coverage.js: -------------------------------------------------------------------------------- 1 | const base = require('./karma.config.base'); 2 | const webpackConfig = require('../webpack/test/webpack.config.coverage'); 3 | const helpers = require('../helpers'); 4 | 5 | module.exports = (config) => { 6 | const karmaConfig = Object.assign({}, base, { 7 | preprocessors: { 8 | './spec.bundle.js': 'webpack', 9 | }, 10 | webpack: webpackConfig, 11 | logLevel: config.LOG_DISABLE, 12 | reporters: [...base.reporters, 'coverage-istanbul'], 13 | coverageIstanbulReporter: { 14 | dir: helpers.resolveFromRootPath('test', 'coverage'), 15 | reports: ['html', 'text'], 16 | fixWebpackSourcePaths: true, 17 | }, 18 | }); 19 | 20 | config.set(karmaConfig); 21 | }; 22 | -------------------------------------------------------------------------------- /src/common/components/virtualized/tableRow.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { TableRowProps } from 'react-virtualized'; 3 | import { TableCellComponent } from './tableCell'; 4 | 5 | // https://github.com/bvaughn/react-virtualized/blob/master/docs/Table.md 6 | // https://github.com/bvaughn/react-virtualized/blob/master/source/Table/defaultRowRenderer.js 7 | export const TableRowComponent: React.StatelessComponent = (props) => { 8 | return ( 9 |
10 | {React.Children.map( 11 | props.children, 12 | (child, index) => , 13 | )} 14 |
15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /src/model/trainer/spec/editTrainingSummary.spec.ts: -------------------------------------------------------------------------------- 1 | import {EditTrainingSummary} from '../editTrainingSummary'; 2 | 3 | describe('EditTrainingSummary', () => { 4 | let editTrainingSummary = null; 5 | 6 | beforeEach(() => { 7 | editTrainingSummary = new EditTrainingSummary(); 8 | }); 9 | 10 | it('Is instantiated and exists', () => { 11 | // Assert 12 | expect(editTrainingSummary).not.to.be.undefined; 13 | expect(editTrainingSummary).not.to.be.null; 14 | }); 15 | 16 | describe('#constructor', () => { 17 | it('Is initialized with the expected values on properties', () => { 18 | expect(editTrainingSummary.id).to.be.equal(-1); 19 | expect(editTrainingSummary.content).to.be.equal(''); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/model/login/spec/loginCredentials.spec.ts: -------------------------------------------------------------------------------- 1 | import { LoginCredentials } from '../loginCredentials'; 2 | 3 | describe('loginCredentials', () => { 4 | it('Is instantiated and exists', () => { 5 | // Arrange 6 | const loginCredentials = new LoginCredentials(); 7 | 8 | // Assert 9 | expect(loginCredentials).not.to.be.undefined; 10 | expect(loginCredentials).not.to.be.null; 11 | }); 12 | 13 | describe('#constructor', () => { 14 | it('Is initialized with default values', () => { 15 | // Arrange 16 | const loginCredentials = new LoginCredentials(); 17 | 18 | // Assert 19 | expect(loginCredentials.login).to.be.equal(''); 20 | expect(loginCredentials.password).to.be.equal(''); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/model/login/spec/loginResponse.spec.ts: -------------------------------------------------------------------------------- 1 | import { LoginResponse } from '../loginResponse'; 2 | 3 | describe('loginResponse', () => { 4 | it('Is instantiated and exists', () => { 5 | // Arrange 6 | const loginResponse = new LoginResponse(); 7 | 8 | // Assert 9 | expect(loginResponse).not.to.be.undefined; 10 | expect(loginResponse).not.to.be.null; 11 | }); 12 | 13 | describe('#constructor', () => { 14 | it('Is initialized with default values', () => { 15 | // Arrange 16 | const loginResponse = new LoginResponse(); 17 | 18 | // Assert 19 | expect(loginResponse.succeded).to.be.false; 20 | expect(loginResponse.userProfile).to.be.equal(loginResponse.userProfile); 21 | }); 22 | }); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /src/model/student/spec/trainingToc.spec.ts: -------------------------------------------------------------------------------- 1 | import { TrainingTOC } from '../trainingToc'; 2 | 3 | describe('Student Module: TrainingTOC', () => { 4 | let trainingTOC: TrainingTOC = null; 5 | 6 | beforeEach(() => { 7 | trainingTOC = new TrainingTOC(); 8 | }); 9 | 10 | it('should be an instance of TrainingTOC', () => { 11 | // Assert 12 | expect(trainingTOC).to.be.an.instanceOf(TrainingTOC); 13 | }); 14 | 15 | it('should have a property "id" initialized with empty', () => { 16 | // Assert 17 | expect(trainingTOC.id).to.be.empty; 18 | }); 19 | 20 | it('should have a property "content" initialized being an empty string', () => { 21 | // Assert 22 | expect(trainingTOC.content).to.be.a('string').with.lengthOf(0); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/common/routeEnums/student/spec/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { studentRouteEnums } from '../index'; 2 | 3 | describe('studentRouteEnums', () => { 4 | it('should be an object', () => { 5 | // Assert 6 | expect(studentRouteEnums).to.be.an('object').not.null; 7 | }); 8 | 9 | it('should have routes defined', () => { 10 | expect(studentRouteEnums.default).to.be.equals('/student'); 11 | expect(studentRouteEnums.training.base).to.be.equals('/student/training'); 12 | expect(studentRouteEnums.training.list).to.be.equals('/student/training/list'); 13 | expect(studentRouteEnums.training.id.base).to.be.equals('/student/training/:trainingId'); 14 | expect(studentRouteEnums.training.id.toc).to.be.equals('/student/training/:trainingId/toc'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/pages/student/training/toc/actions/fetchTrainingToc.ts: -------------------------------------------------------------------------------- 1 | import { studentActionEnums } from '../../../../../common/actionEnums/student/'; 2 | import { studentAPI } from '../../../../../rest-api/student/studentApi'; 3 | import { TrainingTOC } from '../../../../../model/student/trainingToc'; 4 | 5 | export const fetchTrainingTOCStarted = (trainingId: string) => { 6 | return (dispatch) => { 7 | return studentAPI.getTOCByTraining(trainingId).then((trainingTOC) => { 8 | if (trainingTOC) { 9 | dispatch(fetchTrainingTOCCompleted(trainingTOC)); 10 | } 11 | }); 12 | }; 13 | }; 14 | 15 | export const fetchTrainingTOCCompleted = (trainingTOC: TrainingTOC) => ({ 16 | type: studentActionEnums.FETCH_TRAINING_TOC_COMPLETED, 17 | payload: trainingTOC, 18 | }); 19 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/panels/definitions.ts: -------------------------------------------------------------------------------- 1 | import { UploadFilePanelContainer } from './upload'; 2 | import { DeliveryPanelContainer } from './delivery'; 3 | import { EvaluationPanelContainer } from './evaluation'; 4 | import { PanelItem } from '../../../../../../common/components'; 5 | 6 | export const panelIds = { 7 | upload: 'UPLOAD', 8 | evaluation: 'EVALUATION', 9 | delivery: 'DELIVERY', 10 | }; 11 | 12 | export const panelList: PanelItem[] = [ 13 | { 14 | panelId: panelIds.upload, 15 | component: UploadFilePanelContainer, 16 | }, 17 | { 18 | panelId: panelIds.evaluation, 19 | component: EvaluationPanelContainer, 20 | }, 21 | { 22 | panelId: panelIds.delivery, 23 | component: DeliveryPanelContainer, 24 | }, 25 | ]; 26 | -------------------------------------------------------------------------------- /src/rest-api/login/loginAPI.double.ts: -------------------------------------------------------------------------------- 1 | import { LoginCredentials } from '../../model/login/loginCredentials'; 2 | import { LoginResponse } from '../../model/login/loginResponse'; 3 | import { loginMockResponses } from './loginMockData'; 4 | import { LoginFunction } from './loginAPI.contract'; 5 | 6 | export const login: LoginFunction = (loginInfo: LoginCredentials): Promise => { 7 | let loginResponse = loginMockResponses.find((response) => { 8 | return response.userProfile.email === loginInfo.login; 9 | }); 10 | 11 | if (!loginResponse || loginInfo.password !== 'test') { 12 | loginResponse = new LoginResponse(); 13 | loginResponse.succeded = false; 14 | loginResponse.userProfile = null; 15 | } 16 | 17 | return Promise.resolve(loginResponse); 18 | }; 19 | -------------------------------------------------------------------------------- /src/pages/admin/student/list/actions/summaryStudentListRequest.ts: -------------------------------------------------------------------------------- 1 | import { adminActionEnums } from '../../../../../common/actionEnums/admin'; 2 | import { StudentSummary } from '../../../../../model/studentSummary'; 3 | import { studentApi } from '../../../../../rest-api'; 4 | 5 | export const summaryStudentListRequestStarted = () => { 6 | return (dispatcher) => { 7 | const promise = studentApi.getSummaryStudentList(); 8 | 9 | promise.then( 10 | (data) => dispatcher(summaryStudentListRequestCompleted(data)), 11 | ); 12 | 13 | return promise; 14 | }; 15 | }; 16 | 17 | export const summaryStudentListRequestCompleted = (studentSummaryList: StudentSummary[]) => ({ 18 | payload: studentSummaryList, 19 | type: adminActionEnums.GET_SUMMARY_STUDENT_REQUEST_COMPLETED, 20 | }); 21 | -------------------------------------------------------------------------------- /src/model/spec/studentSummary.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import {} from 'mocha'; 3 | import {} from 'core-js'; 4 | import {StudentSummary} from '../studentSummary'; 5 | 6 | describe('StudentSummary', () => { 7 | let student = null; 8 | 9 | beforeEach(() => { 10 | student = new StudentSummary(); 11 | }); 12 | 13 | it('Is instantiated and exists', () => { 14 | // Assert 15 | expect(student).not.to.be.undefined; 16 | expect(student).not.to.be.null; 17 | }); 18 | 19 | describe('#constructor', () => { 20 | it('Is initialized with the expected values on properties', () => { 21 | expect(student.id).to.be.equal('-1'); 22 | expect(student.fullname).to.be.equal(''); 23 | expect(student.email).to.be.equal(''); 24 | }); 25 | }); 26 | 27 | }); 28 | -------------------------------------------------------------------------------- /src/pages/trainer/training/list/actions/trainingActions.ts: -------------------------------------------------------------------------------- 1 | import { TrainingSummary } from './../../../../../model/trainingSummary'; 2 | import { trainingApi } from './../../../../../rest-api/training'; 3 | import { trainerActionEnums } from '../../../../../common/actionEnums/trainer/index'; 4 | 5 | export const fetchTrainingList = (trainerId: string) => { 6 | return (dispatcher) => { 7 | const promise = trainingApi.getTrainingListByTrainer(trainerId); 8 | 9 | promise.then( 10 | (data) => dispatcher(fetchTrainingListSucceeded(data)), 11 | ); 12 | return promise; 13 | }; 14 | }; 15 | 16 | const fetchTrainingListSucceeded = (trainingSummaryList: TrainingSummary[]) => ({ 17 | payload: trainingSummaryList, 18 | type: trainerActionEnums.GET_SUMMARY_TRAINING_REQUEST_COMPLETED, 19 | }); 20 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/page.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router'; 3 | import { EditorContainerComponent } from './components/editorContainer'; 4 | import { NavigationBar } from './components/navigation/navigation'; 5 | const styles: any = require('./page.scss'); 6 | 7 | interface IProps { 8 | trainingId: number; 9 | fetchTrainingContent: (trainingId: number) => void; 10 | } 11 | 12 | export class EditTrainingPage extends React.Component { 13 | public componentDidMount() { 14 | this.props.fetchTrainingContent(this.props.trainingId); 15 | } 16 | 17 | public render() { 18 | return ( 19 |
20 | 21 | 22 |
23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/pages/admin/training/list/actions/summaryTrainingListRequest.ts: -------------------------------------------------------------------------------- 1 | import { adminActionEnums } from './../../../../../common/actionEnums/admin'; 2 | import { TrainingSummary } from './../../../../../model/trainingSummary'; 3 | import { trainingApi } from './../../../../../rest-api/training'; 4 | 5 | export const summaryTrainingListRequestStarted = () => { 6 | return (dispatcher) => { 7 | const promise = trainingApi.getSummaryTrainingList(); 8 | 9 | promise.then( 10 | (data) => dispatcher(summaryTrainingListRequestCompleted(data)), 11 | ); 12 | return promise; 13 | }; 14 | }; 15 | 16 | export const summaryTrainingListRequestCompleted = (trainingSummaryList: TrainingSummary[]) => ({ 17 | payload: trainingSummaryList, 18 | type: adminActionEnums.GET_SUMMARY_TRAINING_REQUEST_COMPLETED, 19 | }); 20 | -------------------------------------------------------------------------------- /src/common/components/virtualized/tableCell.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | // Generic properties column properties 4 | // from https://github.com/bvaughn/react-virtualized/blob/master/source/Table/Table.js#L400 5 | export interface TableCellColumnProps { 6 | className: string; 7 | style: React.CSSProperties; 8 | title: string | null; 9 | } 10 | 11 | interface TableCellComponentsProps { 12 | column: React.ReactElement; 13 | cellContent: React.ReactNode; 14 | } 15 | 16 | export const TableCellComponent: React.StatelessComponent = 17 | ({ cellContent, column }) => { 18 | return ( 19 |
23 | {cellContent} 24 |
25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/markdownEntryConstants.ts: -------------------------------------------------------------------------------- 1 | export const markdownEntryConstants = { 2 | bold: { 3 | mdCaret: '****', 4 | cursorPosition: 2, 5 | }, 6 | bulletedList: { 7 | mdCaret: '\n - ', 8 | cursorPosition: 4, 9 | }, 10 | code: { 11 | mdCaret: '``', 12 | cursorPosition: 1, 13 | }, 14 | header: { 15 | mdCaret: '# ', 16 | cursorPosition: 1, 17 | }, 18 | image: { 19 | mdCaret: '![alt text]()', 20 | cursorPosition: 12, 21 | }, 22 | italic: { 23 | mdCaret: '**', 24 | cursorPosition: 1, 25 | }, 26 | link: { 27 | mdCaret: '[](url)', 28 | cursorPosition: 1, 29 | }, 30 | numberedList: { 31 | mdCaret: '\n 1. ', 32 | cursorPosition: 5, 33 | }, 34 | quote: { 35 | mdCaret: '> ', 36 | cursorPosition: 2, 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/panels/evaluation/evaluationPanelComponent.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { EvaluationFormComponent } from './evaluationPanelForm'; 3 | import { Exercise } from '../../../../../../../model/trainer/exercise'; 4 | 5 | const exercises: Exercise[] = [ 6 | { id: 1, name: 'Exercise 1' }, 7 | { id: 2, name: 'Exercise 2' }, 8 | { id: 3, name: 'Exercise 3' }, 9 | ]; 10 | 11 | interface Props { 12 | togglePanel(): void; 13 | } 14 | 15 | export const EvaluationPanelComponent: React.StatelessComponent = (props) => { 16 | return ( 17 |
18 |

Evaluation Component

19 | 20 |
21 | ); 22 | }; 23 | 24 | EvaluationPanelComponent.displayName = 'EvaluationPanelComponent'; 25 | -------------------------------------------------------------------------------- /src/model/spec/trainingSummary.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import {} from 'mocha'; 3 | import {} from 'core-js'; 4 | import {TrainingSummary} from '../trainingSummary'; 5 | 6 | describe('TrainingSummary', () => { 7 | let trainingSummary = null; 8 | 9 | beforeEach(() => { 10 | trainingSummary = new TrainingSummary(); 11 | }); 12 | 13 | it('Is instantiated and exists', () => { 14 | // Assert 15 | expect(trainingSummary).not.to.be.undefined; 16 | expect(trainingSummary).not.to.be.null; 17 | }); 18 | 19 | describe('#constructor', () => { 20 | it('Is initialized with the expected values on properties', () => { 21 | expect(trainingSummary.id).to.be.equal('-1'); 22 | expect(trainingSummary.name).to.be.equal(''); 23 | expect(trainingSummary.isActive).to.be.false; 24 | }); 25 | }); 26 | 27 | }); 28 | -------------------------------------------------------------------------------- /src/pages/admin/student/edit/actions/summaryStudentRequest.ts: -------------------------------------------------------------------------------- 1 | import { adminActionEnums } from '../../../../../common/actionEnums/admin'; 2 | import { StudentSummary } from '../../../../../model/studentSummary'; 3 | import { studentApi } from '../../../../../rest-api'; 4 | 5 | export const summaryStudentByIdRequestStarted = (studentId: string) => { 6 | return function(dispatcher) { 7 | const promise = studentApi.getStudentById(studentId); 8 | 9 | promise.then( 10 | (data) => { 11 | dispatcher(summaryStudentByIdRequestCompleted(data)); 12 | }, 13 | ); 14 | 15 | return promise; 16 | }; 17 | }; 18 | 19 | export const summaryStudentByIdRequestCompleted = (student: StudentSummary) => { 20 | return { 21 | payload: student, 22 | type: adminActionEnums.GET_SUMMARY_STUDENT_BY_ID_REQUEST_COMPLETED, 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /src/pages/student/training/list/actions/summaryTrainingListRequest.ts: -------------------------------------------------------------------------------- 1 | import { studentActionEnums } from './../../../../../common/actionEnums/student'; 2 | import { TrainingSummary } from './../../../../../model/trainingSummary'; 3 | import { trainingApi } from './../../../../../rest-api/training'; 4 | 5 | export const summaryTrainingListRequestStarted = (studentId) => { 6 | return (dispatcher) => { 7 | const promise = trainingApi.getSummaryTrainingListByStudent(studentId); 8 | 9 | promise.then( 10 | (data) => dispatcher(summaryTrainingListRequestCompleted(data)), 11 | ); 12 | return promise; 13 | }; 14 | }; 15 | 16 | export const summaryTrainingListRequestCompleted = (trainingSummaryList: TrainingSummary[]) => ({ 17 | payload: trainingSummaryList, 18 | type: studentActionEnums.GET_SUMMARY_TRAINING_REQUEST_COMPLETED, 19 | }); 20 | -------------------------------------------------------------------------------- /src/pages/general/login/spec/page.spec.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {shallow} from 'enzyme'; 3 | import {Link} from 'react-router'; 4 | import {multilineTrim} from '../../../../common/parse/multilineTrim'; 5 | import {LoginFormContainerComponent} from '../components/loginForm/loginFormContainer'; 6 | import {LoginPage} from '../page'; 7 | 8 | describe('LoginPage', () => { 9 | it('should be defined', () => { 10 | // Act 11 | const component = shallow( 12 | , 13 | ); 14 | 15 | // Assert 16 | expect(component).not.to.be.undefined; 17 | }); 18 | 19 | it('should renders as expected', () => { 20 | // Act 21 | const component = shallow( 22 | , 23 | ); 24 | 25 | // Assert 26 | expect(component.find(LoginFormContainerComponent)).to.have.lengthOf(1); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/pages/student/training/list/components/navigation.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router'; 3 | import { Breadcrumb, BreadcrumbItem } from '../../../../../common/components'; 4 | const teachingImg: any = require('../../../../../content/image/teaching.svg'); 5 | const styles: any = require('../../../../../content/sass/components/navigation.scss'); 6 | 7 | export const NavigationBar: React.StatelessComponent<{}> = () => ( 8 |
9 | 10 | 11 | teaching logo 12 | Lemoncode 13 | 14 | My Trainings 15 | 16 |
17 | ); 18 | 19 | NavigationBar.displayName = 'NavigationBar'; 20 | -------------------------------------------------------------------------------- /src/common/components/dashboard/components/dashboardItem.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Link} from 'react-router'; 3 | const classNames: any = require('./dashboardItemStyles.scss'); 4 | 5 | export interface IDashboardItem { 6 | icon: string; 7 | name: string; 8 | linkTo: string; 9 | } 10 | 11 | interface IProps { 12 | item: IDashboardItem; 13 | style?: React.CSSProperties; 14 | } 15 | 16 | export class DashboardItemComponent extends React.Component { 17 | public render() { 18 | return ( 19 | 24 | 25 |

{this.props.item.name}

26 | 27 | ); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/common/components/counterButton/counterButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | const styles: any = require('./counterButton.scss'); 3 | 4 | interface Props { 5 | logo?: string; 6 | logoAlt?: string; 7 | text?: string; 8 | total: number; 9 | } 10 | 11 | export const CounterButton: React.StatelessComponent = (props) => { 12 | return ( 13 | 14 | 18 | 19 | 20 | ); 21 | }; 22 | 23 | CounterButton.displayName = 'CounterButton'; 24 | CounterButton.defaultProps = { 25 | logo: '', 26 | logoAlt: '', 27 | text: '', 28 | total: NaN, 29 | }; 30 | -------------------------------------------------------------------------------- /src/model/spec/student.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import {} from 'mocha'; 3 | import {} from 'core-js'; 4 | import {Student} from '../student'; 5 | 6 | describe('Student', () => { 7 | let student: Student = null; 8 | 9 | beforeEach(() => { 10 | student = new Student(); 11 | }); 12 | 13 | it('Is instantiated and exists', () => { 14 | // Assert 15 | expect(student).not.to.be.undefined; 16 | expect(student).not.to.be.null; 17 | }); 18 | 19 | describe('#constructor', () => { 20 | it('Is initialized with the expected values on properties', () => { 21 | expect(student.id).to.be.equal('-1'); 22 | expect(student.fullname).to.be.equal(''); 23 | expect(student.email).to.be.equal(''); 24 | expect(student.phoneNumber).to.be.equal(''); 25 | expect(student.isActive).to.be.true; 26 | }); 27 | }); 28 | 29 | }); 30 | -------------------------------------------------------------------------------- /src/pages/trainer/training/list/components/navigation.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router'; 3 | import { Breadcrumb, BreadcrumbItem, CounterButton } from '../../../../../common/components'; 4 | const teachingImg: any = require('../../../../../content/image/teaching.svg'); 5 | const styles: any = require('../../../../../content/sass/components/navigation.scss'); 6 | 7 | export const NavigationBar: React.StatelessComponent<{}> = () => ( 8 |
9 | 10 | 11 | teaching logo 12 | Lemoncode 13 | 14 | My Trainings 15 | 16 |
17 | ); 18 | 19 | NavigationBar.displayName = 'NavigationBar'; 20 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/toolbar/buttons/toolbarButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { IMarkdownEntry } from '../../../../../../../model/trainer/markdownEntry'; 3 | const classNames: any = require('./buttonStyles.scss'); 4 | 5 | interface IProps { 6 | onClick: () => void; 7 | } 8 | 9 | export class ToolbarButton extends React.Component { 10 | 11 | constructor() { 12 | super(); 13 | 14 | this.onClick = this.onClick.bind(this); 15 | } 16 | private onClick(event) { 17 | event.preventDefault(); 18 | this.props.onClick(); 19 | } 20 | 21 | public render() { 22 | return ( 23 | 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/pages/admin/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {DashboardComponent, IDashboardItem, dashboardIcons} from '../../../common/components/dashboard'; 3 | import {adminRouteEnums} from '../../../common/routeEnums/admin'; 4 | const classNames: any = require('./pageStyles.scss'); 5 | 6 | export class DashboardPage extends React.Component<{}, {}> { 7 | private dashboardItems: IDashboardItem[] = [ 8 | {icon: dashboardIcons.students, name: 'Manage students', linkTo: adminRouteEnums.student.list}, 9 | {icon: dashboardIcons.trainings, name: 'Manage trainings', linkTo: adminRouteEnums.training.list}, 10 | ]; 11 | 12 | public render() { 13 | return ( 14 |
15 |

Admin dashboard

16 | 19 |
20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/reducers/adminTraining.ts: -------------------------------------------------------------------------------- 1 | import { adminActionEnums } from './../common/actionEnums/admin'; 2 | import { TrainingSummary } from './../model/trainingSummary'; 3 | 4 | export class AdminTrainingState { 5 | public trainingSummaryList: TrainingSummary[]; 6 | 7 | public constructor() { 8 | this.trainingSummaryList = []; 9 | } 10 | } 11 | 12 | export const adminTrainingReducer = (state: AdminTrainingState = new AdminTrainingState(), action) => { 13 | switch (action.type) { 14 | case adminActionEnums.GET_SUMMARY_TRAINING_REQUEST_COMPLETED: 15 | return handleGetSummaryTrainingRequestCompleted(state, action.payload); 16 | default: 17 | return state; 18 | } 19 | }; 20 | 21 | const handleGetSummaryTrainingRequestCompleted = (state: AdminTrainingState, payload: TrainingSummary[]) => { 22 | return { 23 | ...state, 24 | trainingSummaryList: payload, 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /src/common/routeEnums/trainer/spec/index.spec.ts: -------------------------------------------------------------------------------- 1 | import {trainerRouteEnums} from '../index'; 2 | 3 | describe('trainerRouteEnums', () => { 4 | it('should be an object', () => { 5 | expect(trainerRouteEnums).to.be.an('object').that.is.not.null; 6 | }); 7 | 8 | it('should have keys defined', () => { 9 | expect(trainerRouteEnums.default).to.be.equals('/trainer'); 10 | expect(trainerRouteEnums.training.base).to.be.equals('/trainer/training'); 11 | expect(trainerRouteEnums.training.byId.base).to.be.equals('/trainer/training/:trainingId'); 12 | expect(trainerRouteEnums.training.byId.dashboard).to.be.equals('/trainer/training/:trainingId/dashboard'); 13 | expect(trainerRouteEnums.training.byId.edit).to.be.equals('/trainer/training/:trainingId/edit'); 14 | expect(trainerRouteEnums.training.byId.evaluation).to.be.equals('/trainer/training/:trainingId/evaluation'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/toolbar/icons/spec/iconEnums.spec.ts: -------------------------------------------------------------------------------- 1 | import {iconEnums} from '../iconEnums'; 2 | 3 | describe('iconEnums', () => { 4 | it('should be defined', () => { 5 | // Assert 6 | expect(iconEnums).to.be.not.undefined; 7 | }); 8 | 9 | it('should have keys defined and field / value match', () => { 10 | // Assert 11 | expect(iconEnums.bold).to.equals('fa fa-bold'); 12 | expect(iconEnums.bulletedList).to.equals('fa fa-list'); 13 | expect(iconEnums.code).to.equals('fa fa-code'); 14 | expect(iconEnums.header).to.equals('fa fa-header'); 15 | expect(iconEnums.image).to.equals('fa fa-picture-o'); 16 | expect(iconEnums.italic).to.equals('fa fa-italic'); 17 | expect(iconEnums.link).to.equals('fa fa-link'); 18 | expect(iconEnums.numberedList).to.equals('fa fa-list-ol'); 19 | expect(iconEnums.quote).to.equals('fa fa-quote-right'); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/actions/spec/setActivePanel.spec.ts: -------------------------------------------------------------------------------- 1 | import {trainerActionEnums} from '../../../../../../common/actionEnums/trainer'; 2 | import { setActivePanelAction } from '../setActivePanel'; 3 | 4 | describe('setActivePanelAction', () => { 5 | it('should be defined', () => { 6 | // Assert 7 | expect(setActivePanelAction).not.to.be.undefined; 8 | }); 9 | 10 | it('returns expected type equals SET_ACTIVE_PANEL', () => { 11 | // Act 12 | const actionResult = setActivePanelAction(null); 13 | 14 | // Assert 15 | expect(actionResult.type).to.equal(trainerActionEnums.SET_ACTIVE_PANEL); 16 | }); 17 | 18 | it('returns expected payload', () => { 19 | // Arrange 20 | const panelId = 'mypanel'; 21 | 22 | // Act 23 | const actionResult = setActivePanelAction(panelId); 24 | 25 | // Assert 26 | expect(actionResult.payload).to.equal(panelId); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/common/components/form/select.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ValidationComponent } from './validation'; 3 | import { CommonInputProps } from './'; 4 | 5 | export const SelectComponent: React.StatelessComponent = (props) => { 6 | return ( 7 | 8 | 11 |
12 | 22 |
23 |
24 | ); 25 | }; 26 | 27 | SelectComponent.displayName = 'SelectComponent'; 28 | -------------------------------------------------------------------------------- /src/common/components/progressBar/spec/progressBar.spec.tsx: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { shallow } from 'enzyme'; 3 | import * as React from 'react'; 4 | import {ProgressBarComponent} from '../progressBarComponent'; 5 | 6 | describe('ProgressBarComponent', () => { 7 | 8 | it('should render a progress element', () => { 9 | // Act 10 | const progressBarComponent = shallow( 11 | , 12 | ); 13 | 14 | // Assert 15 | expect(progressBarComponent.type()).to.be.equals('progress'); 16 | }); 17 | 18 | it('should render the progress tag', () => { 19 | // Act 20 | const progressBarComponent = shallow( 21 | , 22 | ); 23 | 24 | // Assert 25 | expect(progressBarComponent.prop('max')).to.be.equal(10); 26 | expect(progressBarComponent.prop('value')).to.be.equal(5); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/toolbar/icons/spec/icon.spec.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {shallow} from 'enzyme'; 3 | import {multilineTrim} from '../../../../../../../../common/parse/multilineTrim'; 4 | import {Icon} from '../icon'; 5 | 6 | describe('Icon', () => { 7 | it('should be defined', () => { 8 | // Act 9 | const component = shallow( 10 | , 11 | ); 12 | 13 | // Assert 14 | expect(component).not.to.be.undefined; 15 | }); 16 | 17 | it('should renders as expected', () => { 18 | // Arrange 19 | const icon = 'test icon'; 20 | 21 | const expectedComponent = ` 22 | 23 | 24 | `; 25 | // Act 26 | const component = shallow( 27 | , 28 | ); 29 | 30 | // Assert 31 | expect(component.html()).to.equal(multilineTrim(expectedComponent)); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/pages/trainer/training/list/page.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { TrainingSummary } from '../../../../model/trainingSummary'; 3 | import { TrainingTableComponent } from './components/trainingTable'; 4 | import { AutoSizer } from 'react-virtualized'; 5 | import { NavigationBar } from './components/navigation'; 6 | import { TrainingListProvider } from './trainingListProvider'; 7 | 8 | interface Props { 9 | trainingList: TrainingSummary[]; 10 | trainerId: number; 11 | } 12 | 13 | export const TrainingListPage = (props) => { 14 | return ( 15 |
16 | 17 |

My trainings

18 | 19 | 20 | {({ width }) => } 21 | 22 | 23 |
24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /src/reducers/index.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux'; 3 | import { TrainerState, trainerReducer } from './trainer'; 4 | import { StudentState, studentReducer} from './student/'; 5 | import { adminStudentReducer, AdminStudentState } from './adminStudent'; 6 | import { adminTrainingReducer, AdminTrainingState } from './adminTraining'; 7 | import { LoginState, loginReducer } from './login'; 8 | 9 | export interface IAppState { 10 | adminStudent: AdminStudentState; 11 | login: LoginState; 12 | adminTraining: AdminTrainingState; 13 | trainer: TrainerState; 14 | student: StudentState; 15 | } 16 | 17 | export const reducers = combineReducers({ 18 | adminStudent: adminStudentReducer, 19 | login: loginReducer, 20 | adminTraining: adminTrainingReducer, 21 | trainer: trainerReducer, 22 | student: studentReducer, 23 | routing: routerReducer, 24 | }); 25 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/toolbar/buttons/toolbarLabeledButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { IMarkdownEntry } from '../../../../../../../model/trainer/markdownEntry'; 3 | import { ToolbarButton } from './toolbarButton'; 4 | const classNames: any = require('./buttonStyles.scss'); 5 | 6 | interface IProps { 7 | onClick: () => void; 8 | label: string; 9 | } 10 | 11 | export class ToolbarLabeledButton extends React.Component { 12 | 13 | constructor() { 14 | super(); 15 | 16 | } 17 | 18 | public render() { 19 | return ( 20 |
21 | 24 | {this.props.children} 25 | 26 |
27 | {this.props.label} 28 |
29 |
30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/actions/spec/updateEditorCursor.spec.ts: -------------------------------------------------------------------------------- 1 | import {trainerActionEnums} from '../../../../../../common/actionEnums/trainer'; 2 | import {updateEditorCursorAction} from '../updateEditorCursor'; 3 | 4 | describe('updateEditorCursorAction', () => { 5 | it('should be defined', () => { 6 | // Assert 7 | expect(updateEditorCursorAction).not.to.be.undefined; 8 | }); 9 | 10 | it('returns expected type equals UPDATE_EDITOR_CURSOR', () => { 11 | // Act 12 | const actionResult = updateEditorCursorAction(null); 13 | 14 | // Assert 15 | expect(actionResult.type).to.equal(trainerActionEnums.UPDATE_EDITOR_CURSOR); 16 | }); 17 | 18 | it('returns expected payload', () => { 19 | // Arrange 20 | const cursorStartPosition = 5; 21 | 22 | // Act 23 | const actionResult = updateEditorCursorAction(cursorStartPosition); 24 | 25 | // Assert 26 | expect(actionResult.payload).to.equal(cursorStartPosition); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/rest-api/login/loginMockData.ts: -------------------------------------------------------------------------------- 1 | import { LoginResponse } from '../../model/login/loginResponse'; 2 | 3 | export const loginMockResponses: LoginResponse[] = [ 4 | { 5 | succeded: true, 6 | userProfile: { 7 | id: '1', 8 | fullname: 'Admin', 9 | role: 'admin', 10 | email: 'admin', 11 | avatar: 'https://cdn1.iconfinder.com/data/icons/user-pictures/101/malecostume-64.png', 12 | }, 13 | }, 14 | { 15 | succeded: true, 16 | userProfile: { 17 | id: '2', 18 | fullname: 'Trainer', 19 | role: 'trainer', 20 | email: 'trainer', 21 | avatar: 'https://cdn1.iconfinder.com/data/icons/user-pictures/100/female1-64.png', 22 | }, 23 | }, 24 | { 25 | succeded: true, 26 | userProfile: { 27 | id: '3', 28 | fullname: 'Student', 29 | role: 'student', 30 | email: 'student', 31 | avatar: 'https://cdn1.iconfinder.com/data/icons/user-pictures/100/boy-64.png', 32 | }, 33 | }, 34 | ]; 35 | -------------------------------------------------------------------------------- /src/common/components/footer/footer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router'; 3 | const styles: any = require('./footer.scss'); 4 | const logo: any = require('../../../content/image/logo.png'); 5 | 6 | export const FooterComponent: React.StatelessComponent<{}> = () => ( 7 |
8 |
9 |
10 |
    11 |
  • LeanMood powered by the Lemoncode Team
  • 12 |
  • Terms
  • 13 |
  • Privacy
  • 14 |
15 | Lemoncode logo 16 | 20 |
21 |
22 | ); 23 | -------------------------------------------------------------------------------- /src/common/helper/navigationHelper/spec/navigationHelper.spec.ts: -------------------------------------------------------------------------------- 1 | import ReduxThunk from 'redux-thunk'; 2 | import configureStore from 'redux-mock-store'; 3 | import * as router from 'react-router'; 4 | import { navigationHelper } from '../'; 5 | import { history } from '../../../../history'; 6 | 7 | const middlewares = [ReduxThunk]; 8 | const mockStore = configureStore(middlewares); 9 | 10 | describe('navigateBasedOnRole', () => { 11 | it('should be defined', () => { 12 | // Assert 13 | expect(navigationHelper).not.to.be.undefined; 14 | }); 15 | 16 | it('should navigate to path', sinon.test(() => { 17 | // Arrange 18 | const sinon: sinon.SinonStatic = this; 19 | 20 | const hashHistoryStub = sinon.stub(history, 'push'); 21 | 22 | // Act 23 | navigationHelper.navigateToPath('/testPath'); 24 | 25 | // Assert 26 | expect(hashHistoryStub.called).to.be.true; 27 | expect(hashHistoryStub.calledWith('/testPath')).to.be.true; 28 | }).bind(this)); 29 | }); 30 | -------------------------------------------------------------------------------- /src/model/spec/userProfile.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import * as deepFreeze from 'deep-freeze'; 3 | import { UserProfile } from '../userProfile'; 4 | import {} from 'mocha'; 5 | import {} from 'core-js'; 6 | 7 | describe('userProfile', () => { 8 | let userProfile = null; 9 | 10 | beforeEach(() => { 11 | userProfile = new UserProfile(); 12 | }); 13 | 14 | it('Is instantiated and exists', () => { 15 | // Arrange 16 | // Act 17 | // Assert 18 | expect(userProfile).not.to.be.undefined; 19 | expect(userProfile).not.to.be.null; 20 | }); 21 | 22 | describe('#constructor', () => { 23 | it('Is initializaed with the expected fields and default values', () => { 24 | // Arrange 25 | // Act 26 | // Assert 27 | expect(userProfile.id).to.be.equal(''); 28 | expect(userProfile.fullname).to.be.equal(''); 29 | expect(userProfile.email).to.be.equal(''); 30 | expect(userProfile.role).to.be.equal(''); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/pages/general/notFound/components/body.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Link} from 'react-router'; 3 | 4 | export const NotFoundBody = () => { 5 | return ( 6 | /* tslint:disable */ 7 |
8 |

The page you are looking for might have been removed, had its name changed, or is temporarily unavailable. Please try the following:

9 |
    10 |
  • Make sure that the Web site address displayed in the address bar of your browser is spelled and formatted correctly.
  • 11 |
  • If you reached this page by clicking a link, 12 | contact us to alert us that the link is incorrectly formatted.
  • 13 |
  • Forget that this ever happened, and go our Home page :)
  • 14 |
15 |
16 | /* tslint:enable */ 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/common/actionEnums/spec/admin.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import {} from 'mocha'; 3 | import {} from 'core-js'; 4 | import { adminActionEnums } from '../admin'; 5 | 6 | describe('adminActionEnums', () => { 7 | it('should be defined', () => { 8 | expect(adminActionEnums).to.be.not.undefined; 9 | }); 10 | 11 | it('should have keys defined and field / value match', () => { 12 | expect(adminActionEnums.GET_SUMMARY_STUDENT_REQUEST_COMPLETED).not.to.be.undefined; 13 | expect(adminActionEnums.GET_SUMMARY_STUDENT_REQUEST_COMPLETED).to.be.a('string'); 14 | expect(adminActionEnums.GET_SUMMARY_STUDENT_REQUEST_COMPLETED).equal('GET_SUMMARY_STUDENT_REQUEST_COMPLETED'); 15 | expect(adminActionEnums.GET_SUMMARY_TRAINING_REQUEST_COMPLETED).not.to.be.undefined; 16 | expect(adminActionEnums.GET_SUMMARY_TRAINING_REQUEST_COMPLETED).to.be.a('string'); 17 | expect(adminActionEnums.GET_SUMMARY_TRAINING_REQUEST_COMPLETED).equal('GET_SUMMARY_TRAINING_REQUEST_COMPLETED'); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/pages/admin/student/list/page.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router'; 3 | import { AutoSizer } from 'react-virtualized'; 4 | import { StudentSummary } from '../../../../model/studentSummary'; 5 | import { StudentTableComponent } from './components/studentTable'; 6 | import { adminRouteEnums } from '../../../../common/routeEnums/admin'; 7 | 8 | interface Props { 9 | studentList: StudentSummary[]; 10 | fetchStudents(): void; 11 | } 12 | 13 | export class ListStudentPage extends React.Component { 14 | public componentDidMount() { 15 | this.props.fetchStudents(); 16 | } 17 | 18 | public render() { 19 | return ( 20 |
21 |

Students

22 | 23 | {({ width }) => } 24 | 25 | Go back to dashboard 26 |
27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/common/components/subscriptionManager/subscriptionManager.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface Props { 4 | payload: any; 5 | subscribe: (payload) => void; 6 | unsubscribe: (payload) => void; 7 | } 8 | 9 | export class SubscriptionManager extends React.Component { 10 | public componentWillMount() { 11 | subscribe(this.props); 12 | } 13 | 14 | public componentWillReceiveProps(newProps) { 15 | if (this.props.payload !== newProps.payload) { 16 | unsubscribe(this.props); 17 | subscribe(newProps); 18 | } 19 | } 20 | 21 | public componentWillUnmount() { 22 | unsubscribe(this.props); 23 | } 24 | 25 | public render() { 26 | return ( 27 |
28 | {this.props.children} 29 |
30 | ); 31 | } 32 | } 33 | 34 | const subscribe = (props: Props) => { 35 | props.subscribe(props.payload); 36 | }; 37 | 38 | const unsubscribe = (props: Props) => { 39 | if (props.unsubscribe) { 40 | props.unsubscribe(props.payload); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/actions/spec/trainingContentChanged.spec.ts: -------------------------------------------------------------------------------- 1 | import {trainerActionEnums} from '../../../../../../common/actionEnums/trainer'; 2 | import {trainingContentChangedAction} from '../trainingContentChanged'; 3 | 4 | describe('trainingContentChangedAction', () => { 5 | it('should be defined', () => { 6 | // Assert 7 | expect(trainingContentChangedAction).not.to.be.undefined; 8 | }); 9 | 10 | it('returns expected type equals TRAINING_CONTENT_CHANGED', () => { 11 | // Act 12 | const actionResult = trainingContentChangedAction(null); 13 | 14 | // Assert 15 | expect(actionResult.type).to.equal(trainerActionEnums.TRAINING_CONTENT_CHANGED); 16 | }); 17 | 18 | it('returns expected payload equals content', () => { 19 | // Arrange 20 | const expectedContent = 'Test content'; 21 | 22 | // Act 23 | const actionResult = trainingContentChangedAction(expectedContent); 24 | 25 | // Assert 26 | expect(actionResult.payload).to.equal(expectedContent); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/common/actionEnums/trainer/spec/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { trainerActionEnums } from '../index'; 2 | 3 | describe('trainerActionEnums', () => { 4 | it('should be an object', () => { 5 | expect(trainerActionEnums).to.be.an('object').not.null; 6 | }); 7 | 8 | it('should has keys defined and field / value match', () => { 9 | expect(trainerActionEnums.GET_TRAINING_CONTENT_REQUEST_COMPLETED).to 10 | .equal('TRAINER_MODULE/GET_TRAINING_CONTENT_REQUEST_COMPLETED'); 11 | expect(trainerActionEnums.TRAINING_CONTENT_CHANGED).to.equal('TRAINER_MODULE/TRAINING_CONTENT_CHANGED'); 12 | expect(trainerActionEnums.UPDATE_EDITOR_CURSOR).to.equal('TRAINER_MODULE/UPDATE_EDITOR_CURSOR'); 13 | expect(trainerActionEnums.TOGGLE_EDITOR_PREVIEW).to.equal('TRAINER_MODULE/TOGGLE_EDITOR_PREVIEW'); 14 | expect(trainerActionEnums.SET_ACTIVE_PANEL).to.equal('TRAINER_MODULE/SET_ACTIVE_PANEL'); 15 | expect(trainerActionEnums.GET_SUMMARY_TRAINING_REQUEST_COMPLETED) 16 | .to.be.equals('TRAINER_MODULE/GET_SUMMARY_TRAINING_REQUEST_COMPLETED'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/common/components/header/header.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router'; 3 | import { UserProfile } from '../../../model/userProfile'; 4 | const styles: any = require('./header.scss'); 5 | 6 | interface Props { 7 | userProfile: UserProfile; 8 | } 9 | 10 | /* tslint:disable:max-line-length */ 11 | export const HeaderComponent: React.StatelessComponent = ({ userProfile }) => { 12 | return ( 13 |
14 |
15 | avatar 16 |
17 |
{userProfile.fullname}
18 |
{userProfile.role}
19 |
20 |
21 |
22 | 23 | Logout 24 | 25 | 26 |
27 |
28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/pages/student/training/toc/components/navigation.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router'; 3 | import { Breadcrumb, BreadcrumbItem } from '../../../../../common/components'; 4 | import { studentRouteEnums } from '../../../../../common/routeEnums/student'; 5 | const teachingImg: any = require('../../../../../content/image/teaching.svg'); 6 | const styles: any = require('../../../../../content/sass/components/navigation.scss'); 7 | 8 | export const NavigationBar: React.StatelessComponent<{}> = () => ( 9 |
10 | 11 | 12 | teaching logo 13 | Lemoncode 14 | 15 | My Trainings 16 | Full Stack Online Máster 17 | 18 |
19 | ); 20 | 21 | NavigationBar.displayName = 'NavigationBar'; 22 | -------------------------------------------------------------------------------- /src/pages/trainer/evaluation/components/studentDeliveryList.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { StudentDeliveryListRowComponent } from './studentDeliveryListRow'; 3 | import { exerciseEvaluationMockedData } from '../../../../rest-api/trainer/exerciseEvaluationMockedData'; 4 | import { StudentDelivery } from '../../../../model/trainer/deliveryEvaluation'; 5 | const styles: any = require('./studentDeliveryList.scss'); 6 | 7 | interface Props { 8 | deliveryList: StudentDelivery[]; 9 | onGradeChange(evaluationId: number, grade: number): void; 10 | } 11 | 12 | export const StudentDeliveryListComponent: React.StatelessComponent = (props) => { 13 | return ( 14 |
15 | {props.deliveryList.map((delivery) => 16 | , 21 | )} 22 |
23 | ); 24 | }; 25 | 26 | StudentDeliveryListComponent.displayName = 'StudentDeliveryListComponent'; 27 | -------------------------------------------------------------------------------- /src/pages/general/login/components/loginForm/loginFormContainer.tsx: -------------------------------------------------------------------------------- 1 | import {connect} from 'react-redux'; 2 | import {IAppState} from '../../../../../reducers'; 3 | import {LoginCredentials} from '../../../../../model/login/loginCredentials'; 4 | import {LoginFormComponent} from './loginForm'; 5 | import {loginContentChangedStartedAction} from '../../actions/loginContentChanged'; 6 | import {loginRequestStartedAction} from '../../actions/loginRequest'; 7 | 8 | const mapStateToProps = (state: IAppState) => ({ 9 | loginCredentials: state.login.editingLogin, 10 | loginErrors: state.login.loginErrors, 11 | }); 12 | 13 | const mapDispatchToProps = (dispatch) => ({ 14 | updateLoginInfo: (viewModel: LoginCredentials, fieldName: string, value: string) => 15 | (dispatch(loginContentChangedStartedAction(viewModel, fieldName, value))), 16 | loginRequest: (loginCredentials: LoginCredentials) => (dispatch(loginRequestStartedAction(loginCredentials))), 17 | }); 18 | 19 | export const LoginFormContainerComponent = connect( 20 | mapStateToProps, 21 | mapDispatchToProps, 22 | )(LoginFormComponent); 23 | -------------------------------------------------------------------------------- /src/pages/general/login/components/loginForm/components/spec/header.spec.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {shallow} from 'enzyme'; 3 | import {multilineTrim} from '../../../../../../../common/parse/multilineTrim'; 4 | import {HeaderComponent} from '../header'; 5 | 6 | describe('Header LoginFormComponent', () => { 7 | it('should be defined', () => { 8 | // Arrange 9 | 10 | // Act 11 | const component = shallow( 12 | , 13 | ); 14 | // Assert 15 | expect(component).not.to.be.undefined; 16 | }); 17 | 18 | it('should render as expected', () => { 19 | // Arrange 20 | const expectedComponent = ` 21 |
22 |

23 |

Please sign in

24 |

(login: admin | trainer | student / pwd: test)

25 |

26 |
27 | `; 28 | 29 | // Act 30 | const component = shallow( 31 | , 32 | ); 33 | // Assert 34 | expect(component.html()).to.equal(multilineTrim(expectedComponent)); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/pages/trainer/routes.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Route, Redirect } from 'react-router'; 3 | import { DashboardPage } from './dashboard/page'; 4 | import { EvaluationPage } from './evaluation/page'; 5 | import { TrainingListPageContainer } from './training/list/pageContainer'; 6 | import { trainerRouteEnums } from '../../common/routeEnums/trainer'; 7 | import { EditTrainingContainerPage } from './training/edit/pageContainer'; 8 | 9 | export const TrainingRoutes = ( 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | ); 20 | -------------------------------------------------------------------------------- /src/pages/admin/training/list/page.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Link} from 'react-router'; 3 | import { AutoSizer } from 'react-virtualized'; 4 | import { TrainingSummary } from '../../../../model/trainingSummary'; 5 | import { TrainingTableComponent } from '../list/components/trainingTable'; 6 | import {adminRouteEnums} from '../../../../common/routeEnums/admin'; 7 | 8 | interface Props extends React.Props { 9 | trainingList: TrainingSummary[]; 10 | fetchTrainings: () => void; 11 | } 12 | 13 | export class ListTrainingPage extends React.Component { 14 | public componentDidMount() { 15 | this.props.fetchTrainings(); 16 | } 17 | 18 | public render() { 19 | return ( 20 |
21 |

Active Trainigs

22 | 23 | {({ width }) => } 24 | 25 | Go back to dashboard 26 |
27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/pages/trainer/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router'; 3 | import { DashboardComponent, IDashboardItem, dashboardIcons } from '../../../common/components/dashboard'; 4 | import { trainerRouteEnums } from '../../../common/routeEnums/trainer'; 5 | import { NavigationBar } from './components/navigation'; 6 | const styles: any = require('./pageStyles.scss'); 7 | 8 | export class DashboardPage extends React.Component<{}, {}> { 9 | private dashboardItems: IDashboardItem[] = [ 10 | { 11 | icon: dashboardIcons.evaluation, 12 | name: 'Student evaluation', 13 | linkTo: `${trainerRouteEnums.training.base}/1/evaluation`, 14 | }, 15 | { 16 | icon: dashboardIcons.training, 17 | name: 'Edit training content', 18 | linkTo: `${trainerRouteEnums.training.base}/1/edit`, 19 | }, 20 | ]; 21 | public render() { 22 | return ( 23 |
24 | 25 |

Trainer dashboard

26 | 27 |
28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/pages/trainer/training/edit/components/editorStyles.scss: -------------------------------------------------------------------------------- 1 | // TODO: I am not very keen on hardcoding measures in pixels, otherwise 2 | // we will end up with pixels everywhere, and adjusting the layout will 3 | // be a matter of tweaking here and there. 4 | // Heights, widths and similar important measures should go relative. 5 | // Margins and paddings could be in pixels for ease of use, but at least 6 | // they sould be centralized in one place. 7 | 8 | @mixin genericFlexContainer($direction) { 9 | display: flex; 10 | flex-direction: $direction; 11 | flex: 1 1 auto; 12 | } 13 | 14 | @mixin genericFlexItem { 15 | flex: 1 1 0; 16 | margin: 10px; 17 | } 18 | 19 | .editorContainer { 20 | @include genericFlexContainer(column); 21 | } 22 | 23 | .markdownArea { 24 | @include genericFlexContainer(row); 25 | 26 | margin: -10px; 27 | height: 400px; // TODO: why? 28 | } 29 | 30 | .textArea { 31 | @include genericFlexItem(); 32 | 33 | resize: none; 34 | } 35 | 36 | .previewArea { 37 | @include genericFlexItem(); 38 | 39 | & img { 40 | max-width: 100%; 41 | } 42 | 43 | overflow: auto; 44 | } 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { RouteComponentProps } from 'react-router'; 3 | import { HeaderComponent } from './common/components/header/header'; 4 | import { UserProfile } from './model/userProfile'; 5 | import { FooterComponent } from './common/components/footer/footer'; 6 | const styles: any = require('./app.scss'); 7 | 8 | interface Props extends RouteComponentProps<{}, {}> { 9 | userProfile: UserProfile; 10 | } 11 | 12 | export const App: React.StatelessComponent = (props) => { 13 | let header = null; 14 | 15 | // Show header if route is not the login one 16 | if (isNotLoginRoute(props.location.pathname)) { 17 | header = ; 18 | } 19 | 20 | return ( 21 |
22 | {header} 23 |
24 | {props.children} 25 |
26 | 27 |
28 | ); 29 | }; 30 | 31 | App.displayName = 'App'; 32 | 33 | function isNotLoginRoute(path: string): boolean { 34 | const loginRoutes = ['/', '/home']; 35 | return loginRoutes.indexOf(path) === -1; 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 MasterLemon2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/common/components/dashboard/dashboard.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | const classNames: any = require('./dashboardStyles.scss'); 3 | import {DashboardItemComponent, IDashboardItem} from './components/dashboardItem'; 4 | 5 | interface IProps { 6 | items: IDashboardItem[]; 7 | } 8 | 9 | export class DashboardComponent extends React.Component { 10 | public render() { 11 | return( 12 |
13 |
14 | { 15 | this.props.items.map((item) => 16 | , 21 | ) 22 | } 23 |
24 |
25 | ); 26 | } 27 | 28 | private getItemsPerRowStyle(items: IDashboardItem[]): React.CSSProperties { 29 | const maxItemsPerRow = 3; 30 | 31 | return items.length >= maxItemsPerRow ? 32 | {flexBasis: `${100 / maxItemsPerRow}%`} : 33 | {flexBasis: `${100 / items.length}%`}; 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/pages/general/notFound/components/spec/header.spec.tsx: -------------------------------------------------------------------------------- 1 | import {shallow} from 'enzyme'; 2 | import * as React from 'react'; 3 | import {multilineTrim} from '../../../../../common/parse/multilineTrim'; 4 | import {NotFoundHeader} from '../header'; 5 | 6 | describe('general/notFound/components/header', () => { 7 | it('is defined', () => { 8 | // Arrange 9 | 10 | // Act 11 | const component = shallow( 12 | , 13 | ); 14 | 15 | // Assert 16 | expect(component).not.to.be.undefined; 17 | }); 18 | 19 | it('renders as expected', () => { 20 | // Arrange 21 | 22 | // Act 23 | const component = shallow( 24 | , 25 | ); 26 | 27 | // Assert 28 | 29 | const expectedDomTree = ` 30 |
31 |

32 | Oops: 33 | Page not found - 404 error 34 |

35 |
36 | `; 37 | 38 | expect(component.html()).to.be.equal(multilineTrim(expectedDomTree)); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/pages/student/training/list/page.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router'; 3 | import { AutoSizer } from 'react-virtualized'; 4 | import { studentRouteEnums } from '../../../../common/routeEnums/student'; 5 | import { TrainingSummary } from '../../../../model/trainingSummary'; 6 | import { TrainingTableComponent } from './components/trainingTable'; 7 | import { NavigationBar } from './components/navigation'; 8 | 9 | interface IProps extends React.Props { 10 | trainingList: TrainingSummary[]; 11 | studentId: string; 12 | fetchTrainings: (studentId: string) => void; 13 | } 14 | 15 | export class TrainingListPage extends React.Component { 16 | public componentDidMount() { 17 | this.props.fetchTrainings(this.props.studentId); 18 | } 19 | 20 | public render() { 21 | return ( 22 |
23 | 24 |

My trainings

25 | 26 | {({ width }) => } 27 | 28 |
29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/pages/trainer/dashboard/components/navigation.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router'; 3 | import { Breadcrumb, BreadcrumbItem, CounterButton } from '../../../../common/components'; 4 | const teachingImg: any = require('../../../../content/image/teaching.svg'); 5 | const styles: any = require('../../../../content/sass/components/navigation.scss'); 6 | 7 | export const NavigationBar: React.StatelessComponent<{}> = () => ( 8 | 9 | 10 | teaching logo 11 | Lemoncode 12 | 13 | My Trainings 14 | Master Full Stack 15 | 16 | 21 | 22 | 23 | ); 24 | 25 | NavigationBar.displayName = 'NavigationBar'; 26 | -------------------------------------------------------------------------------- /config/webpack/app/webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const merge = require('webpack-merge'); 3 | const base = require('./webpack.config.base'); 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | const helpers = require('../../helpers'); 6 | 7 | const hotReloadingEntries = [ 8 | 'react-hot-loader/patch', 9 | ]; 10 | 11 | module.exports = merge.strategy({ 12 | entry: 'prepend', 13 | })(base, { 14 | devtool: 'inline-source-map', 15 | entry: { 16 | app: hotReloadingEntries, 17 | appStyles: hotReloadingEntries, 18 | }, 19 | output: { 20 | path: helpers.resolveFromRootPath('dist'), 21 | filename: '[name].js', 22 | }, 23 | devServer: { 24 | contentBase: helpers.resolveFromRootPath('dist'), 25 | inline: true, 26 | host: 'localhost', 27 | port: 8080, 28 | stats: 'errors-only', 29 | hot: true, 30 | }, 31 | plugins: [ 32 | // enable HMR globally 33 | new webpack.HotModuleReplacementPlugin(), 34 | // Display in console what modules are hot reloaded 35 | new webpack.NamedModulesPlugin(), 36 | new ExtractTextPlugin({ 37 | disable: true, 38 | }), 39 | ], 40 | }); 41 | -------------------------------------------------------------------------------- /src/rest-api/trainingMockData.ts: -------------------------------------------------------------------------------- 1 | import { Student } from '../model/student'; 2 | import { Trainer } from '../model/trainer'; 3 | import { Training } from '../model/training'; 4 | 5 | export const trainingMockData: Training[] = [ 6 | { 7 | end: new Date(2017, 0, 31), 8 | id: '32', 9 | isActive: true, 10 | name: 'React/Redux', 11 | start: new Date(2017, 0, 1), 12 | students: [], 13 | trainers: [], 14 | }, 15 | { 16 | end: new Date(2017, 1, 28), 17 | id: '12', 18 | isActive: true, 19 | name: 'Responsive web design', 20 | start: new Date(2017, 1, 1), 21 | students: [], 22 | trainers: [], 23 | }, 24 | { 25 | end: new Date(2017, 2, 31), 26 | id: '33', 27 | isActive: true, 28 | name: 'AngularJS 2.0', 29 | start: new Date(2017, 2, 1), 30 | students: [], 31 | trainers: [], 32 | }, 33 | { 34 | end: new Date(2017, 0, 31), 35 | id: '34', 36 | isActive: true, 37 | name: 'Full Stack Online Máster', 38 | start: new Date(2017, 0, 1), 39 | students: [{ id: '3', fullname: 'Student', isActive: true, email: 'student', phoneNumber: '' }], 40 | trainers: [{ id: '2' }], 41 | }, 42 | ]; 43 | -------------------------------------------------------------------------------- /src/common/components/panel/panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as CSSTransitionReplace from 'react-css-transition-replace'; 3 | 4 | export class PanelItem { 5 | public panelId: string; 6 | public component: (React.ComponentClass | React.StatelessComponent); 7 | } 8 | 9 | interface Props { 10 | activePanelId: string; 11 | panelList: PanelItem[]; 12 | } 13 | 14 | const renderSelectedComponent = ({ activePanelId, panelList }: Props) => { 15 | let panelItem =
; 16 | const panel = panelList.find((currentPanel) => currentPanel.panelId === activePanelId); 17 | 18 | if (panel) { 19 | const Component = panel.component; 20 | if (Component) { 21 | panelItem = ; 22 | } 23 | } 24 | 25 | return ( 26 | 27 | {panelItem} 28 | 29 | ); 30 | }; 31 | 32 | export const PanelComponent: React.StatelessComponent = (props) => { 33 | return renderSelectedComponent(props); 34 | }; 35 | 36 | PanelComponent.displayName = 'PanelComponent'; 37 | -------------------------------------------------------------------------------- /config/webpack/webpack.config.common.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const helpers = require('../helpers'); 3 | 4 | module.exports = { 5 | resolve: { 6 | extensions: ['.js', '.ts', '.tsx'], 7 | alias: { 8 | 'globalStyles': helpers.resolveFromRootPath("src/content/sass"), 9 | }, 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.tsx?$/, 15 | enforce: 'pre', 16 | loader: 'tslint-loader', 17 | }, 18 | { 19 | test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/, 20 | loader: 'url-loader?limit=10000&mimetype=application/font-woff', 21 | }, 22 | { 23 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 24 | loader: 'url-loader?limit=10000&mimetype=application/octet-stream', 25 | }, 26 | { 27 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 28 | loader: 'file-loader', 29 | }, 30 | { 31 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 32 | loader: 'url-loader?limit=10000&mimetype=image/svg+xml', 33 | }, 34 | { 35 | test: /\.(png|jpg|ico)?$/, 36 | loader: 'url-loader?limit=10000&mimetype=image/png', 37 | }, 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/pages/admin/student/edit/page.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Link } from 'react-router'; 3 | import { adminRouteEnums } from '../../../../common/routeEnums/admin'; 4 | import { StudentSummary } from '../../../../model/studentSummary'; 5 | 6 | interface Props { 7 | student: StudentSummary; 8 | getStudent: (id: string) => void; 9 | studentId: string; 10 | } 11 | 12 | export class EditStudentPage extends React.Component { 13 | public componentDidMount() { 14 | const studentId: string = this.props.studentId; 15 | this.props.getStudent(studentId); 16 | } 17 | 18 | public render() { 19 | return ( 20 |
21 | Student name: {this.props.student.fullname} 22 |
23 | Email: {this.props.student.email} 24 |
25 | Is Active?: {this.props.student.isActive ? 'Yes' : 'No'} 26 |
27 | Back to student list 28 | Back to Dashboard 29 | 30 |
31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LeanMood Front end ![build status](https://travis-ci.org/Lemoncode/LeanMood.svg?branch=master "Build Status") 2 | 3 | Work in progress. 4 | 5 | LeanMood frontend project will be a React / Redux based project. 6 | 7 | Backend project will be hosted in the following [repository](https://github.com/MasterLemon2016/LeanMoodBackend). 8 | 9 | # How to get started 10 | 11 | Clone or download this project. 12 | 13 | From the root folder of the project execute (command prompt): 14 | 15 | ``` 16 | npm install 17 | ``` 18 | 19 | Then 20 | 21 | ``` 22 | npm start 23 | ``` 24 | 25 | The open a browser and navigate to http://localhost:8080 26 | 27 | # Project info 28 | 29 | About the project definition you can visit our 30 | [wiki](https://github.com/MasterLemon2016/LeanMood/wiki) 31 | 32 | # About Lemoncode 33 | 34 | We are a team of long-term experienced freelance developers, established as a group in 2010. 35 | We specialize in Front End technologies and .NET. [Click here](http://lemoncode.net/services/en/#en-home) to get more info about us. 36 | 37 | For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/pages/admin/routes.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Route } from 'react-router'; 3 | import { DashboardPage} from './dashboard/page'; 4 | import { EditStudentPageContainer } from './student/edit/pageContainer'; 5 | import { ListStudentPageContainer } from './student/list/pageContainer'; 6 | import { EditTrainingPage } from './training/edit/page'; 7 | import { ListTrainingPageContainer } from './training/list/pageContainer'; 8 | import {adminRouteEnums} from '../../common/routeEnums/admin'; 9 | 10 | // http://stackoverflow.com/questions/35048738/react-router-import-routes 11 | // AssembliesRoutes.js 12 | // http://randycoulman.com/blog/2016/02/02/composing-routes-in-react-router/ 13 | export const AdminRoutes = ( 14 |
15 | 16 | 17 | 18 | 19 | 20 |
21 | ); 22 | -------------------------------------------------------------------------------- /src/rest-api/student.ts: -------------------------------------------------------------------------------- 1 | import { Student } from '../model/student'; 2 | import { StudentSummary } from '../model/studentSummary'; 3 | import { studentMockData } from './studentMockData'; 4 | 5 | class StudentApi { 6 | public studentList: Student[]; 7 | 8 | constructor() { 9 | this.studentList = studentMockData; 10 | } 11 | 12 | public setMockDataSeed(studentList: Student[]) { 13 | this.studentList = studentList; 14 | } 15 | 16 | public getSummaryStudentList(): Promise { 17 | const studentSummaryList: StudentSummary[] = this.studentList.map((student) => { 18 | const summary = new StudentSummary(); 19 | summary.id = student.id; 20 | summary.fullname = student.fullname; 21 | summary.email = student.email; 22 | summary.isActive = student.isActive; 23 | 24 | return summary; 25 | }); 26 | 27 | return Promise.resolve(studentSummaryList); 28 | } 29 | 30 | public getStudentById(id: string): Promise { 31 | const studentSummary: StudentSummary = this.studentList.find((st) => st.id === id); 32 | return Promise.resolve(studentSummary); 33 | } 34 | } 35 | 36 | export const studentApi = new StudentApi(); 37 | -------------------------------------------------------------------------------- /src/common/components/form/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ValidationComponent } from './validation'; 3 | import { CommonInputProps } from './'; 4 | 5 | export interface TextAreaComponentProps extends CommonInputProps { 6 | cols?: number; 7 | rows?: number; 8 | className?: string; 9 | } 10 | 11 | export const TextAreaComponent: React.StatelessComponent = (props) => { 12 | let className = 'form-control'; 13 | if (props.className) { 14 | className = `${className} ${props.className}`; 15 | } 16 | return ( 17 | 18 | 21 |
22 |