diff --git a/assets/src/js/v3/@types/index.d.ts b/assets/src/js/v3/@types/index.d.ts index ccef5f4850..4b25dd5663 100644 --- a/assets/src/js/v3/@types/index.d.ts +++ b/assets/src/js/v3/@types/index.d.ts @@ -187,6 +187,7 @@ declare global { enable_individual_tax_control: boolean; is_tax_included_in_price: boolean; pagination_per_page: string; + quiz_attempts_allowed: string; }; tutor_currency: { symbol: string; diff --git a/assets/src/js/v3/entries/addon-list/components/App.tsx b/assets/src/js/v3/entries/addon-list/components/App.tsx index ce5535fb5a..b67034f82a 100644 --- a/assets/src/js/v3/entries/addon-list/components/App.tsx +++ b/assets/src/js/v3/entries/addon-list/components/App.tsx @@ -2,6 +2,7 @@ import { Global } from '@emotion/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import ToastProvider from '@TutorShared/atoms/Toast'; import RTLProvider from '@TutorShared/components/RTLProvider'; +import { SVGIconConfigProvider } from '@TutorShared/contexts/SVGIconConfigContext'; import { createGlobalCss } from '@TutorShared/utils/style-utils'; import { useState } from 'react'; import Main from './layout/Main'; @@ -27,10 +28,12 @@ function App() { return ( - - -
- + + + +
+ + ); diff --git a/assets/src/js/v3/entries/coupon-details/components/App.tsx b/assets/src/js/v3/entries/coupon-details/components/App.tsx index e84684d87a..696e88f3e7 100644 --- a/assets/src/js/v3/entries/coupon-details/components/App.tsx +++ b/assets/src/js/v3/entries/coupon-details/components/App.tsx @@ -3,6 +3,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import ToastProvider from '@TutorShared/atoms/Toast'; import { ModalProvider } from '@TutorShared/components/modals/Modal'; import RTLProvider from '@TutorShared/components/RTLProvider'; +import { SVGIconConfigProvider } from '@TutorShared/contexts/SVGIconConfigContext'; import { createGlobalCss } from '@TutorShared/utils/style-utils'; import { useState } from 'react'; import Main from './layout/Main'; @@ -27,12 +28,14 @@ function App() { return ( - - - -
- - + + + + +
+ + + ); diff --git a/assets/src/js/v3/entries/course-builder/components/App.tsx b/assets/src/js/v3/entries/course-builder/components/App.tsx index 8e8469d6da..7389f19e13 100644 --- a/assets/src/js/v3/entries/course-builder/components/App.tsx +++ b/assets/src/js/v3/entries/course-builder/components/App.tsx @@ -3,6 +3,7 @@ import { CourseBuilderSlotProvider } from '@CourseBuilderContexts/CourseBuilderS import ToastProvider from '@TutorShared/atoms/Toast'; import RTLProvider from '@TutorShared/components/RTLProvider'; import { ModalProvider } from '@TutorShared/components/modals/Modal'; +import { SVGIconConfigProvider } from '@TutorShared/contexts/SVGIconConfigContext'; import { createGlobalCss } from '@TutorShared/utils/style-utils'; import { Global } from '@emotion/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; @@ -32,14 +33,16 @@ const App = () => { return ( - - - - - {routers} - - - + + + + + + {routers} + + + + ); diff --git a/assets/src/js/v3/entries/course-builder/components/modals/QuizModal.tsx b/assets/src/js/v3/entries/course-builder/components/modals/QuizModal.tsx index fa86c556e9..5b9c6b0466 100644 --- a/assets/src/js/v3/entries/course-builder/components/modals/QuizModal.tsx +++ b/assets/src/js/v3/entries/course-builder/components/modals/QuizModal.tsx @@ -44,6 +44,7 @@ import { POPOVER_PLACEMENTS } from '@TutorShared/hooks/usePortalPopover'; import { validateQuizQuestion } from '@TutorShared/utils/quiz'; import { type ID, isDefined, type TopicContentType } from '@TutorShared/utils/types'; import { findSlotFields } from '@TutorShared/utils/util'; +import { tutorConfig } from '@TutorShared/config/config'; interface QuizModalProps extends ModalProps { quizId?: ID; @@ -56,6 +57,7 @@ interface QuizModalProps extends ModalProps { type QuizTabs = 'details' | 'settings'; const courseId = getCourseId(); +const defaultQuizAttemptsAllowed = tutorConfig.settings?.quiz_attempts_allowed ?? 10; const QuizModal = ({ closeModal, @@ -89,7 +91,7 @@ const QuizModal = ({ hide_quiz_time_display: false, limit_attempts_allowed: false, limit_questions_to_answer: false, - attempts_allowed: 10, + attempts_allowed: defaultQuizAttemptsAllowed, passing_grade: 80, max_questions_for_answer: contentType === 'tutor_h5p_quiz' ? 0 : 10, quiz_auto_start: false, diff --git a/assets/src/js/v3/entries/course-builder/services/quiz.ts b/assets/src/js/v3/entries/course-builder/services/quiz.ts index 2f1a0a849a..0c21947507 100644 --- a/assets/src/js/v3/entries/course-builder/services/quiz.ts +++ b/assets/src/js/v3/entries/course-builder/services/quiz.ts @@ -163,6 +163,7 @@ interface QuizUpdateQuestionPayload { } export const convertQuizResponseToFormData = (quiz: QuizDetailsResponse, slotFields: string[]): QuizForm => { + const defaultQuizAttemptsAllowed = tutorConfig.settings?.quiz_attempts_allowed ?? 10; const legacyQuizOption = quiz.quiz_option as QuizDetailsResponse['quiz_option'] & { feedback_mode?: 'default' | 'reveal' | 'retry'; }; @@ -182,7 +183,7 @@ export const convertQuizResponseToFormData = (quiz: QuizDetailsResponse, slotFie limit_attempts_allowed: isDefined(quiz.quiz_option.limit_attempts_allowed) ? quiz.quiz_option.limit_attempts_allowed === '1' : legacyQuizOption.feedback_mode === 'retry', - attempts_allowed: quiz.quiz_option.attempts_allowed ?? 10, + attempts_allowed: quiz.quiz_option.attempts_allowed ?? defaultQuizAttemptsAllowed, pass_is_required: quiz.quiz_option.pass_is_required === '1', passing_grade: quiz.quiz_option.passing_grade ?? 80, limit_questions_to_answer: !!Number(quiz.quiz_option.max_questions_for_answer), diff --git a/assets/src/js/v3/entries/import-export/components/App.tsx b/assets/src/js/v3/entries/import-export/components/App.tsx index 84beede438..2cd814e012 100644 --- a/assets/src/js/v3/entries/import-export/components/App.tsx +++ b/assets/src/js/v3/entries/import-export/components/App.tsx @@ -6,6 +6,7 @@ import Main from '@ImportExport/components/Main'; import ToastProvider from '@TutorShared/atoms/Toast'; import RTLProvider from '@TutorShared/components/RTLProvider'; import { ModalProvider } from '@TutorShared/components/modals/Modal'; +import { SVGIconConfigProvider } from '@TutorShared/contexts/SVGIconConfigContext'; import { createGlobalCss } from '@TutorShared/utils/style-utils'; function App() { @@ -29,12 +30,14 @@ function App() { return ( - - - -
- - + + + + +
+ + + ); diff --git a/assets/src/js/v3/entries/order-details/components/App.tsx b/assets/src/js/v3/entries/order-details/components/App.tsx index 4b7f6b4980..f043072233 100644 --- a/assets/src/js/v3/entries/order-details/components/App.tsx +++ b/assets/src/js/v3/entries/order-details/components/App.tsx @@ -6,6 +6,7 @@ import ToastProvider from '@TutorShared/atoms/Toast'; import RTLProvider from '@TutorShared/components/RTLProvider'; import { ModalProvider } from '@TutorShared/components/modals/Modal'; +import { SVGIconConfigProvider } from '@TutorShared/contexts/SVGIconConfigContext'; import { createGlobalCss } from '@TutorShared/utils/style-utils'; import Main from './layout/Main'; @@ -30,12 +31,14 @@ function App() { return ( - - - -
- - + + + + +
+ + + ); diff --git a/assets/src/js/v3/entries/payment-settings/components/App.tsx b/assets/src/js/v3/entries/payment-settings/components/App.tsx index c47eb3fb7c..84194e955d 100644 --- a/assets/src/js/v3/entries/payment-settings/components/App.tsx +++ b/assets/src/js/v3/entries/payment-settings/components/App.tsx @@ -5,6 +5,7 @@ import { useState } from 'react'; import ToastProvider from '@TutorShared/atoms/Toast'; import RTLProvider from '@TutorShared/components/RTLProvider'; import { ModalProvider } from '@TutorShared/components/modals/Modal'; +import { SVGIconConfigProvider } from '@TutorShared/contexts/SVGIconConfigContext'; import { createGlobalCss } from '@TutorShared/utils/style-utils'; import { PaymentProvider } from '../contexts/payment-context'; @@ -31,14 +32,16 @@ function App() { return ( - - - - - - - - + + + + + + + + + + ); diff --git a/assets/src/js/v3/entries/tax-settings/components/App.tsx b/assets/src/js/v3/entries/tax-settings/components/App.tsx index 235918190e..fe0d357527 100644 --- a/assets/src/js/v3/entries/tax-settings/components/App.tsx +++ b/assets/src/js/v3/entries/tax-settings/components/App.tsx @@ -5,6 +5,7 @@ import { useState } from 'react'; import ToastProvider from '@TutorShared/atoms/Toast'; import RTLProvider from '@TutorShared/components/RTLProvider'; import { ModalProvider } from '@TutorShared/components/modals/Modal'; +import { SVGIconConfigProvider } from '@TutorShared/contexts/SVGIconConfigContext'; import { createGlobalCss } from '@TutorShared/utils/style-utils'; import TaxSettingsPage from './TaxSettings'; @@ -30,12 +31,14 @@ function App() { return ( - - - - - - + + + + + + + + ); diff --git a/assets/src/js/v3/shared/atoms/SVGIcon.tsx b/assets/src/js/v3/shared/atoms/SVGIcon.tsx index 30383f1b4f..a6e97bd390 100644 --- a/assets/src/js/v3/shared/atoms/SVGIcon.tsx +++ b/assets/src/js/v3/shared/atoms/SVGIcon.tsx @@ -1,4 +1,5 @@ import { tutorConfig } from '@TutorShared/config/config'; +import { useSVGIconConfig } from '@TutorShared/contexts/SVGIconConfigContext'; import { type IconCollection } from '@TutorShared/icons/types'; import { type SerializedStyles, css } from '@emotion/react'; import { memo, useEffect, useState } from 'react'; @@ -28,16 +29,10 @@ interface IconCacheEntry { const iconCache: Record = {}; -const SVGIcon = ({ - name, - width = 16, - height = 16, - style, - isColorIcon = false, - ignoreKids = false, - ...rest -}: SVGIconProps) => { - const cacheKey = ignoreKids ? `${name}-ignoreKids` : name; +const SVGIcon = ({ name, width = 16, height = 16, style, isColorIcon = false, ignoreKids, ...rest }: SVGIconProps) => { + const { showKidsIcons } = useSVGIconConfig(); + const shouldIgnoreKids = ignoreKids ?? !showKidsIcons; + const cacheKey = shouldIgnoreKids ? `${name}-ignoreKids` : name; const [icon, setIcon] = useState(iconCache[cacheKey]?.icon || null); const [isLoading, setIsLoading] = useState(!iconCache[cacheKey]?.icon); @@ -50,7 +45,7 @@ const SVGIcon = ({ setIsLoading(true); - fetchIcon(name, cacheKey, width, height, ignoreKids) + fetchIcon(name, cacheKey, width, height, shouldIgnoreKids) .then((loadedIcon) => { setIcon(loadedIcon); }) @@ -60,7 +55,7 @@ const SVGIcon = ({ .finally(() => { setIsLoading(false); }); - }, [name, width, height, ignoreKids, cacheKey]); + }, [name, width, height, shouldIgnoreKids, cacheKey]); const additionalAttributes = { ...(isColorIcon && { 'data-colorize': true }), diff --git a/assets/src/js/v3/shared/contexts/SVGIconConfigContext.tsx b/assets/src/js/v3/shared/contexts/SVGIconConfigContext.tsx new file mode 100644 index 0000000000..80404643e5 --- /dev/null +++ b/assets/src/js/v3/shared/contexts/SVGIconConfigContext.tsx @@ -0,0 +1,31 @@ +import React, { useContext } from 'react'; + +interface SVGIconConfigContextValue { + supportKidsIcon: boolean; +} + +const defaultValue: SVGIconConfigContextValue = { + supportKidsIcon: false, +}; + +const SVGIconConfigContext = React.createContext(defaultValue); + +export const useSVGIconConfig = () => useContext(SVGIconConfigContext); + +export const SVGIconConfigProvider = ({ + children, + supportKidsIcon = false, +}: { + children: React.ReactNode; + supportKidsIcon?: boolean; +}) => { + return ( + + {children} + + ); +}; diff --git a/classes/Course.php b/classes/Course.php index 70b2fc1ad7..ad8c871a38 100644 --- a/classes/Course.php +++ b/classes/Course.php @@ -1543,6 +1543,7 @@ public function localize_course_builder_data( $data ) { 'instructor_can_publish_course', 'instructor_can_change_course_author', 'instructor_can_manage_co_instructors', + 'quiz_attempts_allowed', ); $full_settings = get_option( 'tutor_option', array() );