@magmamath/students-features 1.8.1 → 1.8.2-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/dist/commonjs/features/exampleSolution/ExampleSolutionModal.js +29 -0
  2. package/dist/commonjs/features/exampleSolution/ExampleSolutionModal.js.map +1 -0
  3. package/dist/commonjs/features/exampleSolution/assets/grid.webp +0 -0
  4. package/dist/commonjs/features/exampleSolution/components/ExampleSolutionModalContent.js +97 -0
  5. package/dist/commonjs/features/exampleSolution/components/ExampleSolutionModalContent.js.map +1 -0
  6. package/dist/commonjs/features/exampleSolution/components/QuestionSection.js +70 -0
  7. package/dist/commonjs/features/exampleSolution/components/QuestionSection.js.map +1 -0
  8. package/dist/commonjs/features/exampleSolution/components/SolutionSection.js +104 -0
  9. package/dist/commonjs/features/exampleSolution/components/SolutionSection.js.map +1 -0
  10. package/dist/commonjs/features/exampleSolution/constants.js +13 -0
  11. package/dist/commonjs/features/exampleSolution/constants.js.map +1 -0
  12. package/dist/commonjs/features/exampleSolution/hooks/useExampleSolution.js +61 -0
  13. package/dist/commonjs/features/exampleSolution/hooks/useExampleSolution.js.map +1 -0
  14. package/dist/commonjs/features/exampleSolution/index.js +39 -0
  15. package/dist/commonjs/features/exampleSolution/index.js.map +1 -0
  16. package/dist/commonjs/features/exampleSolution/model/ExampleSolution.model.js +24 -0
  17. package/dist/commonjs/features/exampleSolution/model/ExampleSolution.model.js.map +1 -0
  18. package/dist/commonjs/features/exampleSolution/model/ExampleSolutionApi.js +28 -0
  19. package/dist/commonjs/features/exampleSolution/model/ExampleSolutionApi.js.map +1 -0
  20. package/dist/commonjs/features/exampleSolution/model/ExampleSolutionCache.js +18 -0
  21. package/dist/commonjs/features/exampleSolution/model/ExampleSolutionCache.js.map +1 -0
  22. package/dist/commonjs/features/exampleSolution/types.js +6 -0
  23. package/dist/commonjs/features/exampleSolution/types.js.map +1 -0
  24. package/dist/commonjs/features/uiMode/uiMode.helpers.js +6 -1
  25. package/dist/commonjs/features/uiMode/uiMode.helpers.js.map +1 -1
  26. package/dist/commonjs/i18n/i18n.js +3 -1
  27. package/dist/commonjs/i18n/i18n.js.map +1 -1
  28. package/dist/commonjs/index.js +22 -10
  29. package/dist/commonjs/index.js.map +1 -1
  30. package/dist/module/features/exampleSolution/ExampleSolutionModal.js +23 -0
  31. package/dist/module/features/exampleSolution/ExampleSolutionModal.js.map +1 -0
  32. package/dist/module/features/exampleSolution/assets/grid.webp +0 -0
  33. package/dist/module/features/exampleSolution/components/ExampleSolutionModalContent.js +91 -0
  34. package/dist/module/features/exampleSolution/components/ExampleSolutionModalContent.js.map +1 -0
  35. package/dist/module/features/exampleSolution/components/QuestionSection.js +62 -0
  36. package/dist/module/features/exampleSolution/components/QuestionSection.js.map +1 -0
  37. package/dist/module/features/exampleSolution/components/SolutionSection.js +96 -0
  38. package/dist/module/features/exampleSolution/components/SolutionSection.js.map +1 -0
  39. package/dist/module/features/exampleSolution/constants.js +9 -0
  40. package/dist/module/features/exampleSolution/constants.js.map +1 -0
  41. package/dist/module/features/exampleSolution/hooks/useExampleSolution.js +56 -0
  42. package/dist/module/features/exampleSolution/hooks/useExampleSolution.js.map +1 -0
  43. package/dist/module/features/exampleSolution/index.js +6 -0
  44. package/dist/module/features/exampleSolution/index.js.map +1 -0
  45. package/dist/module/features/exampleSolution/model/ExampleSolution.model.js +19 -0
  46. package/dist/module/features/exampleSolution/model/ExampleSolution.model.js.map +1 -0
  47. package/dist/module/features/exampleSolution/model/ExampleSolutionApi.js +23 -0
  48. package/dist/module/features/exampleSolution/model/ExampleSolutionApi.js.map +1 -0
  49. package/dist/module/features/exampleSolution/model/ExampleSolutionCache.js +13 -0
  50. package/dist/module/features/exampleSolution/model/ExampleSolutionCache.js.map +1 -0
  51. package/dist/module/features/exampleSolution/types.js +4 -0
  52. package/dist/module/features/exampleSolution/types.js.map +1 -0
  53. package/dist/module/features/uiMode/uiMode.helpers.js +6 -1
  54. package/dist/module/features/uiMode/uiMode.helpers.js.map +1 -1
  55. package/dist/module/i18n/i18n.js +3 -1
  56. package/dist/module/i18n/i18n.js.map +1 -1
  57. package/dist/module/index.js +1 -0
  58. package/dist/module/index.js.map +1 -1
  59. package/dist/typescript/commonjs/features/exampleSolution/ExampleSolutionModal.d.ts +11 -0
  60. package/dist/typescript/commonjs/features/exampleSolution/ExampleSolutionModal.d.ts.map +1 -0
  61. package/dist/typescript/commonjs/features/exampleSolution/components/ExampleSolutionModalContent.d.ts +8 -0
  62. package/dist/typescript/commonjs/features/exampleSolution/components/ExampleSolutionModalContent.d.ts.map +1 -0
  63. package/dist/typescript/commonjs/features/exampleSolution/components/QuestionSection.d.ts +10 -0
  64. package/dist/typescript/commonjs/features/exampleSolution/components/QuestionSection.d.ts.map +1 -0
  65. package/dist/typescript/commonjs/features/exampleSolution/components/SolutionSection.d.ts +9 -0
  66. package/dist/typescript/commonjs/features/exampleSolution/components/SolutionSection.d.ts.map +1 -0
  67. package/dist/typescript/commonjs/features/exampleSolution/constants.d.ts +6 -0
  68. package/dist/typescript/commonjs/features/exampleSolution/constants.d.ts.map +1 -0
  69. package/dist/typescript/commonjs/features/exampleSolution/hooks/useExampleSolution.d.ts +9 -0
  70. package/dist/typescript/commonjs/features/exampleSolution/hooks/useExampleSolution.d.ts.map +1 -0
  71. package/dist/typescript/commonjs/features/exampleSolution/index.d.ts +4 -0
  72. package/dist/typescript/commonjs/features/exampleSolution/index.d.ts.map +1 -0
  73. package/dist/typescript/commonjs/features/exampleSolution/model/ExampleSolution.model.d.ts +10 -0
  74. package/dist/typescript/commonjs/features/exampleSolution/model/ExampleSolution.model.d.ts.map +1 -0
  75. package/dist/typescript/commonjs/features/exampleSolution/model/ExampleSolutionApi.d.ts +13 -0
  76. package/dist/typescript/commonjs/features/exampleSolution/model/ExampleSolutionApi.d.ts.map +1 -0
  77. package/dist/typescript/commonjs/features/exampleSolution/model/ExampleSolutionCache.d.ts +11 -0
  78. package/dist/typescript/commonjs/features/exampleSolution/model/ExampleSolutionCache.d.ts.map +1 -0
  79. package/dist/typescript/commonjs/features/exampleSolution/types.d.ts +27 -0
  80. package/dist/typescript/commonjs/features/exampleSolution/types.d.ts.map +1 -0
  81. package/dist/typescript/commonjs/features/fluency/hooks/useFluencyNumpad.d.ts +1 -2
  82. package/dist/typescript/commonjs/features/fluency/hooks/useFluencyNumpad.d.ts.map +1 -1
  83. package/dist/typescript/commonjs/features/uiMode/uiMode.helpers.d.ts.map +1 -1
  84. package/dist/typescript/commonjs/features/voice/playing/hooks/useVoiceTranscriptionController.d.ts +1 -1
  85. package/dist/typescript/commonjs/i18n/i18n.d.ts.map +1 -1
  86. package/dist/typescript/commonjs/index.d.ts +1 -0
  87. package/dist/typescript/commonjs/index.d.ts.map +1 -1
  88. package/dist/typescript/commonjs/lib/helpers/helpers.d.ts +1 -1
  89. package/dist/typescript/commonjs/lib/helpers/helpers.d.ts.map +1 -1
  90. package/dist/typescript/module/features/exampleSolution/ExampleSolutionModal.d.ts +11 -0
  91. package/dist/typescript/module/features/exampleSolution/ExampleSolutionModal.d.ts.map +1 -0
  92. package/dist/typescript/module/features/exampleSolution/components/ExampleSolutionModalContent.d.ts +8 -0
  93. package/dist/typescript/module/features/exampleSolution/components/ExampleSolutionModalContent.d.ts.map +1 -0
  94. package/dist/typescript/module/features/exampleSolution/components/QuestionSection.d.ts +10 -0
  95. package/dist/typescript/module/features/exampleSolution/components/QuestionSection.d.ts.map +1 -0
  96. package/dist/typescript/module/features/exampleSolution/components/SolutionSection.d.ts +9 -0
  97. package/dist/typescript/module/features/exampleSolution/components/SolutionSection.d.ts.map +1 -0
  98. package/dist/typescript/module/features/exampleSolution/constants.d.ts +6 -0
  99. package/dist/typescript/module/features/exampleSolution/constants.d.ts.map +1 -0
  100. package/dist/typescript/module/features/exampleSolution/hooks/useExampleSolution.d.ts +9 -0
  101. package/dist/typescript/module/features/exampleSolution/hooks/useExampleSolution.d.ts.map +1 -0
  102. package/dist/typescript/module/features/exampleSolution/index.d.ts +4 -0
  103. package/dist/typescript/module/features/exampleSolution/index.d.ts.map +1 -0
  104. package/dist/typescript/module/features/exampleSolution/model/ExampleSolution.model.d.ts +10 -0
  105. package/dist/typescript/module/features/exampleSolution/model/ExampleSolution.model.d.ts.map +1 -0
  106. package/dist/typescript/module/features/exampleSolution/model/ExampleSolutionApi.d.ts +13 -0
  107. package/dist/typescript/module/features/exampleSolution/model/ExampleSolutionApi.d.ts.map +1 -0
  108. package/dist/typescript/module/features/exampleSolution/model/ExampleSolutionCache.d.ts +11 -0
  109. package/dist/typescript/module/features/exampleSolution/model/ExampleSolutionCache.d.ts.map +1 -0
  110. package/dist/typescript/module/features/exampleSolution/types.d.ts +27 -0
  111. package/dist/typescript/module/features/exampleSolution/types.d.ts.map +1 -0
  112. package/dist/typescript/module/features/fluency/hooks/useFluencyNumpad.d.ts +1 -2
  113. package/dist/typescript/module/features/fluency/hooks/useFluencyNumpad.d.ts.map +1 -1
  114. package/dist/typescript/module/features/uiMode/uiMode.helpers.d.ts.map +1 -1
  115. package/dist/typescript/module/features/voice/playing/hooks/useVoiceTranscriptionController.d.ts +1 -1
  116. package/dist/typescript/module/i18n/i18n.d.ts.map +1 -1
  117. package/dist/typescript/module/index.d.ts +1 -0
  118. package/dist/typescript/module/index.d.ts.map +1 -1
  119. package/dist/typescript/module/lib/helpers/helpers.d.ts +1 -1
  120. package/dist/typescript/module/lib/helpers/helpers.d.ts.map +1 -1
  121. package/package.json +2 -2
  122. package/src/features/exampleSolution/ExampleSolutionModal.tsx +23 -0
  123. package/src/features/exampleSolution/assets/grid.webp +0 -0
  124. package/src/features/exampleSolution/components/ExampleSolutionModalContent.tsx +80 -0
  125. package/src/features/exampleSolution/components/QuestionSection.tsx +61 -0
  126. package/src/features/exampleSolution/components/SolutionSection.tsx +117 -0
  127. package/src/features/exampleSolution/constants.ts +5 -0
  128. package/src/features/exampleSolution/hooks/useExampleSolution.ts +66 -0
  129. package/src/features/exampleSolution/index.ts +3 -0
  130. package/src/features/exampleSolution/model/ExampleSolution.model.ts +15 -0
  131. package/src/features/exampleSolution/model/ExampleSolutionApi.ts +29 -0
  132. package/src/features/exampleSolution/model/ExampleSolutionCache.ts +20 -0
  133. package/src/features/exampleSolution/types.ts +27 -0
  134. package/src/features/uiMode/__tests__/getUIMode.test.ts +64 -0
  135. package/src/features/uiMode/uiMode.helpers.ts +16 -1
  136. package/src/i18n/i18n.ts +2 -1
  137. package/src/index.ts +1 -0
  138. package/src/i18n/.generated/schema.json +0 -154
