@shopify/shop-minis-cli 0.0.37 → 0.0.38
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.
- package/build/commands/check/index.d.ts +1 -0
- package/build/commands/check/index.js +11 -1
- package/build/commands/check/index.js.map +1 -1
- package/build/commands/check/utils/manifest.d.ts +4 -0
- package/build/commands/check/utils/manifest.js +26 -0
- package/build/commands/check/utils/manifest.js.map +1 -0
- package/build/commands/check/utils/schemas/contextual-image.schema.json +21 -0
- package/build/commands/check/utils/schemas/contextual-object.schema.json +17 -0
- package/build/commands/check/utils/schemas/manifest.schema.json +226 -0
- package/build/commands/check/utils/schemas/visibility.schema.json +17 -0
- package/package.json +8 -1
- package/templates/__template_common/package.json +1 -0
- package/templates/__template_hello_world/src/components/ComponentLink.ts +6 -2
- package/templates/__template_hello_world/src/components/quiz/ImageCarouselSlide.tsx +54 -0
- package/templates/__template_hello_world/src/components/quiz/MultipleChoiceSlide.tsx +77 -0
- package/templates/__template_hello_world/src/components/quiz/QuizProvider.tsx +64 -0
- package/templates/__template_hello_world/src/components/quiz/QuizSlide.tsx +76 -0
- package/templates/__template_hello_world/src/components/quiz/QuizSlideCommander.tsx +35 -0
- package/templates/__template_hello_world/src/components/quiz/TextFieldSlide.tsx +47 -0
- package/templates/__template_hello_world/src/components/quiz/TextSlide.tsx +6 -0
- package/templates/__template_hello_world/src/components/quiz/types.ts +72 -0
- package/templates/__template_hello_world/src/hooks/useQuizData.ts +26 -0
- package/templates/__template_hello_world/src/hooks/useQuizState.ts +27 -0
- package/templates/__template_hello_world/src/routes.tsx +9 -3
- package/templates/__template_hello_world/src/screens/{ImageCarouselScreen.tsx → ImageMultipleChoiceScreen.tsx} +4 -6
- package/templates/__template_hello_world/src/screens/InputScreen.tsx +42 -58
- package/templates/__template_hello_world/src/screens/QuizResultScreen.tsx +82 -0
- package/templates/__template_hello_world/src/screens/QuizScreen.tsx +40 -0
- package/templates/__template_hello_world/src/screens/QuizSlideScreen.tsx +150 -0
- package/templates/__template_hello_world/src/types.ts +2 -1
- package/templates/__template_hello_world/src/utils/mockQuizData.ts +313 -0
- package/templates/__template_hello_world/src/utils/quizUtils.ts +75 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {Box, Button, Text, useTheme} from '@shopify/shop-minis-platform-sdk'
|
|
2
|
+
import {ReactNode} from 'react'
|
|
3
|
+
import {StyleSheet, useWindowDimensions} from 'react-native'
|
|
4
|
+
|
|
5
|
+
import {QuizSlideType} from './types'
|
|
6
|
+
|
|
7
|
+
export interface QuizSlideProps extends QuizSlideType {
|
|
8
|
+
children?: ReactNode
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const QuizSlide = (props: QuizSlideProps) => {
|
|
12
|
+
const {
|
|
13
|
+
mainText,
|
|
14
|
+
subText,
|
|
15
|
+
ctaText,
|
|
16
|
+
ctaOnPressHandler,
|
|
17
|
+
ctaDisabled,
|
|
18
|
+
children,
|
|
19
|
+
isTallSlide,
|
|
20
|
+
} = props
|
|
21
|
+
|
|
22
|
+
const theme = useTheme()
|
|
23
|
+
const gutter = theme.spacing.gutter
|
|
24
|
+
|
|
25
|
+
const {height: windowHeight, width} = useWindowDimensions()
|
|
26
|
+
|
|
27
|
+
const containerWidth = width - gutter * 2
|
|
28
|
+
const containerHeight = windowHeight - 245
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Box
|
|
32
|
+
height={containerHeight}
|
|
33
|
+
flex={1}
|
|
34
|
+
paddingHorizontal="gutter"
|
|
35
|
+
paddingTop={isTallSlide ? 'xxxl' : 'auto'}
|
|
36
|
+
position="relative"
|
|
37
|
+
justifyContent="center"
|
|
38
|
+
>
|
|
39
|
+
<Box marginBottom="m" width={containerWidth}>
|
|
40
|
+
<Text variant="subtitle">{mainText}</Text>
|
|
41
|
+
{subText ? (
|
|
42
|
+
<Text marginTop="xxs" variant="bodySmall">
|
|
43
|
+
{subText}
|
|
44
|
+
</Text>
|
|
45
|
+
) : null}
|
|
46
|
+
</Box>
|
|
47
|
+
<Box marginBottom="xxxl" paddingBottom={isTallSlide ? 'l' : 'none'}>
|
|
48
|
+
{children}
|
|
49
|
+
</Box>
|
|
50
|
+
<Box style={styles.ctaContainer} paddingBottom="s" width={containerWidth}>
|
|
51
|
+
<Button
|
|
52
|
+
size="l"
|
|
53
|
+
style={{width: containerWidth}}
|
|
54
|
+
onPress={
|
|
55
|
+
ctaOnPressHandler
|
|
56
|
+
? ctaOnPressHandler
|
|
57
|
+
: () => console.log('Next Press')
|
|
58
|
+
}
|
|
59
|
+
variant="secondary"
|
|
60
|
+
text={ctaText ?? 'Next'}
|
|
61
|
+
disabled={ctaDisabled ?? false}
|
|
62
|
+
/>
|
|
63
|
+
</Box>
|
|
64
|
+
</Box>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const styles = StyleSheet.create({
|
|
69
|
+
ctaContainer: {
|
|
70
|
+
alignItems: 'center',
|
|
71
|
+
position: 'absolute',
|
|
72
|
+
bottom: 0,
|
|
73
|
+
left: 0,
|
|
74
|
+
right: 0,
|
|
75
|
+
},
|
|
76
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {ImageCarouselSlide, ImageCarouselSlideProps} from './ImageCarouselSlide'
|
|
2
|
+
import {
|
|
3
|
+
MultipleChoiceSlide,
|
|
4
|
+
MultipleChoiceSlideProps,
|
|
5
|
+
} from './MultipleChoiceSlide'
|
|
6
|
+
import {QuizSlide} from './QuizSlide'
|
|
7
|
+
import {TextFieldSlide, TextFieldSlideProps} from './TextFieldSlide'
|
|
8
|
+
import {TextSlide} from './TextSlide'
|
|
9
|
+
import {QuizSlideScreenType, TextSlideProps} from './types'
|
|
10
|
+
|
|
11
|
+
export interface QuizSlideCommanderProps {
|
|
12
|
+
type: QuizSlideScreenType
|
|
13
|
+
props: TextSlideProps | MultipleChoiceSlideProps | ImageCarouselSlideProps
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const QuizSlideCommander = ({type, props}: QuizSlideCommanderProps) => {
|
|
17
|
+
switch (type) {
|
|
18
|
+
case 'TextSlide':
|
|
19
|
+
return <TextSlide {...(props as TextSlideProps)} />
|
|
20
|
+
case 'TextFieldSlide':
|
|
21
|
+
return <TextFieldSlide {...(props as TextFieldSlideProps)} />
|
|
22
|
+
case 'ImageCarouselSlide':
|
|
23
|
+
return <ImageCarouselSlide {...(props as ImageCarouselSlideProps)} />
|
|
24
|
+
case 'MultipleChoiceSlide':
|
|
25
|
+
return <MultipleChoiceSlide {...(props as MultipleChoiceSlideProps)} />
|
|
26
|
+
default:
|
|
27
|
+
return (
|
|
28
|
+
<QuizSlide
|
|
29
|
+
mainText="error"
|
|
30
|
+
subText={`slide type ${type} is unknown`}
|
|
31
|
+
ctaText="oh no!"
|
|
32
|
+
/>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {Box, TextField} from '@shopify/shop-minis-platform-sdk'
|
|
2
|
+
import {ComponentProps, useCallback, useEffect, useState} from 'react'
|
|
3
|
+
|
|
4
|
+
import {QuizSlide, QuizSlideProps} from './QuizSlide'
|
|
5
|
+
import {SlideLogic} from './types'
|
|
6
|
+
|
|
7
|
+
export interface TextFieldSlideProps extends SlideLogic<string> {
|
|
8
|
+
quizSlideProps: QuizSlideProps
|
|
9
|
+
textFieldProps: ComponentProps<typeof TextField>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const TextFieldSlide = ({
|
|
13
|
+
quizSlideProps,
|
|
14
|
+
textFieldProps,
|
|
15
|
+
determineCtaDisabled,
|
|
16
|
+
handleAnswerSelection,
|
|
17
|
+
}: TextFieldSlideProps) => {
|
|
18
|
+
const [fieldValue, setFieldValue] = useState<string>()
|
|
19
|
+
|
|
20
|
+
const [isCtaDisabled, setCtaDisabled] = useState(
|
|
21
|
+
quizSlideProps.ctaDisabled ?? false
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
const handleChange = useCallback((newTextValue: string) => {
|
|
25
|
+
setFieldValue(newTextValue)
|
|
26
|
+
}, [])
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (handleAnswerSelection === undefined || fieldValue === undefined) return
|
|
30
|
+
|
|
31
|
+
handleAnswerSelection(fieldValue)
|
|
32
|
+
}, [fieldValue, handleAnswerSelection])
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (determineCtaDisabled === undefined || fieldValue === undefined) return
|
|
36
|
+
|
|
37
|
+
setCtaDisabled(determineCtaDisabled(fieldValue))
|
|
38
|
+
}, [determineCtaDisabled, fieldValue])
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<QuizSlide {...quizSlideProps} ctaDisabled={isCtaDisabled}>
|
|
42
|
+
<Box marginBottom="xl">
|
|
43
|
+
<TextField {...textFieldProps} onChangeText={handleChange} />
|
|
44
|
+
</Box>
|
|
45
|
+
</QuizSlide>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type {ImageCarouselSlideProps} from './ImageCarouselSlide'
|
|
2
|
+
import type {MultipleChoiceSlideProps} from './MultipleChoiceSlide'
|
|
3
|
+
import type {TextFieldSlideProps} from './TextFieldSlide'
|
|
4
|
+
|
|
5
|
+
export interface QuizSlideType {
|
|
6
|
+
mainText: string
|
|
7
|
+
ctaOnPressHandler?: () => void
|
|
8
|
+
ctaDisabled?: boolean
|
|
9
|
+
subText?: string
|
|
10
|
+
ctaText?: string // default to 'next'
|
|
11
|
+
isTallSlide?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface TextSlideProps extends SlideLogic<string> {
|
|
15
|
+
quizSlideProps: QuizSlideType
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type QuizSlideScreenType =
|
|
19
|
+
| 'TextSlide'
|
|
20
|
+
| 'TextFieldSlide'
|
|
21
|
+
| 'MultipleChoiceSlide'
|
|
22
|
+
| 'ImageCarouselSlide'
|
|
23
|
+
|
|
24
|
+
export interface SlideInformation {
|
|
25
|
+
id: string
|
|
26
|
+
type: QuizSlideScreenType
|
|
27
|
+
// When adding the slide props, we get a dependency cycle
|
|
28
|
+
props:
|
|
29
|
+
| TextSlideProps
|
|
30
|
+
| TextFieldSlideProps
|
|
31
|
+
| MultipleChoiceSlideProps
|
|
32
|
+
| ImageCarouselSlideProps
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface QuizData {
|
|
36
|
+
slides: SlideInformation[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SlideLogic<T extends QuizAnswerType> {
|
|
40
|
+
handleAnswerSelection?: (answer: T) => void
|
|
41
|
+
determineNextSlide?: (answer: T) => string
|
|
42
|
+
determineCtaDisabled?: (answer: T) => boolean
|
|
43
|
+
nextSlideId?: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// @react-navigation/native-stack requires a `type` instead of an `interface`
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
48
|
+
export type QuizStackParamList = {
|
|
49
|
+
'__MINI_APP_HANDLE_PASCAL_CASE__.QuizResult': undefined
|
|
50
|
+
'__MINI_APP_HANDLE_PASCAL_CASE__.QuizSlide': {
|
|
51
|
+
slideIndex: number
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface SlideChoiceConsequence {
|
|
56
|
+
nextSlideId?: string
|
|
57
|
+
skipSlideIds?: [string]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type QuizAnswerType = string | number | number[]
|
|
61
|
+
|
|
62
|
+
export type DetermineNextSlide<T> = (answer: T) => string
|
|
63
|
+
|
|
64
|
+
export interface QuizResultEntry {
|
|
65
|
+
slideType: QuizSlideScreenType
|
|
66
|
+
answer: QuizAnswerType
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// key is the ID of the question
|
|
70
|
+
export type QuizResult = Record<string, QuizResultEntry>
|
|
71
|
+
|
|
72
|
+
export type QuizDataState = 'Loading' | 'Ready'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import {useEffect, useState} from 'react'
|
|
2
|
+
|
|
3
|
+
import {QuizData, QuizDataState} from '../components/quiz/types'
|
|
4
|
+
import {mockQuizData} from '../utils/mockQuizData'
|
|
5
|
+
|
|
6
|
+
const MOCK_FETCH_TIME = 2000
|
|
7
|
+
|
|
8
|
+
export const useQuizData = () => {
|
|
9
|
+
const [quizDataState, setQuizDataState] = useState<QuizDataState>('Loading')
|
|
10
|
+
const [quizData, setQuizData] = useState<QuizData>()
|
|
11
|
+
|
|
12
|
+
// Here is where you would fetch the quesitons from your server
|
|
13
|
+
// and process them into the shape you need
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const timer = setTimeout(() => {
|
|
16
|
+
setQuizData(mockQuizData)
|
|
17
|
+
setQuizDataState('Ready')
|
|
18
|
+
}, MOCK_FETCH_TIME)
|
|
19
|
+
return () => clearTimeout(timer)
|
|
20
|
+
}, [])
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
quizDataState,
|
|
24
|
+
quizData,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {useCallback, useState} from 'react'
|
|
2
|
+
|
|
3
|
+
import {QuizResult, QuizResultEntry} from '../components/quiz/types'
|
|
4
|
+
|
|
5
|
+
export const useQuizState = () => {
|
|
6
|
+
const [userName, setUserName] = useState('you')
|
|
7
|
+
|
|
8
|
+
const [quizResult, setQuizResult] = useState<QuizResult>()
|
|
9
|
+
|
|
10
|
+
const recordQuestionResult = useCallback(
|
|
11
|
+
(id: string, answer: QuizResultEntry) => {
|
|
12
|
+
const newQuizResult = {
|
|
13
|
+
...quizResult,
|
|
14
|
+
[id]: answer,
|
|
15
|
+
}
|
|
16
|
+
setQuizResult(newQuizResult)
|
|
17
|
+
},
|
|
18
|
+
[quizResult]
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
recordQuestionResult,
|
|
23
|
+
quizResult,
|
|
24
|
+
setUserName,
|
|
25
|
+
userName,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -18,7 +18,8 @@ import {ProductCardScreen} from './screens/ProductCardScreen'
|
|
|
18
18
|
import {ProductCardGridScreen} from './screens/ProductCardGridScreen'
|
|
19
19
|
import {InputScreen} from './screens/InputScreen'
|
|
20
20
|
import {MultipleChoiceScreen} from './screens/MultipleChoiceScreen'
|
|
21
|
-
import {
|
|
21
|
+
import {QuizScreen} from './screens/QuizScreen'
|
|
22
|
+
import {ImageMultipleChoiceScreen} from './screens/ImageMultipleChoiceScreen'
|
|
22
23
|
|
|
23
24
|
const Stack = createNativeStackNavigator()
|
|
24
25
|
|
|
@@ -116,8 +117,13 @@ export const __MINI_APP_HANDLE_PASCAL_CASE__Navigator = () => {
|
|
|
116
117
|
options={{headerShown: false}}
|
|
117
118
|
/>
|
|
118
119
|
<Stack.Screen
|
|
119
|
-
name="__MINI_APP_HANDLE_PASCAL_CASE__.
|
|
120
|
-
component={
|
|
120
|
+
name="__MINI_APP_HANDLE_PASCAL_CASE__.ImageMultipleChoice"
|
|
121
|
+
component={ImageMultipleChoiceScreen}
|
|
122
|
+
options={{headerShown: false}}
|
|
123
|
+
/>
|
|
124
|
+
<Stack.Screen
|
|
125
|
+
name="__MINI_APP_HANDLE_PASCAL_CASE__.Quiz"
|
|
126
|
+
component={QuizScreen}
|
|
121
127
|
options={{headerShown: false}}
|
|
122
128
|
/>
|
|
123
129
|
</Stack.Navigator>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Box,
|
|
3
3
|
Button,
|
|
4
|
-
|
|
4
|
+
ImageMultipleChoice,
|
|
5
5
|
ImageChoiceType,
|
|
6
6
|
Text,
|
|
7
7
|
useMultiSelect,
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
} from '@shopify/shop-minis-platform-sdk'
|
|
10
10
|
import {useMemo, useState} from 'react'
|
|
11
11
|
import {SafeAreaView, ScrollView} from 'react-native'
|
|
12
|
-
import {Header} from '../components/Header'
|
|
13
12
|
|
|
14
13
|
const DEFAULT_IMAGE_SIZE = 160
|
|
15
14
|
const MOCK_IMAGE_INDEX_OFFSET = 20
|
|
@@ -62,7 +61,7 @@ const ControlSection = ({label, children}: ControlSectionProps) => (
|
|
|
62
61
|
{children}
|
|
63
62
|
</Box>
|
|
64
63
|
)
|
|
65
|
-
export const
|
|
64
|
+
export const ImageMultipleChoiceScreen = () => {
|
|
66
65
|
const [choiceText, setChoiceText] =
|
|
67
66
|
useState<keyof typeof textOptions>('short')
|
|
68
67
|
|
|
@@ -82,7 +81,6 @@ export const ImageCarouselScreen = () => {
|
|
|
82
81
|
|
|
83
82
|
return (
|
|
84
83
|
<SafeAreaView style={{flex: 1}}>
|
|
85
|
-
<Header />
|
|
86
84
|
<Box paddingVertical="xs">
|
|
87
85
|
<ControlSection label="Text Length">
|
|
88
86
|
<Box flexDirection="row">
|
|
@@ -120,7 +118,7 @@ export const ImageCarouselScreen = () => {
|
|
|
120
118
|
<Box marginVertical="s" marginHorizontal="gutter">
|
|
121
119
|
<Text variant="headerNormal">Single Select</Text>
|
|
122
120
|
</Box>
|
|
123
|
-
<
|
|
121
|
+
<ImageMultipleChoice
|
|
124
122
|
initialOffsetX={theme.spacing.gutter}
|
|
125
123
|
imageSize={imageSize}
|
|
126
124
|
choices={choices}
|
|
@@ -133,7 +131,7 @@ export const ImageCarouselScreen = () => {
|
|
|
133
131
|
<Box marginVertical="s" marginHorizontal="gutter">
|
|
134
132
|
<Text variant="headerNormal">Multi Select</Text>
|
|
135
133
|
</Box>
|
|
136
|
-
<
|
|
134
|
+
<ImageMultipleChoice
|
|
137
135
|
initialOffsetX={theme.spacing.gutter}
|
|
138
136
|
imageSize={imageSize}
|
|
139
137
|
choices={choices}
|
|
@@ -2,22 +2,23 @@ import {ItemValue} from '@react-native-community/picker/typings/Picker'
|
|
|
2
2
|
import {
|
|
3
3
|
Box,
|
|
4
4
|
DatePicker,
|
|
5
|
-
Icon,
|
|
6
5
|
KeyboardAvoidingView,
|
|
7
6
|
Picker,
|
|
7
|
+
TextFieldPhoneNumber,
|
|
8
8
|
TextField,
|
|
9
|
+
TextFieldAutofill,
|
|
9
10
|
Text,
|
|
11
|
+
countryCodesAndPhonePrefixes,
|
|
12
|
+
getFlagEmoji,
|
|
10
13
|
} from '@shopify/shop-minis-platform-sdk'
|
|
11
14
|
import {useState} from 'react'
|
|
12
15
|
import {
|
|
13
16
|
NativeSyntheticEvent,
|
|
14
|
-
Pressable,
|
|
15
17
|
SafeAreaView,
|
|
16
18
|
ScrollView,
|
|
17
19
|
TextInputEndEditingEventData,
|
|
18
20
|
} from 'react-native'
|
|
19
21
|
|
|
20
|
-
import {getFlagEmoji} from '../utils/getFlagEmoji'
|
|
21
22
|
import {Header} from '../components/Header'
|
|
22
23
|
|
|
23
24
|
interface Item {
|
|
@@ -26,31 +27,12 @@ interface Item {
|
|
|
26
27
|
selected?: boolean
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const countryData: Item[] = [
|
|
37
|
-
{
|
|
38
|
-
value: 'US',
|
|
39
|
-
label: `US ${getFlagEmoji('US')}`,
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
value: 'DE',
|
|
43
|
-
label: `DE ${getFlagEmoji('DE')}`,
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
value: 'BR',
|
|
47
|
-
label: `BR ${getFlagEmoji('BR')}`,
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
value: 'NL',
|
|
51
|
-
label: `NL ${getFlagEmoji('NL')}`,
|
|
52
|
-
},
|
|
53
|
-
]
|
|
30
|
+
const countryData: Item[] = countryCodesAndPhonePrefixes.map(
|
|
31
|
+
({code, name}) => ({
|
|
32
|
+
value: code,
|
|
33
|
+
label: `${name} ${getFlagEmoji(code)}`,
|
|
34
|
+
})
|
|
35
|
+
)
|
|
54
36
|
|
|
55
37
|
export const InputScreen = () => {
|
|
56
38
|
const [showCountryPicker, setShowCountryPicker] = useState(false)
|
|
@@ -85,14 +67,38 @@ export const InputScreen = () => {
|
|
|
85
67
|
</Box>
|
|
86
68
|
<Box marginBottom="s">
|
|
87
69
|
<Box marginBottom="xs">
|
|
88
|
-
<Text variant="bodyLargeBold">Text
|
|
70
|
+
<Text variant="bodyLargeBold">Text</Text>
|
|
89
71
|
</Box>
|
|
90
72
|
|
|
91
73
|
<TextField
|
|
74
|
+
placeholder="Enter a word"
|
|
75
|
+
onChangeText={console.log}
|
|
76
|
+
/>
|
|
77
|
+
</Box>
|
|
78
|
+
<Box marginBottom="s">
|
|
79
|
+
<Box marginBottom="xs">
|
|
80
|
+
<Text variant="bodyLargeBold">Email with Autofill</Text>
|
|
81
|
+
</Box>
|
|
82
|
+
|
|
83
|
+
<TextFieldAutofill
|
|
84
|
+
type="email"
|
|
85
|
+
shopId="shopify"
|
|
92
86
|
placeholder="Enter your email"
|
|
93
87
|
onChangeText={console.log}
|
|
94
88
|
/>
|
|
95
89
|
</Box>
|
|
90
|
+
<Box marginBottom="s">
|
|
91
|
+
<Box marginBottom="xs">
|
|
92
|
+
<Text variant="bodyLargeBold">Name with Autofill</Text>
|
|
93
|
+
</Box>
|
|
94
|
+
|
|
95
|
+
<TextFieldAutofill
|
|
96
|
+
type="name"
|
|
97
|
+
shopId="shopify"
|
|
98
|
+
placeholder="Enter your name"
|
|
99
|
+
onChangeText={console.log}
|
|
100
|
+
/>
|
|
101
|
+
</Box>
|
|
96
102
|
<Box marginBottom="s">
|
|
97
103
|
<Box marginBottom="xs">
|
|
98
104
|
<Text variant="bodyLargeBold">Date</Text>
|
|
@@ -113,36 +119,14 @@ export const InputScreen = () => {
|
|
|
113
119
|
</Box>
|
|
114
120
|
<Box marginBottom="s">
|
|
115
121
|
<Box marginBottom="xs">
|
|
116
|
-
<Text variant="bodyLargeBold">
|
|
122
|
+
<Text variant="bodyLargeBold">TextFieldPhoneNumber</Text>
|
|
117
123
|
</Box>
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
countryCodeToPrefixMap[
|
|
125
|
-
selectedCountry as keyof typeof countryCodeToPrefixMap
|
|
126
|
-
]
|
|
127
|
-
}
|
|
128
|
-
</Text>
|
|
129
|
-
) : null
|
|
130
|
-
}
|
|
131
|
-
trailingComponent={
|
|
132
|
-
<Pressable onPress={() => setShowCountryPicker(true)}>
|
|
133
|
-
<Box
|
|
134
|
-
flexDirection="row"
|
|
135
|
-
borderStartWidth={1}
|
|
136
|
-
borderStartColor="inputs-border-color"
|
|
137
|
-
paddingStart="xs"
|
|
138
|
-
>
|
|
139
|
-
<Text marginEnd="xs">
|
|
140
|
-
{getFlagEmoji(selectedCountry ?? 'US')}
|
|
141
|
-
</Text>
|
|
142
|
-
<Icon name="chevron-down" />
|
|
143
|
-
</Box>
|
|
144
|
-
</Pressable>
|
|
145
|
-
}
|
|
124
|
+
<TextFieldPhoneNumber
|
|
125
|
+
selectedCountry={selectedCountry}
|
|
126
|
+
onFlagPress={() => setShowCountryPicker(true)}
|
|
127
|
+
shopId="shop"
|
|
128
|
+
autofill
|
|
129
|
+
onPhoneInfoChange={console.log}
|
|
146
130
|
/>
|
|
147
131
|
</Box>
|
|
148
132
|
<Box marginBottom="s">
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {Box, Text} from '@shopify/shop-minis-platform-sdk'
|
|
2
|
+
import {useMemo} from 'react'
|
|
3
|
+
import {Image, SafeAreaView, ScrollView} from 'react-native'
|
|
4
|
+
|
|
5
|
+
import {useQuizContext} from '../components/quiz/QuizProvider'
|
|
6
|
+
import {
|
|
7
|
+
addNameToText,
|
|
8
|
+
getImageCarouselAnswerUrls,
|
|
9
|
+
getMultilpleChoiceAnswerLabels,
|
|
10
|
+
getQuestionById,
|
|
11
|
+
} from '../utils/quizUtils'
|
|
12
|
+
|
|
13
|
+
export const QuizResultScreen = () => {
|
|
14
|
+
const {quizResult, quizData, userName} = useQuizContext()
|
|
15
|
+
|
|
16
|
+
const formattedResults = useMemo(() => {
|
|
17
|
+
if (!quizData) return []
|
|
18
|
+
|
|
19
|
+
return Object.entries(quizResult!).map(([questionId, result]) => {
|
|
20
|
+
let answer = ''
|
|
21
|
+
// image carousel type will be an imageUrl
|
|
22
|
+
let type: 'imageUrl' | 'string' = 'string'
|
|
23
|
+
|
|
24
|
+
switch (result.slideType) {
|
|
25
|
+
case 'MultipleChoiceSlide':
|
|
26
|
+
answer = getMultilpleChoiceAnswerLabels(
|
|
27
|
+
quizData,
|
|
28
|
+
questionId,
|
|
29
|
+
result.answer as number[]
|
|
30
|
+
).join(', ')
|
|
31
|
+
break
|
|
32
|
+
case 'ImageCarouselSlide':
|
|
33
|
+
type = 'imageUrl'
|
|
34
|
+
answer = getImageCarouselAnswerUrls(
|
|
35
|
+
quizData,
|
|
36
|
+
questionId,
|
|
37
|
+
result.answer as number[]
|
|
38
|
+
)[0]
|
|
39
|
+
break
|
|
40
|
+
default:
|
|
41
|
+
answer = result.answer as string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const question = addNameToText(
|
|
45
|
+
getQuestionById(quizData, questionId),
|
|
46
|
+
userName
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
type,
|
|
51
|
+
question,
|
|
52
|
+
answer,
|
|
53
|
+
}
|
|
54
|
+
})
|
|
55
|
+
}, [quizData, quizResult, userName])
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<SafeAreaView>
|
|
59
|
+
<ScrollView>
|
|
60
|
+
<Box paddingHorizontal="gutter" paddingVertical="xs">
|
|
61
|
+
<Text variant="posterXS" textAlign="center" marginVertical="m">
|
|
62
|
+
Results
|
|
63
|
+
</Text>
|
|
64
|
+
{formattedResults.map(({question, answer, type}, index) => (
|
|
65
|
+
<Box key={question} marginBottom="s">
|
|
66
|
+
<Text variant="headerNormal" marginBottom="xs">
|
|
67
|
+
{index + 1}. {question}
|
|
68
|
+
</Text>
|
|
69
|
+
{type === 'imageUrl' ? (
|
|
70
|
+
<Image
|
|
71
|
+
style={{width: 160, height: 160}}
|
|
72
|
+
source={{uri: answer}}
|
|
73
|
+
/>
|
|
74
|
+
) : null}
|
|
75
|
+
{type === 'string' ? <Text>Answer: {answer}</Text> : null}
|
|
76
|
+
</Box>
|
|
77
|
+
))}
|
|
78
|
+
</Box>
|
|
79
|
+
</ScrollView>
|
|
80
|
+
</SafeAreaView>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import {NavigationContainer} from '@react-navigation/native'
|
|
2
|
+
import {createNativeStackNavigator} from '@react-navigation/native-stack'
|
|
3
|
+
|
|
4
|
+
import {Header} from '../components/Header'
|
|
5
|
+
import {QuizContextProvider} from '../components/quiz/QuizProvider'
|
|
6
|
+
|
|
7
|
+
import {QuizResultScreen} from './QuizResultScreen'
|
|
8
|
+
import {QuizSlideScreen} from './QuizSlideScreen'
|
|
9
|
+
|
|
10
|
+
const Stack = createNativeStackNavigator()
|
|
11
|
+
|
|
12
|
+
export const QuizScreen = () => {
|
|
13
|
+
return (
|
|
14
|
+
<QuizContextProvider>
|
|
15
|
+
<>
|
|
16
|
+
<Header />
|
|
17
|
+
<NavigationContainer independent>
|
|
18
|
+
<Stack.Navigator>
|
|
19
|
+
<Stack.Screen
|
|
20
|
+
name="__MINI_APP_HANDLE_PASCAL_CASE__.QuizSlide"
|
|
21
|
+
component={QuizSlideScreen}
|
|
22
|
+
options={{
|
|
23
|
+
title: '__MINI_APP_HANDLE_PASCAL_CASE__ Quiz',
|
|
24
|
+
headerBackTitle: 'Previous',
|
|
25
|
+
}}
|
|
26
|
+
initialParams={{
|
|
27
|
+
slideIndex: 0,
|
|
28
|
+
}}
|
|
29
|
+
/>
|
|
30
|
+
<Stack.Screen
|
|
31
|
+
name="__MINI_APP_HANDLE_PASCAL_CASE__.QuizResult"
|
|
32
|
+
component={QuizResultScreen}
|
|
33
|
+
options={{headerShown: false}}
|
|
34
|
+
/>
|
|
35
|
+
</Stack.Navigator>
|
|
36
|
+
</NavigationContainer>
|
|
37
|
+
</>
|
|
38
|
+
</QuizContextProvider>
|
|
39
|
+
)
|
|
40
|
+
}
|