diff --git a/src/commons/application/actions/__tests__/SessionActions.ts b/src/commons/application/actions/__tests__/SessionActions.ts index 4b336e82fc..9e3773434a 100644 --- a/src/commons/application/actions/__tests__/SessionActions.ts +++ b/src/commons/application/actions/__tests__/SessionActions.ts @@ -228,6 +228,7 @@ test('setCourseConfiguration generates correct action object', () => { enableGame: true, enableAchievements: true, enableSourcecast: true, + enableStories: false, sourceChapter: Chapter.SOURCE_1, sourceVariant: Variant.DEFAULT, moduleHelpText: 'Help text', @@ -612,6 +613,7 @@ test('updateCourseConfig generates correct action object', () => { enableGame: true, enableAchievements: true, enableSourcecast: true, + enableStories: false, sourceChapter: Chapter.SOURCE_1, sourceVariant: Variant.DEFAULT, moduleHelpText: 'Help text', diff --git a/src/commons/application/reducers/__tests__/SessionReducer.ts b/src/commons/application/reducers/__tests__/SessionReducer.ts index cbfd2c0e52..8d622a5a15 100644 --- a/src/commons/application/reducers/__tests__/SessionReducer.ts +++ b/src/commons/application/reducers/__tests__/SessionReducer.ts @@ -96,6 +96,7 @@ test('SET_COURSE_CONFIGURATION works correctly', () => { enableGame: true, enableAchievements: true, enableSourcecast: true, + enableStories: false, sourceChapter: Chapter.SOURCE_1, sourceVariant: Variant.DEFAULT, moduleHelpText: 'Help text', diff --git a/src/commons/application/types/SessionTypes.ts b/src/commons/application/types/SessionTypes.ts index f1b5d12681..fbf069b11f 100644 --- a/src/commons/application/types/SessionTypes.ts +++ b/src/commons/application/types/SessionTypes.ts @@ -103,6 +103,7 @@ export type SessionState = { readonly enableGame?: boolean; readonly enableAchievements?: boolean; readonly enableSourcecast?: boolean; + readonly enableStories?: boolean; readonly sourceChapter?: number; readonly sourceVariant?: Variant; readonly moduleHelpText?: string; @@ -168,6 +169,7 @@ export type CourseConfiguration = { enableGame: boolean; enableAchievements: boolean; enableSourcecast: boolean; + enableStories: boolean; sourceChapter: Chapter; sourceVariant: Variant; moduleHelpText: string; diff --git a/src/commons/dropdown/DropdownCreateCourse.tsx b/src/commons/dropdown/DropdownCreateCourse.tsx index 7c5b7f70e1..f69c7b8da5 100644 --- a/src/commons/dropdown/DropdownCreateCourse.tsx +++ b/src/commons/dropdown/DropdownCreateCourse.tsx @@ -38,6 +38,7 @@ const DropdownCreateCourse: React.FC = props => { enableGame: true, enableAchievements: true, enableSourcecast: true, + enableStories: false, sourceChapter: Chapter.SOURCE_1, sourceVariant: Variant.DEFAULT, moduleHelpText: '' @@ -222,6 +223,18 @@ const DropdownCreateCourse: React.FC = props => { }) } /> + + + setCourseConfig({ + ...courseConfig, + enableStories: (e.target as HTMLInputElement).checked + }) + } + />
diff --git a/src/commons/mocks/UserMocks.ts b/src/commons/mocks/UserMocks.ts index 35b1737da0..0517e6a96e 100644 --- a/src/commons/mocks/UserMocks.ts +++ b/src/commons/mocks/UserMocks.ts @@ -130,6 +130,7 @@ export const mockCourseConfigurations: CourseConfiguration[] = [ enableGame: false, enableAchievements: true, enableSourcecast: true, + enableStories: false, sourceChapter: Chapter.SOURCE_1, sourceVariant: Variant.DEFAULT, moduleHelpText: '', @@ -142,6 +143,7 @@ export const mockCourseConfigurations: CourseConfiguration[] = [ enableGame: false, enableAchievements: false, enableSourcecast: false, + enableStories: false, sourceChapter: Chapter.SOURCE_2, sourceVariant: Variant.DEFAULT, moduleHelpText: 'Help Text!', diff --git a/src/commons/navigationBar/NavigationBar.tsx b/src/commons/navigationBar/NavigationBar.tsx index 3a8a331886..9b71994531 100644 --- a/src/commons/navigationBar/NavigationBar.tsx +++ b/src/commons/navigationBar/NavigationBar.tsx @@ -55,6 +55,7 @@ const NavigationBar: React.FC = () => { courseShortName, enableAchievements, enableSourcecast, + enableStories, assessmentConfigurations } = useSession(); const assessmentTypes = useMemo( @@ -180,14 +181,21 @@ const NavigationBar: React.FC = () => { disabled: !(isEnrolledInACourse && enableAchievements) }, { - to: '/stories', + to: `/courses/${courseId}/stories`, icon: IconNames.GIT_REPO, text: 'Stories', - // TODO: Enable when stories are implemented - disabled: true && !isLoggedIn + // TODO: Enable for public deployment + disabled: !(isEnrolledInACourse && enableStories) } ]; - }, [isLoggedIn, isEnrolledInACourse, courseId, enableSourcecast, enableAchievements]); + }, [ + courseId, + isEnrolledInACourse, + enableSourcecast, + enableStories, + isLoggedIn, + enableAchievements + ]); const fullAcademyMobileNavbarLeftAdditionalInfo = useMemo( () => getAcademyNavbarRightInfo({ isEnrolledInACourse, courseId, role }), @@ -359,14 +367,14 @@ const playgroundOnlyNavbarLeftInfo: NavbarEntryInfo[] = [ to: '/sicpjs', icon: IconNames.BOOK, text: 'SICP JS' - }, - { - to: '/stories', - icon: IconNames.GIT_REPO, - text: 'Stories', - // TODO: Enable when stories are implemented - disabled: true } + // { + // to: '/stories', + // icon: IconNames.GIT_REPO, + // text: 'Stories', + // // TODO: Enable for public deployment + // disabled: true + // } ]; export const renderNavlinksFromInfo = ( diff --git a/src/commons/navigationBar/__tests__/__snapshots__/NavigationBar.tsx.snap b/src/commons/navigationBar/__tests__/__snapshots__/NavigationBar.tsx.snap index 04091d52bc..bf6cdb8b8b 100644 --- a/src/commons/navigationBar/__tests__/__snapshots__/NavigationBar.tsx.snap +++ b/src/commons/navigationBar/__tests__/__snapshots__/NavigationBar.tsx.snap @@ -347,20 +347,6 @@ exports[`NavigationBar Renders correctly for student with course 1`] = ` Achievements
- - -
- Stories -
-
- - -
- Stories -
-
component. @@ -245,8 +249,12 @@ function* BackendSaga(): SagaIterator { yield put(actions.setCourseConfiguration(courseConfiguration)); yield put(actions.setAssessmentConfigurations(assessmentConfigurations)); - yield put(actions.getStoriesUser()); - // TODO: Fetch associated stories group ID + if (courseConfiguration.enableStories) { + yield put(actions.getStoriesUser()); + // TODO: Fetch associated stories group ID + } else { + yield put(actions.clearStoriesUserAndGroup()); + } } } ); @@ -257,8 +265,12 @@ function* BackendSaga(): SagaIterator { if (config) { yield put(actions.setCourseConfiguration(config)); - yield put(actions.getStoriesUser()); - // TODO: Fetch associated stories group ID + if (config.enableStories) { + yield put(actions.getStoriesUser()); + // TODO: Fetch associated stories group ID + } else { + yield put(actions.clearStoriesUserAndGroup()); + } } }); @@ -724,8 +736,12 @@ function* BackendSaga(): SagaIterator { yield put(actions.setAssessmentConfigurations(assessmentConfigurations)); yield put(actions.setCourseRegistration(courseRegistration)); - yield put(actions.getStoriesUser()); - // TODO: Fetch associated stories group ID + if (courseConfiguration.enableStories) { + yield put(actions.getStoriesUser()); + // TODO: Fetch associated stories group ID + } else { + yield put(actions.clearStoriesUserAndGroup()); + } yield call(showSuccessMessage, `Switched to ${courseConfiguration.courseName}!`, 5000); } @@ -742,6 +758,13 @@ function* BackendSaga(): SagaIterator { return yield handleResponseError(resp); } + if (courseConfig.enableStories) { + yield put(actions.getStoriesUser()); + // TODO: Fetch associated stories group ID + } else { + yield put(actions.clearStoriesUserAndGroup()); + } + yield put(actions.setCourseConfiguration(courseConfig)); yield call(showSuccessMessage, 'Updated successfully!', 1000); } @@ -945,6 +968,13 @@ function* BackendSaga(): SagaIterator { yield put(actions.setUser(user)); yield put(actions.setCourseRegistration({ role: Role.Student })); + if (courseConfiguration.enableStories) { + yield put(actions.getStoriesUser()); + // TODO: Fetch associated stories group ID + } else { + yield put(actions.clearStoriesUserAndGroup()); + } + const placeholderAssessmentConfig = [ { type: 'Missions', diff --git a/src/commons/sagas/__tests__/BackendSaga.ts b/src/commons/sagas/__tests__/BackendSaga.ts index 164d0d57ff..5525f1b47c 100644 --- a/src/commons/sagas/__tests__/BackendSaga.ts +++ b/src/commons/sagas/__tests__/BackendSaga.ts @@ -173,6 +173,7 @@ const mockCourseConfiguration1: CourseConfiguration = { enableGame: true, enableAchievements: true, enableSourcecast: true, + enableStories: false, sourceChapter: Chapter.SOURCE_1, sourceVariant: Variant.DEFAULT, moduleHelpText: 'Help text', @@ -203,6 +204,7 @@ const mockCourseConfiguration2: CourseConfiguration = { enableGame: true, enableAchievements: true, enableSourcecast: true, + enableStories: false, sourceChapter: Chapter.SOURCE_4, sourceVariant: Variant.DEFAULT, moduleHelpText: 'Help text', @@ -887,6 +889,7 @@ describe('Test UPDATE_COURSE_CONFIG action', () => { enableGame: false, enableAchievements: false, enableSourcecast: false, + enableStories: false, sourceChapter: Chapter.SOURCE_4, sourceVariant: Variant.DEFAULT, moduleHelpText: 'Help', @@ -984,6 +987,7 @@ describe('Test CREATE_COURSE action', () => { enableGame: true, enableAchievements: true, enableSourcecast: true, + enableStories: false, sourceChapter: Chapter.SOURCE_1, sourceVariant: Variant.DEFAULT, moduleHelpText: 'Help Text' diff --git a/src/features/academy/__tests__/AcademyActions.ts b/src/features/academy/__tests__/AcademyActions.ts index 6e0b1f059e..fa02cadf36 100644 --- a/src/features/academy/__tests__/AcademyActions.ts +++ b/src/features/academy/__tests__/AcademyActions.ts @@ -31,6 +31,7 @@ test('createCourse generates correct action object', () => { enableGame: true, enableAchievements: true, enableSourcecast: true, + enableStories: false, sourceChapter: Chapter.SOURCE_1, sourceVariant: Variant.DEFAULT, moduleHelpText: 'Help Text' diff --git a/src/features/stories/StoriesActions.ts b/src/features/stories/StoriesActions.ts index f48dcc67d7..d27b7df4e9 100644 --- a/src/features/stories/StoriesActions.ts +++ b/src/features/stories/StoriesActions.ts @@ -4,6 +4,7 @@ import { action } from 'typesafe-actions'; import { ADD_STORY_ENV, + CLEAR_STORIES_USER_AND_GROUP, CLEAR_STORY_ENV, CREATE_STORY, DELETE_STORY, @@ -78,3 +79,5 @@ export const setCurrentStoriesGroup = ( name: string | undefined, role: StoriesRole | undefined ) => action(SET_CURRENT_STORIES_GROUP, { id, name, role }); +// Helper/wrapper actions +export const clearStoriesUserAndGroup = () => action(CLEAR_STORIES_USER_AND_GROUP); diff --git a/src/features/stories/StoriesReducer.ts b/src/features/stories/StoriesReducer.ts index 5213fce320..52308b5e44 100644 --- a/src/features/stories/StoriesReducer.ts +++ b/src/features/stories/StoriesReducer.ts @@ -13,6 +13,7 @@ import { SourceActionType } from '../../commons/utils/ActionsHelper'; import { DEFAULT_ENV } from './storiesComponents/UserBlogContent'; import { ADD_STORY_ENV, + CLEAR_STORIES_USER_AND_GROUP, CLEAR_STORY_ENV, EVAL_STORY, EVAL_STORY_ERROR, @@ -212,6 +213,15 @@ export const StoriesReducer: Reducer = ( ...state, currentStory: action.payload }; + case CLEAR_STORIES_USER_AND_GROUP: + return { + ...state, + userId: undefined, + userName: undefined, + groupId: undefined, + groupName: undefined, + role: undefined + }; case SET_CURRENT_STORIES_USER: return { ...state, diff --git a/src/features/stories/StoriesTypes.ts b/src/features/stories/StoriesTypes.ts index ee184e33f7..524210e5b2 100644 --- a/src/features/stories/StoriesTypes.ts +++ b/src/features/stories/StoriesTypes.ts @@ -21,6 +21,7 @@ export const SAVE_STORY = 'SAVE_STORY'; export const DELETE_STORY = 'DELETE_STORY'; // Auth-related actions export const GET_STORIES_USER = 'GET_STORIES_USER'; +export const CLEAR_STORIES_USER_AND_GROUP = 'CLEAR_STORIES_USER_AND_GROUP'; // TODO: Investigate possibility of combining the two actions export const SET_CURRENT_STORIES_USER = 'SET_CURRENT_STORIES_USER'; export const SET_CURRENT_STORIES_GROUP = 'SET_CURRENT_STORIES_GROUP'; diff --git a/src/pages/__tests__/localStorage.test.ts b/src/pages/__tests__/localStorage.test.ts index 00123fac05..d26314adba 100644 --- a/src/pages/__tests__/localStorage.test.ts +++ b/src/pages/__tests__/localStorage.test.ts @@ -20,6 +20,7 @@ const mockShortDefaultState: SavedState = { enableGame: defaultState.session.enableGame, enableAchievements: defaultState.session.enableAchievements, enableSourcecast: defaultState.session.enableSourcecast, + enableStories: defaultState.session.enableStories, moduleHelpText: defaultState.session.moduleHelpText, assetsPrefix: defaultState.session.assetsPrefix, assessmentConfigurations: defaultState.session.assessmentConfigurations diff --git a/src/pages/academy/adminPanel/AdminPanel.tsx b/src/pages/academy/adminPanel/AdminPanel.tsx index 1b6415219b..14b9b634bd 100644 --- a/src/pages/academy/adminPanel/AdminPanel.tsx +++ b/src/pages/academy/adminPanel/AdminPanel.tsx @@ -45,6 +45,7 @@ const AdminPanel: React.FC = () => { enableGame: true, enableAchievements: true, enableSourcecast: true, + enableStories: false, moduleHelpText: '' }); @@ -84,6 +85,7 @@ const AdminPanel: React.FC = () => { enableGame: session.enableGame, enableAchievements: session.enableAchievements, enableSourcecast: session.enableSourcecast, + enableStories: session.enableStories, moduleHelpText: session.moduleHelpText }); diff --git a/src/pages/academy/adminPanel/subcomponents/CourseConfigPanel.tsx b/src/pages/academy/adminPanel/subcomponents/CourseConfigPanel.tsx index 621c5ebe83..cb66bf41d1 100644 --- a/src/pages/academy/adminPanel/subcomponents/CourseConfigPanel.tsx +++ b/src/pages/academy/adminPanel/subcomponents/CourseConfigPanel.tsx @@ -39,6 +39,7 @@ const CourseConfigPanel: React.FC = props => { enableGame, enableAchievements, enableSourcecast, + enableStories, moduleHelpText } = props.courseConfiguration; @@ -177,6 +178,16 @@ const CourseConfigPanel: React.FC = props => { }) } /> + + props.setCourseConfiguration({ + ...props.courseConfiguration, + enableStories: (e.target as HTMLInputElement).checked + }) + } + /> diff --git a/src/pages/localStorage.ts b/src/pages/localStorage.ts index 83fef9cebc..261f8b8c1d 100644 --- a/src/pages/localStorage.ts +++ b/src/pages/localStorage.ts @@ -68,6 +68,7 @@ export const saveState = (state: OverallState) => { enableGame: state.session.enableGame, enableAchievements: state.session.enableAchievements, enableSourcecast: state.session.enableSourcecast, + enableStories: state.session.enableStories, moduleHelpText: state.session.moduleHelpText, assetsPrefix: state.session.assetsPrefix, assessmentConfigurations: state.session.assessmentConfigurations, diff --git a/src/pages/stories/Stories.tsx b/src/pages/stories/Stories.tsx index b877ab1020..85357d9501 100644 --- a/src/pages/stories/Stories.tsx +++ b/src/pages/stories/Stories.tsx @@ -32,7 +32,7 @@ const Stories: React.FC = () => { const isStoriesDisabled = useTypedSelector(state => !state.stories.groupId); const isLoggedIn = !!storiesUserId; - const handleNewStory = useCallback(() => navigate('/stories/new'), [navigate]); + const handleNewStory = useCallback(() => navigate('./new'), [navigate]); const handleDeleteStory = useCallback( async (id: number) => { const confirm = await showSimpleConfirmDialog({ diff --git a/src/pages/stories/StoryActions.tsx b/src/pages/stories/StoryActions.tsx index 2b32c50c26..db5f1e020c 100644 --- a/src/pages/stories/StoryActions.tsx +++ b/src/pages/stories/StoryActions.tsx @@ -32,7 +32,7 @@ const StoryActions: React.FC = ({ return ( {canView && ( - + } @@ -42,7 +42,7 @@ const StoryActions: React.FC = ({ )} {canEdit && ( - + } diff --git a/src/routes/routerConfig.tsx b/src/routes/routerConfig.tsx index b916aa374d..0975b7d4b2 100644 --- a/src/routes/routerConfig.tsx +++ b/src/routes/routerConfig.tsx @@ -157,20 +157,24 @@ export const getFullAcademyRouterConfig = ({ lazy: MissionControl }, { - path: 'stories/new', - lazy: EditStory + path: 'courses/:courseId/stories/new', + lazy: EditStory, + loader: ensureUserAndRole }, { - path: 'stories/view/:id', - lazy: ViewStory + path: 'courses/:courseId/stories/view/:id', + lazy: ViewStory, + loader: ensureUserAndRole }, { - path: 'stories/edit/:id', - lazy: EditStory + path: 'courses/:courseId/stories/edit/:id', + lazy: EditStory, + loader: ensureUserAndRole }, { - path: 'stories', - lazy: Stories + path: 'courses/:courseId/stories', + lazy: Stories, + loader: ensureUserAndRole }, ...commonChildrenRoutes, {