@@ -0,0 +1,23 @@
1
+ import React from 'react'
2
+ import { BaseModal, SPACING } from '@magmamath/react-native-ui'
3
+ import { ExampleSolutionModalContent } from './components/ExampleSolutionModalContent'
4
+ import { ExampleSolutionModalParams } from './types'
5
+
6
+ type ExampleSolutionModalComponentProps = {
7
+ modal: {
8
+ closeModal: () => void
9
+ params: ExampleSolutionModalParams
10
+ }
11
+ }
12
+
13
+ export const ExampleSolutionModal = ({
14
+ modal: { closeModal, params },
15
+ }: ExampleSolutionModalComponentProps) => (
16
+ <BaseModal
17
+ style={{
18
+ container: { paddingHorizontal: SPACING[800] },
19
+ }}
20
+ onClose={closeModal}
21
+ content={<ExampleSolutionModalContent params={params} />}
22
+ />
23
+ )
@@ -0,0 +1,80 @@
1
+ import { StyleSheet, View } from 'react-native'
2
+ import React from 'react'
3
+ import { COLORS, HeadingVariants, IS_WEB, SPACING, Typography } from '@magmamath/react-native-ui'
4
+ import { ExampleSolutionModalParams } from '../types'
5
+ import { QuestionSection } from './QuestionSection'
6
+ import { SolutionSection } from './SolutionSection'
7
+ import { useExampleSolution } from '../hooks/useExampleSolution'
8
+ import { TEST_IDS } from '@magmamath/students-qa'
9
+ import { useText } from '../../../i18n/i18n'
10
+
11
+ type ExampleSolutionModalContentProps = {
12
+ params: ExampleSolutionModalParams
13
+ }
14
+
15
+ const CONTAINER_HEIGHT = 593
16
+
17
+ export const ExampleSolutionModalContent = ({
18
+ params: { model, payload, cacheKey },
19
+ }: ExampleSolutionModalContentProps) => {
20
+ const { result, status } = useExampleSolution({ model, payload, cacheKey })
21
+ const t = useText()
22
+
23
+ return (
24
+ <View
25
+ testID={TEST_IDS.EXAMPLE_SOLUTION_MODAL}
26
+ style={[
27
+ styles.container,
28
+ IS_WEB ? { maxHeight: CONTAINER_HEIGHT } : { height: CONTAINER_HEIGHT },
29
+ ]}
30
+ >
31
+ <Typography align="center" variant={HeadingVariants.H2} style={{ color: COLORS.NEUTRAL_9 }}>
32
+ {t('exampleSolution.exampleSolution')}
33
+ </Typography>
34
+ <View style={styles.content}>
35
+ <QuestionSection
36
+ status={status}
37
+ question={result?.question}
38
+ renderKatexComponent={model.renderKatexComponent}
39
+ />
40
+ <SolutionSection status={status} answer={result?.answer} />
41
+ </View>
42
+ </View>
43
+ )
44
+ }
45
+
46
+ const styles = StyleSheet.create({
47
+ container: {
48
+ width: 880,
49
+ },
50
+ content: {
51
+ flex: 1,
52
+ flexDirection: 'row',
53
+ marginTop: SPACING[400],
54
+ gap: SPACING[400],
55
+ },
56
+ solutionContainer: {
57
+ width: 351,
58
+ overflow: 'hidden',
59
+ borderRadius: 8,
60
+ borderWidth: 1,
61
+ borderColor: COLORS.NEUTRAL_5,
62
+ },
63
+ lavaIconContainer: {
64
+ position: 'absolute',
65
+ bottom: -24,
66
+ right: -26,
67
+ },
68
+ loaderContainer: {
69
+ paddingTop: 34,
70
+ position: 'absolute',
71
+ width: '100%',
72
+ height: '100%',
73
+ backgroundColor: COLORS.NEUTRAL_1,
74
+ },
75
+ messageBlock: {
76
+ position: 'absolute',
77
+ right: 58,
78
+ bottom: -12,
79
+ },
80
+ })
@@ -0,0 +1,61 @@
1
+ import { StyleSheet, View } from 'react-native'
2
+ import React from 'react'
3
+ import { COLORS, Loader, LoaderColor, SPACING } from '@magmamath/react-native-ui'
4
+ import Animated, { FadeOut } from 'react-native-reanimated'
5
+ import { ExampleSolutionStatuses } from '../constants'
6
+ import { TEST_IDS } from '@magmamath/students-qa'
7
+
8
+ type QuestionSectionProps = {
9
+ question?: string
10
+ status: ExampleSolutionStatuses
11
+ renderKatexComponent: (katexString: string) => React.ReactNode
12
+ }
13
+
14
+ export const QuestionSection = ({
15
+ question,
16
+ status,
17
+ renderKatexComponent,
18
+ }: QuestionSectionProps) => {
19
+ return (
20
+ <View style={styles.container} testID={TEST_IDS.EXAMPLE_SOLUTION_QUESTION_SECTION}>
21
+ {question && <View style={styles.katexContainer}>{renderKatexComponent(question)}</View>}
22
+ {status === ExampleSolutionStatuses.LOADING && (
23
+ <Animated.View exiting={FadeOut} style={styles.loaderContainer}>
24
+ <Loader size="large" color={LoaderColor.BLUE} />
25
+ </Animated.View>
26
+ )}
27
+ </View>
28
+ )
29
+ }
30
+
31
+ const styles = StyleSheet.create({
32
+ container: {
33
+ flex: 1,
34
+ },
35
+ katexContainer: {
36
+ flex: 1,
37
+ },
38
+ loaderContainer: {
39
+ paddingTop: 34,
40
+ position: 'absolute',
41
+ zIndex: 100,
42
+ width: '100%',
43
+ height: '100%',
44
+ backgroundColor: COLORS.NEUTRAL_1,
45
+ justifyContent: 'flex-start',
46
+ alignItems: 'center',
47
+ },
48
+ errorContainer: {
49
+ position: 'absolute',
50
+ width: '100%',
51
+ height: '100%',
52
+ backgroundColor: COLORS.NEUTRAL_1,
53
+ justifyContent: 'center',
54
+ alignItems: 'center',
55
+ padding: SPACING[400],
56
+ },
57
+ errorText: {
58
+ color: COLORS.PRIMARY_RED,
59
+ textAlign: 'center',
60
+ },
61
+ })
@@ -0,0 +1,117 @@
1
+ import {
2
+ StyleSheet,
3
+ View,
4
+ Image,
5
+ ImageBackground,
6
+ ScrollView,
7
+ useWindowDimensions,
8
+ } from 'react-native'
9
+ import React from 'react'
10
+ import { COLORS, IS_WEB, SPACING } from '@magmamath/react-native-ui'
11
+ import Animated, { FadeIn } from 'react-native-reanimated'
12
+ import { ExampleSolutionStatuses } from '../constants'
13
+ import gridImage from '../assets/grid.webp'
14
+ import { TEST_IDS } from '@magmamath/students-qa'
15
+
16
+ type SolutionSectionProps = {
17
+ answer?: string
18
+ status: ExampleSolutionStatuses
19
+ }
20
+
21
+ const CONTAINER_SIZES = {
22
+ width: 489,
23
+ height: 543,
24
+ }
25
+ const BORDER_WIDTH = 1
26
+ const CONTAINER_HEIGHT_OFFSET = 160
27
+
28
+ const gridImagePath = !IS_WEB ? Image.resolveAssetSource(gridImage).uri : gridImage
29
+
30
+ export const SolutionSection = ({ answer, status }: SolutionSectionProps) => {
31
+ const { height } = useWindowDimensions()
32
+
33
+ const shouldShowAnswer =
34
+ answer &&
35
+ (status === ExampleSolutionStatuses.DONE_DATA || status === ExampleSolutionStatuses.CACHE)
36
+
37
+ return (
38
+ <View
39
+ style={[styles.container, IS_WEB && { maxHeight: height - CONTAINER_HEIGHT_OFFSET }]}
40
+ testID={TEST_IDS.EXAMPLE_SOLUTION_SOLUTION_SECTION}
41
+ >
42
+ <ScrollView
43
+ style={styles.solutionScroll}
44
+ contentContainerStyle={styles.solutionScrollContent}
45
+ bounces
46
+ >
47
+ {IS_WEB ? (
48
+ <ImageBackground
49
+ resizeMode="repeat"
50
+ style={[styles.gridImageWeb]}
51
+ source={{ uri: gridImagePath }}
52
+ >
53
+ {shouldShowAnswer && (
54
+ <Animated.Text
55
+ entering={status === 'cache' ? undefined : FadeIn.delay(1000).duration(500)}
56
+ style={styles.solutionText}
57
+ >
58
+ {answer}
59
+ </Animated.Text>
60
+ )}
61
+ </ImageBackground>
62
+ ) : (
63
+ <>
64
+ <ImageBackground
65
+ resizeMode="repeat"
66
+ style={styles.gridImageRn}
67
+ source={{ uri: gridImagePath }}
68
+ />
69
+ {shouldShowAnswer && (
70
+ <Animated.Text
71
+ entering={status === 'cache' ? undefined : FadeIn.delay(1000).duration(500)}
72
+ style={styles.solutionText}
73
+ >
74
+ {answer}
75
+ </Animated.Text>
76
+ )}
77
+ </>
78
+ )}
79
+ </ScrollView>
80
+ </View>
81
+ )
82
+ }
83
+
84
+ const styles = StyleSheet.create({
85
+ container: {
86
+ width: CONTAINER_SIZES.width,
87
+ height: CONTAINER_SIZES.height,
88
+ overflow: 'hidden',
89
+ borderRadius: 8,
90
+ borderWidth: BORDER_WIDTH,
91
+ borderColor: COLORS.NEUTRAL_5,
92
+ },
93
+ gridImageRn: {
94
+ top: -327,
95
+ width: '100%',
96
+ height: '150%',
97
+ position: 'absolute',
98
+ },
99
+ gridImageWeb: {
100
+ width: '100%',
101
+ height: '100%',
102
+ },
103
+ solutionScroll: {
104
+ flex: 1,
105
+ },
106
+ solutionScrollContent: {
107
+ minHeight: CONTAINER_SIZES.height - BORDER_WIDTH * 2,
108
+ width: '100%',
109
+ },
110
+ solutionText: {
111
+ fontFamily: 'PlaypenSans-Light',
112
+ fontSize: 28,
113
+ marginHorizontal: SPACING[800],
114
+ marginVertical: SPACING[400],
115
+ lineHeight: 36,
116
+ },
117
+ })
@@ -0,0 +1,5 @@
1
+ export const enum ExampleSolutionStatuses {
2
+ LOADING = 'loading',
3
+ DONE_DATA = 'doneData',
4
+ CACHE = 'cache',
5
+ }
@@ -0,0 +1,66 @@
1
+ import { useEffect, useState } from 'react'
2
+ import { useUnit } from 'effector-react'
3
+ import { ExampleSolutionModalParams, ExampleSolutionResponse } from '../types'
4
+ import { ExampleSolutionStatuses } from '../constants'
5
+
6
+ const ANIMATION_DELAY = 600
7
+
8
+ type UseExampleSolutionReturn = {
9
+ result: ExampleSolutionResponse | null
10
+ status: ExampleSolutionStatuses
11
+ }
12
+
13
+ export const useExampleSolution = ({
14
+ model,
15
+ payload,
16
+ cacheKey,
17
+ }: ExampleSolutionModalParams): UseExampleSolutionReturn => {
18
+ const exampleSolutionsCache = useUnit(model.cache.$state)
19
+ const [result, setResult] = useState<ExampleSolutionResponse | null>(null)
20
+ const [status, setStatus] = useState<ExampleSolutionStatuses>(ExampleSolutionStatuses.LOADING)
21
+
22
+ useEffect(() => {
23
+ let timer: NodeJS.Timeout | null = null
24
+
25
+ const fetchExampleSolution = async () => {
26
+ try {
27
+ if (exampleSolutionsCache[cacheKey]) {
28
+ setResult(exampleSolutionsCache[cacheKey])
29
+ setStatus(ExampleSolutionStatuses.CACHE)
30
+ return
31
+ }
32
+
33
+ const startTime = Date.now()
34
+ const response = await model.api.generateExampleSolutionFx(payload)
35
+
36
+ setResult(response)
37
+ model.cache.addExampleSolution({ cacheKey, response })
38
+
39
+ const elapsedTime = Date.now() - startTime
40
+ if (elapsedTime >= ANIMATION_DELAY) {
41
+ setStatus(ExampleSolutionStatuses.DONE_DATA)
42
+ } else {
43
+ timer = setTimeout(() => {
44
+ setStatus(ExampleSolutionStatuses.DONE_DATA)
45
+ }, ANIMATION_DELAY - elapsedTime)
46
+ }
47
+ } catch (error) {
48
+ console.error('Failed to fetch example solution:', error)
49
+ setStatus(ExampleSolutionStatuses.DONE_DATA)
50
+ }
51
+ }
52
+
53
+ fetchExampleSolution()
54
+
55
+ return () => {
56
+ if (timer) {
57
+ clearTimeout(timer)
58
+ }
59
+ }
60
+ }, [])
61
+
62
+ return {
63
+ result,
64
+ status,
65
+ }
66
+ }
@@ -0,0 +1,3 @@
1
+ export * from './ExampleSolutionModal'
2
+ export * from './model/ExampleSolution.model'
3
+ export * from './types'
@@ -0,0 +1,15 @@
1
+ import { ExampleSolutionModelProps } from '../types'
2
+ import { ExampleSolutionsApi } from './ExampleSolutionApi'
3
+ import { ExampleSolutionCache } from './ExampleSolutionCache'
4
+
5
+ export class ExampleSolutionModel {
6
+ public readonly api
7
+ public readonly cache = new ExampleSolutionCache()
8
+
9
+ public readonly renderKatexComponent: (katexString: string) => React.ReactNode
10
+
11
+ constructor({ api, errorHandler, renderKatexComponent }: ExampleSolutionModelProps) {
12
+ this.api = new ExampleSolutionsApi({ api, errorHandler })
13
+ this.renderKatexComponent = renderKatexComponent
14
+ }
15
+ }
@@ -0,0 +1,29 @@
1
+ import { createControllerEffect } from './../../../lib/effector/createControllerEffect'
2
+ import { ExampleSolutionApiRequests, ExampleSolutionErrorHandler } from '../types'
3
+ import { createEffect, sample } from 'effector'
4
+ import { getText } from '../../../i18n/i18n'
5
+
6
+ type ExampleSolutionApiProps = {
7
+ api: ExampleSolutionApiRequests
8
+ errorHandler?: ExampleSolutionErrorHandler
9
+ }
10
+
11
+ export class ExampleSolutionsApi {
12
+ public readonly generateExampleSolutionFx
13
+ public readonly errorHandlerFx?: ExampleSolutionErrorHandler
14
+
15
+ public readonly errorHandlingFx = createEffect(() => {
16
+ if (!this.errorHandlerFx) return
17
+ this.errorHandlerFx(getText('exampleSolution.errorMessage'))
18
+ })
19
+
20
+ constructor({ api, errorHandler }: ExampleSolutionApiProps) {
21
+ this.generateExampleSolutionFx = createControllerEffect(api.generateExampleSolution)
22
+ this.errorHandlerFx = errorHandler
23
+
24
+ sample({
25
+ clock: this.generateExampleSolutionFx.fail,
26
+ target: this.errorHandlingFx,
27
+ })
28
+ }
29
+ }
@@ -0,0 +1,20 @@
1
+ import { createEvent, createStore } from 'effector'
2
+ import { ExampleSolutionResponse } from '../types'
3
+
4
+ type AddExampleSolution = {
5
+ cacheKey: string
6
+ response: ExampleSolutionResponse
7
+ }
8
+
9
+ export class ExampleSolutionCache {
10
+ public readonly addExampleSolution = createEvent<AddExampleSolution>()
11
+ public readonly $state = createStore<Record<string, ExampleSolutionResponse>>({}).on(
12
+ this.addExampleSolution,
13
+ (state, exampleSolution) => {
14
+ return {
15
+ ...state,
16
+ [exampleSolution.cacheKey]: exampleSolution.response,
17
+ }
18
+ },
19
+ )
20
+ }
@@ -0,0 +1,27 @@
1
+ import { ExampleSolutionModel } from './model/ExampleSolution.model'
2
+
3
+ export type ExampleSolutionPayload = {
4
+ question: string
5
+ answer: string
6
+ language: string
7
+ altText?: string
8
+ imageCdnId?: string
9
+ }
10
+ export type ExampleSolutionResponse = {
11
+ question: string
12
+ answer: string
13
+ }
14
+ export type ExampleSolutionApiRequests = {
15
+ generateExampleSolution: (body: ExampleSolutionPayload) => Promise<ExampleSolutionResponse>
16
+ }
17
+ export type ExampleSolutionErrorHandler = (errorMessage: string) => void
18
+ export type ExampleSolutionModelProps = {
19
+ api: ExampleSolutionApiRequests
20
+ errorHandler?: ExampleSolutionErrorHandler
21
+ renderKatexComponent: (katexString: string) => React.ReactNode
22
+ }
23
+ export type ExampleSolutionModalParams = {
24
+ cacheKey: string
25
+ payload: ExampleSolutionPayload
26
+ model: ExampleSolutionModel
27
+ }
@@ -362,3 +362,67 @@ describe('answer area is SIMPLE only in simple mode when practicing, has variant
362
362
  expect(result.answerArea).toBe(UIMode.REGULAR)
363
363
  })
