Add TypeScript types to the redux state (#2394)
Adds some TypeScript types to the global redux state that's in `src/store.ts`. I've only added types for a few parts of the state but already it's caught quite a few bugs in the code, which I've tried to fix in this PR.
This commit is contained in:
@@ -43,7 +43,7 @@ export const COURSE_CREATOR_STATES = {
|
||||
granted: 'granted',
|
||||
denied: 'denied',
|
||||
disallowedForThisSite: 'disallowed_for_this_site',
|
||||
};
|
||||
} as const;
|
||||
|
||||
export const DECODED_ROUTES = {
|
||||
COURSE_UNIT: [
|
||||
@@ -3,7 +3,7 @@
|
||||
* @readonly
|
||||
* @enum {string}
|
||||
*/
|
||||
export const RequestStatus = /** @type {const} */ ({
|
||||
export const RequestStatus = {
|
||||
IN_PROGRESS: 'in-progress',
|
||||
SUCCESSFUL: 'successful',
|
||||
FAILED: 'failed',
|
||||
@@ -13,7 +13,8 @@ export const RequestStatus = /** @type {const} */ ({
|
||||
PARTIAL: 'partial',
|
||||
PARTIAL_FAILURE: 'partial failure',
|
||||
NOT_FOUND: 'not-found',
|
||||
});
|
||||
} as const;
|
||||
export type RequestStatusType = (typeof RequestStatus)[keyof typeof RequestStatus];
|
||||
|
||||
export const RequestFailureStatuses = [
|
||||
RequestStatus.FAILED,
|
||||
@@ -25,39 +26,37 @@ export const RequestFailureStatuses = [
|
||||
/**
|
||||
* Team sizes enum
|
||||
* @enum
|
||||
* @type {{MIN: number, MAX: number, DEFAULT: number}}
|
||||
*/
|
||||
export const TeamSizes = /** @type {const} */ ({
|
||||
export const TeamSizes = {
|
||||
DEFAULT: 5,
|
||||
MIN: 1,
|
||||
MAX: 500,
|
||||
});
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Group types enum
|
||||
* @enum
|
||||
* @type {{PRIVATE_MANAGED: string, PUBLIC_MANAGED: string, OPEN: string}}
|
||||
*/
|
||||
export const GroupTypes = /** @type {const} */ ({
|
||||
export const GroupTypes = {
|
||||
OPEN: 'open',
|
||||
PUBLIC_MANAGED: 'public_managed',
|
||||
PRIVATE_MANAGED: 'private_managed',
|
||||
OPEN_MANAGED: 'open_managed',
|
||||
});
|
||||
} as const;
|
||||
|
||||
export const DivisionSchemes = /** @type {const} */ ({
|
||||
export const DivisionSchemes = {
|
||||
NONE: 'none',
|
||||
COHORT: 'cohort',
|
||||
});
|
||||
} as const;
|
||||
|
||||
export const VisibilityTypes = /** @type {const} */ ({
|
||||
export const VisibilityTypes = {
|
||||
GATED: 'gated',
|
||||
LIVE: 'live',
|
||||
STAFF_ONLY: 'staff_only',
|
||||
HIDE_AFTER_DUE: 'hide_after_due',
|
||||
UNSCHEDULED: 'unscheduled',
|
||||
NEEDS_ATTENTION: 'needs_attention',
|
||||
});
|
||||
} as const;
|
||||
|
||||
export const TOTAL_LENGTH_KEY = 'total-length';
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { type RequestStatusType } from './constants';
|
||||
|
||||
export const LOADED = 'LOADED';
|
||||
|
||||
const slice = createSlice({
|
||||
name: 'courseDetail',
|
||||
initialState: {
|
||||
courseId: null,
|
||||
status: null,
|
||||
canChangeProvider: null,
|
||||
courseId: null as string | null,
|
||||
status: null as RequestStatusType | null,
|
||||
canChangeProviders: null as null | boolean,
|
||||
},
|
||||
reducers: {
|
||||
updateStatus: (state, { payload }) => {
|
||||
@@ -20,7 +20,7 @@ export function fetchCourseDetail(courseId) {
|
||||
canChangeProviders: getAuthenticatedUser().administrator || new Date(courseDetail.start) > new Date(),
|
||||
}));
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 404) {
|
||||
if ((error as any).response && (error as any).response.status === 404) {
|
||||
dispatch(updateStatus({ courseId, status: RequestStatus.NOT_FOUND }));
|
||||
} else {
|
||||
dispatch(updateStatus({ courseId, status: RequestStatus.FAILED }));
|
||||
@@ -1,29 +1,12 @@
|
||||
import React from 'react';
|
||||
import { render, screen, initializeMocks } from '@src/testUtils';
|
||||
import { selectors } from '@src/editors/data/redux';
|
||||
import AnswerOption from './AnswerOption';
|
||||
import * as hooks from './hooks';
|
||||
import { selectors } from '../../../../../data/redux';
|
||||
|
||||
const { problem } = selectors;
|
||||
|
||||
const initialState = {
|
||||
problem: {
|
||||
problemType: 'multiplechoiceresponse', // No problem type selected by default
|
||||
// ... other problem-related state
|
||||
},
|
||||
app: {
|
||||
images: {}, // No images loaded by default; use {} if it's an object keyed by IDs, or [] if it's a list
|
||||
isLibrary: false, // Default to false; not in library context initially
|
||||
learningContextId: 'course+org+run', // No context ID by default
|
||||
blockId: 'block-id', // No block ID initially
|
||||
// ... other app-related state
|
||||
},
|
||||
// ... any other top-level state slices
|
||||
};
|
||||
|
||||
export default initialState;
|
||||
|
||||
jest.mock('../../../../../data/redux', () => ({
|
||||
jest.mock('@src/editors/data/redux', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
selectors: {
|
||||
@@ -44,7 +27,7 @@ jest.mock('../../../../../data/redux', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../../../../../sharedComponents/ExpandableTextArea', () => 'ExpandableTextArea');
|
||||
jest.mock('@src/editors/sharedComponents/ExpandableTextArea', () => 'ExpandableTextArea');
|
||||
|
||||
describe('AnswerOption', () => {
|
||||
const answerWithOnlyFeedback = {
|
||||
@@ -86,7 +69,7 @@ describe('AnswerOption', () => {
|
||||
isFeedbackVisible: false,
|
||||
toggleFeedback: jest.fn(),
|
||||
});
|
||||
initializeMocks({ initialState });
|
||||
initializeMocks();
|
||||
});
|
||||
|
||||
test('renders correct option with feedback', () => {
|
||||
|
||||
@@ -3,9 +3,9 @@ import React from 'react';
|
||||
import { ProblemTypeKeys } from '@src/editors/data/constants/problem';
|
||||
import {
|
||||
render, screen, fireEvent, initializeMocks,
|
||||
} from '../../../../../../testUtils';
|
||||
} from '@src/testUtils';
|
||||
import { actions } from '@src/editors/data/redux';
|
||||
import AnswersContainer from './AnswersContainer';
|
||||
import { actions } from '../../../../../data/redux';
|
||||
|
||||
const { useAnswerContainer } = require('./hooks');
|
||||
|
||||
@@ -15,12 +15,6 @@ const answers = [
|
||||
{ id: 'a2', isAnswerRange: false },
|
||||
];
|
||||
|
||||
const initialState = {
|
||||
problem: {
|
||||
answers,
|
||||
},
|
||||
};
|
||||
|
||||
// Mock actions module
|
||||
jest.mock('../../../../../data/redux', () => ({
|
||||
__esModule: true,
|
||||
@@ -57,7 +51,7 @@ describe('AnswersContainer', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
initializeMocks({ initialState });
|
||||
initializeMocks();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// @ts-check
|
||||
import { camelCaseObject, getConfig } from '@edx/frontend-platform';
|
||||
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export const getGroupConfigurationsData = (state) => state.groupConfigurations.groupConfigurations;
|
||||
export const getLoadingStatus = (state) => state.groupConfigurations.loadingStatus;
|
||||
export const getSavingStatus = (state) => state.groupConfigurations.savingStatus;
|
||||
export const getErrorMessage = (state) => state.groupConfigurations.errorMessage;
|
||||
8
src/group-configurations/data/selectors.ts
Normal file
8
src/group-configurations/data/selectors.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { DeprecatedReduxState } from '@src/store';
|
||||
|
||||
export const getGroupConfigurationsData = (state: DeprecatedReduxState) => (
|
||||
state.groupConfigurations.groupConfigurations
|
||||
);
|
||||
export const getLoadingStatus = (state: DeprecatedReduxState) => state.groupConfigurations.loadingStatus;
|
||||
export const getSavingStatus = (state: DeprecatedReduxState) => state.groupConfigurations.savingStatus;
|
||||
export const getErrorMessage = (state: DeprecatedReduxState) => state.groupConfigurations.errorMessage;
|
||||
@@ -63,6 +63,7 @@ describe('groupConfigurations slice', () => {
|
||||
it('should delete an experiment configuration with deleteExperimentConfigurationSuccess', () => {
|
||||
const initialStateWithExperiment = {
|
||||
savingStatus: '',
|
||||
errorMessage: '',
|
||||
loadingStatus: RequestStatus.IN_PROGRESS,
|
||||
groupConfigurations: {
|
||||
allGroupConfigurations: [],
|
||||
@@ -9,7 +9,7 @@ const slice = createSlice({
|
||||
savingStatus: '',
|
||||
errorMessage: '',
|
||||
loadingStatus: RequestStatus.IN_PROGRESS,
|
||||
groupConfigurations: {},
|
||||
groupConfigurations: {} as Record<string, any>,
|
||||
},
|
||||
reducers: {
|
||||
fetchGroupConfigurations: (state, { payload }) => {
|
||||
@@ -1,10 +1,10 @@
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
import { NOTIFICATION_MESSAGES } from '../../constants';
|
||||
import { RequestStatus } from '@src/data/constants';
|
||||
import { NOTIFICATION_MESSAGES } from '@src/constants';
|
||||
import {
|
||||
hideProcessingNotification,
|
||||
showProcessingNotification,
|
||||
} from '../../generic/processing-notification/data/slice';
|
||||
import { handleResponseErrors } from '../../generic/saving-error-alert';
|
||||
} from '@src/generic/processing-notification/data/slice';
|
||||
import { handleResponseErrors } from '@src/generic/saving-error-alert';
|
||||
import {
|
||||
getGroupConfigurations,
|
||||
createContentGroup,
|
||||
@@ -33,7 +33,7 @@ export function fetchGroupConfigurationsQuery(courseId) {
|
||||
dispatch(fetchGroupConfigurations({ groupConfigurations }));
|
||||
dispatch(updateLoadingStatus({ status: RequestStatus.SUCCESSFUL }));
|
||||
} catch (error) {
|
||||
if (error.response && error.response.status === 403) {
|
||||
if ((error as any).response && (error as any).response.status === 403) {
|
||||
dispatch(updateLoadingStatus({ status: RequestStatus.DENIED }));
|
||||
} else {
|
||||
dispatch(updateLoadingStatus({ courseId, status: RequestStatus.FAILED }));
|
||||
@@ -1,4 +1,4 @@
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
import { configureStore, Reducer } from '@reduxjs/toolkit';
|
||||
|
||||
// FIXME: because the 'live' plugin is using Redux, we have to hard-code a reference to it here.
|
||||
// If this app + the plugin were using React-query, there'd be no issues.
|
||||
@@ -30,8 +30,43 @@ import { reducer as textbooksReducer } from './textbooks/data/slice';
|
||||
import { reducer as certificatesReducer } from './certificates/data/slice';
|
||||
import { reducer as groupConfigurationsReducer } from './group-configurations/data/slice';
|
||||
|
||||
export default function initializeStore(preloadedState = undefined) {
|
||||
return configureStore({
|
||||
type InferState<ReducerType> = ReducerType extends Reducer<infer T> ? T : never;
|
||||
|
||||
/**
|
||||
* @deprecated The global Redux state for Authoring MFE, excluding editors.
|
||||
* TODO: refactor each part to use React Context and React Query instead.
|
||||
*/
|
||||
export interface DeprecatedReduxState {
|
||||
courseDetail: InferState<typeof courseDetailReducer>;
|
||||
customPages: Record<string, any>;
|
||||
discussions: Record<string, any>;
|
||||
assets: Record<string, any>;
|
||||
pagesAndResources: Record<string, any>;
|
||||
scheduleAndDetails: Record<string, any>;
|
||||
advancedSettings: Record<string, any>;
|
||||
studioHome: InferState<typeof studioHomeReducer>;
|
||||
models: Record<string, any>;
|
||||
live: Record<string, any>;
|
||||
courseTeam: Record<string, any>;
|
||||
courseUpdates: Record<string, any>;
|
||||
processingNotification: Record<string, any>;
|
||||
helpUrls: Record<string, any>;
|
||||
courseExport: Record<string, any>;
|
||||
courseOptimizer: Record<string, any>;
|
||||
generic: Record<string, any>;
|
||||
courseImport: Record<string, any>;
|
||||
videos: Record<string, any>;
|
||||
courseOutline: Record<string, any>;
|
||||
courseUnit: Record<string, any>;
|
||||
courseChecklist: Record<string, any>;
|
||||
accessibilityPage: Record<string, any>;
|
||||
certificates: Record<string, any>;
|
||||
groupConfigurations: InferState<typeof groupConfigurationsReducer>;
|
||||
textbooks: Record<string, any>;
|
||||
}
|
||||
|
||||
export default function initializeStore(preloadedState: Partial<DeprecatedReduxState> | undefined = undefined) {
|
||||
return configureStore<DeprecatedReduxState>({
|
||||
reducer: {
|
||||
courseDetail: courseDetailReducer,
|
||||
customPages: customPagesReducer,
|
||||
@@ -150,7 +150,7 @@ const StudioHome = () => {
|
||||
<TabsSection
|
||||
showNewCourseContainer={showNewCourseContainer}
|
||||
onClickNewCourse={() => setShowNewCourseContainer(true)}
|
||||
isShowProcessing={isShowProcessing && !isFiltered}
|
||||
isShowProcessing={Boolean(isShowProcessing) && !isFiltered}
|
||||
librariesV1Enabled={librariesV1Enabled}
|
||||
librariesV2Enabled={librariesV2Enabled}
|
||||
/>
|
||||
|
||||
@@ -68,7 +68,6 @@ export default {
|
||||
rerunCreatorStatus: true,
|
||||
showNewLibraryButton: true,
|
||||
showNewLibraryV2Button: true,
|
||||
splitStudioHome: false,
|
||||
studioName: 'Studio',
|
||||
studioShortName: 'Studio',
|
||||
studioRequestEmail: 'request@email.com',
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export const getStudioHomeData = state => state.studioHome.studioHomeData;
|
||||
export const getLoadingStatuses = (state) => state.studioHome.loadingStatuses;
|
||||
export const getSavingStatuses = (state) => state.studioHome.savingStatuses;
|
||||
export const getStudioHomeCoursesParams = (state) => state.studioHome.studioHomeCoursesRequestParams;
|
||||
8
src/studio-home/data/selectors.ts
Normal file
8
src/studio-home/data/selectors.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { type DeprecatedReduxState } from '@src/store';
|
||||
|
||||
export const getStudioHomeData = (state: DeprecatedReduxState) => state.studioHome.studioHomeData;
|
||||
export const getLoadingStatuses = (state: DeprecatedReduxState) => state.studioHome.loadingStatuses;
|
||||
export const getSavingStatuses = (state: DeprecatedReduxState) => state.studioHome.savingStatuses;
|
||||
export const getStudioHomeCoursesParams = (state: DeprecatedReduxState) => (
|
||||
state.studioHome.studioHomeCoursesRequestParams
|
||||
);
|
||||
@@ -1,9 +1,20 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { type COURSE_CREATOR_STATES } from '@src/constants';
|
||||
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
import { RequestStatus, type RequestStatusType } from '@src/data/constants';
|
||||
|
||||
export const studioHomeCoursesRequestParamsDefault = {
|
||||
export interface Params {
|
||||
currentPage: number;
|
||||
search?: string;
|
||||
order?: string;
|
||||
archivedOnly?: boolean;
|
||||
activeOnly?: boolean;
|
||||
isFiltered?: boolean;
|
||||
cleanFilters?: boolean;
|
||||
}
|
||||
|
||||
export const studioHomeCoursesRequestParamsDefault: Params = {
|
||||
currentPage: 1,
|
||||
search: '',
|
||||
order: 'display_name',
|
||||
@@ -17,16 +28,42 @@ const slice = createSlice({
|
||||
name: 'studioHome',
|
||||
initialState: {
|
||||
loadingStatuses: {
|
||||
studioHomeLoadingStatus: RequestStatus.IN_PROGRESS,
|
||||
courseNotificationLoadingStatus: RequestStatus.IN_PROGRESS,
|
||||
courseLoadingStatus: RequestStatus.IN_PROGRESS,
|
||||
libraryLoadingStatus: RequestStatus.IN_PROGRESS,
|
||||
studioHomeLoadingStatus: RequestStatus.IN_PROGRESS as RequestStatusType,
|
||||
courseNotificationLoadingStatus: RequestStatus.IN_PROGRESS as RequestStatusType,
|
||||
courseLoadingStatus: RequestStatus.IN_PROGRESS as RequestStatusType,
|
||||
libraryLoadingStatus: RequestStatus.IN_PROGRESS as RequestStatusType,
|
||||
},
|
||||
savingStatuses: {
|
||||
courseCreatorSavingStatus: '',
|
||||
deleteNotificationSavingStatus: '',
|
||||
courseCreatorSavingStatus: '' as RequestStatusType | '',
|
||||
deleteNotificationSavingStatus: '' as RequestStatusType | '',
|
||||
},
|
||||
studioHomeData: {} as {
|
||||
allowCourseReruns?: boolean;
|
||||
allowToCreateNewOrg?: boolean;
|
||||
canCreateOrganizations?: boolean; // TODO: redundant with 'allowToCreateNewOrg' ???
|
||||
allowedOrganizations?: string[];
|
||||
allowedOrganizationsForLibraries?: string[];
|
||||
courseCreatorStatus?: (typeof COURSE_CREATOR_STATES)[keyof typeof COURSE_CREATOR_STATES];
|
||||
coursesCount?: any;
|
||||
courses?: any;
|
||||
archivedCourses?: any;
|
||||
inProcessCourseActions?: any;
|
||||
numPages?: any;
|
||||
optimizationEnabled?: boolean;
|
||||
libraries?: any;
|
||||
librariesV1Enabled?: boolean;
|
||||
librariesV2Enabled?: boolean;
|
||||
platformName?: string;
|
||||
rerunCreatorStatus?: boolean;
|
||||
requestCourseCreatorUrl?: string;
|
||||
showNewLibraryButton?: boolean;
|
||||
showNewLibraryV2Button?: boolean;
|
||||
studioRequestEmail?: string;
|
||||
studioName?: string;
|
||||
studioShortName?: string;
|
||||
techSupportEmail?: string;
|
||||
userIsActive?: boolean;
|
||||
},
|
||||
studioHomeData: {},
|
||||
studioHomeCoursesRequestParams: studioHomeCoursesRequestParamsDefault,
|
||||
},
|
||||
reducers: {
|
||||
@@ -1,4 +1,5 @@
|
||||
import { RequestStatus } from '../../data/constants';
|
||||
import { type DeprecatedReduxState } from '@src/store';
|
||||
import { RequestStatus } from '@src/data/constants';
|
||||
|
||||
export const courseId = 'course';
|
||||
|
||||
@@ -19,10 +20,9 @@ export const initialState = {
|
||||
currentPage: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
} satisfies Partial<DeprecatedReduxState>;
|
||||
|
||||
export const generateGetStudioHomeDataApiResponse = () => ({
|
||||
activeTab: 'courses',
|
||||
export const generateGetStudioHomeDataApiResponse = (): DeprecatedReduxState['studioHome']['studioHomeData'] => ({
|
||||
allowCourseReruns: true,
|
||||
allowedOrganizations: ['edx', 'org'],
|
||||
archivedCourses: [],
|
||||
@@ -38,7 +38,6 @@ export const generateGetStudioHomeDataApiResponse = () => ({
|
||||
rerunCreatorStatus: true,
|
||||
showNewLibraryButton: true,
|
||||
showNewLibraryV2Button: true,
|
||||
splitStudioHome: false,
|
||||
studioName: 'Studio',
|
||||
studioShortName: 'Studio',
|
||||
studioRequestEmail: 'request@email.com',
|
||||
@@ -5,12 +5,15 @@ import {
|
||||
screen,
|
||||
} from '@src/testUtils';
|
||||
import { COURSE_CREATOR_STATES } from '@src/constants';
|
||||
import { type DeprecatedReduxState } from '@src/store';
|
||||
import studioHomeMock from '@src/studio-home/__mocks__/studioHomeMock';
|
||||
import { initialState } from '../../factories/mockApiResponses';
|
||||
|
||||
import CoursesTab from '.';
|
||||
import { studioHomeCoursesRequestParamsDefault } from '../../data/slice';
|
||||
|
||||
type StudioHomeState = DeprecatedReduxState['studioHome'];
|
||||
|
||||
const onClickNewCourse = jest.fn();
|
||||
const isShowProcessing = false;
|
||||
const isLoading = false;
|
||||
@@ -19,9 +22,9 @@ const numPages = 1;
|
||||
const coursesCount = studioHomeMock.courses.length;
|
||||
const showNewCourseContainer = true;
|
||||
|
||||
const renderComponent = (overrideProps = {}, studioHomeState = {}) => {
|
||||
const renderComponent = (overrideProps = {}, studioHomeState: Partial<StudioHomeState> = {}) => {
|
||||
// Generate a custom initial state based on studioHomeCoursesRequestParams
|
||||
const customInitialState: any = { // TODO: remove 'any' once our redux state has proper types
|
||||
const customInitialState: Partial<DeprecatedReduxState> = {
|
||||
...initialState,
|
||||
studioHome: {
|
||||
...initialState.studioHome,
|
||||
@@ -118,7 +121,7 @@ describe('<CoursesTab />', () => {
|
||||
|
||||
it('should reset filters when in pressed the button to clean them', () => {
|
||||
const props = { isLoading: false, coursesDataItems: [] };
|
||||
const customStoreData = { studioHomeCoursesRequestParams: { isFiltered: true } };
|
||||
const customStoreData = { studioHomeCoursesRequestParams: { currentPage: 1, isFiltered: true } };
|
||||
const { store } = renderComponent(props, customStoreData);
|
||||
const cleanFiltersButton = screen.getByRole('button', { name: /clear filters/i });
|
||||
expect(cleanFiltersButton).toBeInTheDocument();
|
||||
|
||||
@@ -11,18 +11,18 @@ import {
|
||||
} from '@openedx/paragon';
|
||||
import { Error } from '@openedx/paragon/icons';
|
||||
|
||||
import { COURSE_CREATOR_STATES } from '../../../constants';
|
||||
import { getStudioHomeData, getStudioHomeCoursesParams } from '../../data/selectors';
|
||||
import { resetStudioHomeCoursesCustomParams, updateStudioHomeCoursesCustomParams } from '../../data/slice';
|
||||
import { fetchStudioHomeData } from '../../data/thunks';
|
||||
import CardItem from '../../card-item';
|
||||
import CollapsibleStateWithAction from '../../collapsible-state-with-action';
|
||||
import ContactAdministrator from './contact-administrator';
|
||||
import CoursesFilters from './courses-filters';
|
||||
import ProcessingCourses from '../../processing-courses';
|
||||
import { LoadingSpinner } from '../../../generic/Loading';
|
||||
import AlertMessage from '../../../generic/alert-message';
|
||||
import { COURSE_CREATOR_STATES } from '@src/constants';
|
||||
import { getStudioHomeData, getStudioHomeCoursesParams } from '@src/studio-home/data/selectors';
|
||||
import { resetStudioHomeCoursesCustomParams, updateStudioHomeCoursesCustomParams } from '@src/studio-home/data/slice';
|
||||
import { fetchStudioHomeData } from '@src/studio-home/data/thunks';
|
||||
import CardItem from '@src/studio-home/card-item';
|
||||
import CollapsibleStateWithAction from '@src/studio-home/collapsible-state-with-action';
|
||||
import ProcessingCourses from '@src/studio-home/processing-courses';
|
||||
import { LoadingSpinner } from '@src/generic/Loading';
|
||||
import AlertMessage from '@src/generic/alert-message';
|
||||
import messages from '../messages';
|
||||
import CoursesFilters from './courses-filters';
|
||||
import ContactAdministrator from './contact-administrator';
|
||||
import './index.scss';
|
||||
|
||||
interface Props {
|
||||
@@ -69,7 +69,7 @@ const CoursesTab: React.FC<Props> = ({
|
||||
COURSE_CREATOR_STATES.denied,
|
||||
COURSE_CREATOR_STATES.pending,
|
||||
COURSE_CREATOR_STATES.unrequested,
|
||||
].includes(courseCreatorStatus);
|
||||
].includes(courseCreatorStatus as any);
|
||||
const locationValue = location.search ?? '';
|
||||
|
||||
const handlePageSelected = (page) => {
|
||||
@@ -191,7 +191,7 @@ const CoursesTab: React.FC<Props> = ({
|
||||
)}
|
||||
{showCollapsible && (
|
||||
<CollapsibleStateWithAction
|
||||
state={courseCreatorStatus}
|
||||
state={courseCreatorStatus!}
|
||||
className="mt-3"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
} from 'react-router-dom';
|
||||
|
||||
import { ToastContext, type ToastContextData } from './generic/toast-context';
|
||||
import initializeReduxStore from './store';
|
||||
import initializeReduxStore, { type DeprecatedReduxState } from './store';
|
||||
import { getApiWaffleFlagsUrl } from './data/api';
|
||||
|
||||
/** @deprecated Use React Query and/or regular React Context instead of redux */
|
||||
@@ -157,7 +157,7 @@ const defaultUser = {
|
||||
*/
|
||||
export function initializeMocks({ user = defaultUser, initialState = undefined }: {
|
||||
user?: { userId: number, username: string },
|
||||
initialState?: Record<string, any>, // TODO: proper typing for our redux state
|
||||
initialState?: Partial<DeprecatedReduxState>
|
||||
} = {}) {
|
||||
initializeMockApp({
|
||||
authenticatedUser: user,
|
||||
|
||||
Reference in New Issue
Block a user