364
364
  })
365
+
366
+ describe('K2 student in PM with special char types gets REGULAR answer area (special keyboard must show)', () => {
367
+ const k2PmBase = { practice: { isEnabled: true, grade: 1 } }
368
+
369
+ it('CURRENCY characterType → REGULAR', () => {
370
+ const result = getUIModeWithDefaults({
371
+ ...k2PmBase,
372
+ problem: {
373
+ answerType: AnswerType.HANDWRITING,
374
+ characterType: MyScriptMathCharacterTypes.CURRENCY,
375
+ hasAnswerVariants: false,
376
+ },
377
+ })
378
+ expect(result.answerArea).toBe(UIMode.REGULAR)
379
+ })
380
+
381
+ it('UNITS characterType → REGULAR', () => {
382
+ const result = getUIModeWithDefaults({
383
+ ...k2PmBase,
384
+ problem: {
385
+ answerType: AnswerType.HANDWRITING,
386
+ characterType: MyScriptMathCharacterTypes.UNITS,
387
+ hasAnswerVariants: false,
388
+ },
389
+ })
390
+ expect(result.answerArea).toBe(UIMode.REGULAR)
391
+ })
392
+
393
+ it('NUMBERS_AND_UNITS characterType → REGULAR', () => {
394
+ const result = getUIModeWithDefaults({
395
+ ...k2PmBase,
396
+ problem: {
397
+ answerType: AnswerType.HANDWRITING,
398
+ characterType: MyScriptMathCharacterTypes.NUMBERS_AND_UNITS,
399
+ hasAnswerVariants: false,
400
+ },
401
+ })
402
+ expect(result.answerArea).toBe(UIMode.REGULAR)
403
+ })
404
+
405
+ it('TIME characterType → REGULAR', () => {
406
+ const result = getUIModeWithDefaults({
407
+ ...k2PmBase,
408
+ problem: {
409
+ answerType: AnswerType.HANDWRITING,
410
+ characterType: MyScriptMathCharacterTypes.TIME,
411
+ hasAnswerVariants: false,
412
+ },
413
+ })
414
+ expect(result.answerArea).toBe(UIMode.REGULAR)
415
+ })
416
+
417
+ it('DEFAULT characterType → still SIMPLE (regression guard)', () => {
418
+ const result = getUIModeWithDefaults({
419
+ ...k2PmBase,
420
+ problem: {
421
+ answerType: AnswerType.HANDWRITING,
422
+ characterType: MyScriptMathCharacterTypes.DEFAULT,
423
+ hasAnswerVariants: false,
424
+ },
425
+ })
426
+ expect(result.answerArea).toBe(UIMode.SIMPLE)
427
+ })
428
+ })
@@ -25,12 +25,27 @@ export const isSimpleMode = ({ user, practice, assignment }: IsSimpleModeProps)
25
25
  return isBelowGradeK2(user.grade)
26
26
  }
27
27
 
28
+ const SPECIAL_CHAR_TYPES = [
29
+ MyScriptMathCharacterTypes.CURRENCY,
30
+ MyScriptMathCharacterTypes.UNITS,
31
+ MyScriptMathCharacterTypes.NUMBERS_AND_UNITS,
32
+ MyScriptMathCharacterTypes.TIME,
33
+ ]
34
+
28
35
  export const getAnswerAreaMode = ({ practice, problem }: GetAnswerAreaModeProps): UIMode => {
29
36
  const isHandwritingNumbersWhole =
30
37
  problem.answerType === AnswerType.HANDWRITING &&
31
38
  problem.characterType === MyScriptMathCharacterTypes.NUMBERS_WHOLE
32
39
 
33
- if (practice.isEnabled || problem.hasAnswerVariants || isHandwritingNumbersWhole) {
40
+ if (problem.hasAnswerVariants || isHandwritingNumbersWhole) {
41
+ return UIMode.SIMPLE
42
+ }
43
+
44
+ const needsSpecialKeyboard =
45
+ problem.answerType === AnswerType.HANDWRITING &&
46
+ SPECIAL_CHAR_TYPES.includes(problem.characterType)
47
+
48
+ if (practice.isEnabled && !needsSpecialKeyboard) {
34
49
  return UIMode.SIMPLE
35
50
  }
36
51
 
package/src/i18n/i18n.ts CHANGED
@@ -8,6 +8,7 @@ export type TranslationKey = ParseKeys<typeof NAMESPACE>
8
8
  export type GetText = TFunction<typeof NAMESPACE>
9
9
 
10
10
  export const getText: GetText = ((key: TranslationKey, options?: object) =>
11
- i18next.t(key, { ns: NAMESPACE, ...options })) as GetText
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ (i18next.t as any)(key, { ns: NAMESPACE, ...options })) as GetText
12
13
 
13
14
  export const useText = (): GetText => useTranslation(NAMESPACE).t
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ export * from './features/endOfAssignment'
6
6
  export * from './features/formulaSheet'
7
7
  export * from './features/problemSelector'
8
8
  export * from './features/gifCelebrations'
9
+ export * from './features/exampleSolution'
9
10
  export * from './features/keyboard'
10
11
  export * from './features/pmProgress'
11
12
  export * from './features/openEnded'