Skip to content

Commit 41325f2

Browse files
committed
chore: Add QA settings screen
1 parent ffaf926 commit 41325f2

File tree

10 files changed

+187
-31
lines changed

10 files changed

+187
-31
lines changed

example/src/App.tsx

+11-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { ContentNavigator } from '@screens';
77
import { Storage } from '@services';
88
import { appTheme } from '@utils';
99
import { CustomerIO } from 'customerio-reactnative';
10-
import FlashMessage from 'react-native-flash-message';
10+
import FlashMessage, { showMessage } from 'react-native-flash-message';
1111

1212
export default function App({ moduleName }: { moduleName: string }) {
1313
const [isLoading, setIsLoading] = React.useState(true);
@@ -48,8 +48,16 @@ export default function App({ moduleName }: { moduleName: string }) {
4848
CustomerIO.clearIdentify();
4949
},
5050
onTrackEvent: (eventPayload) => {
51-
console.log('Tracking event', eventPayload);
52-
CustomerIO.track(eventPayload.name, eventPayload.properties);
51+
if (CustomerIO.isInitialized()) {
52+
console.log('Tracking event', eventPayload);
53+
CustomerIO.track(eventPayload.name, eventPayload.properties);
54+
} else {
55+
showMessage({
56+
message: 'CustomerIO not initialized',
57+
description: 'Please set the CustomerIO config',
58+
type: 'danger',
59+
});
60+
}
5361
},
5462
onProfileAttributes(attributes) {
5563
console.log('Setting profile attributes', attributes);

example/src/components/navigation-button.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,19 @@ import {
1010
export const NavigationButton = ({
1111
onPress,
1212
iconSource,
13+
onLongPress,
1314
}: {
1415
onPress: () => void;
16+
onLongPress?: () => void;
1517
iconSource: ImageSourcePropType;
1618
}) => {
1719
return (
18-
<TouchableOpacity onPress={onPress}>
20+
<TouchableOpacity
21+
onPress={onPress}
22+
onLongPress={() => {
23+
onLongPress?.();
24+
}}
25+
>
1926
<Image
2027
source={iconSource}
2128
tintColor={Colors.bodySecondaryText}

example/src/components/settings-nav-button.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const SettingsNavButton = () => {
77
return (
88
<NavigationButton
99
onPress={() => navigation.navigate('Settings')}
10+
onLongPress={() => navigation.navigate('QA Settings')}
1011
iconSource={require('@assets/images/settings.png')}
1112
/>
1213
);

example/src/navigation/props.ts

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const LoginScreenName = 'Login' as const;
99
export const TrackScreenName = 'Track' as const;
1010
export const CustomProfileAttrScreenName = 'Profile Attributes' as const;
1111
export const CustomDeviceAttrScreenName = 'Device Attributes' as const;
12+
export const QASettingsScreenName = 'QA Settings' as const;
1213

1314
export type NavigationStackParamList = {
1415
[SettingsScreenName]: undefined;
@@ -18,6 +19,7 @@ export type NavigationStackParamList = {
1819
[TrackScreenName]: undefined;
1920
[CustomProfileAttrScreenName]: undefined;
2021
[CustomDeviceAttrScreenName]: undefined;
22+
[QASettingsScreenName]: undefined;
2123
};
2224

2325
export type NavigationProps = NavigationProp<NavigationStackParamList>;

example/src/screens/content-navigator.tsx

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
LoginScreenName,
66
NavigationCallbackContext,
77
NavigationStackParamList,
8+
QASettingsScreenName,
89
SettingsScreenName,
910
TrackScreenName,
1011
} from '@navigation';
@@ -17,6 +18,7 @@ import {
1718
CustomProfileAttrScreen,
1819
HomeScreen,
1920
LogingScreen,
21+
QASettingsScreen,
2022
SettingsScreen,
2123
TrackScreen,
2224
} from '@screens';
@@ -53,6 +55,10 @@ export const ContentNavigator = ({ moduleName }: { moduleName: string }) => {
5355
}}
5456
>
5557
<Stack.Screen name={SettingsScreenName} component={SettingsScreen} />
58+
<Stack.Screen
59+
name={QASettingsScreenName}
60+
component={QASettingsScreen}
61+
/>
5662
<Stack.Screen
5763
name={HomeScreenName}
5864
component={HomeScreen}

example/src/screens/home.tsx

+6-7
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@ import { BuildInfoText, Button, Profile } from '@components';
22
import {
33
CustomDeviceAttrScreenName,
44
CustomProfileAttrScreenName,
5+
NavigationCallbackContext,
56
NavigationScreenProps,
67
} from '@navigation';
78
import { Storage } from '@services';
8-
import { CustomerIO } from 'customerio-reactnative';
9-
import React, { useState } from 'react';
9+
import React, { useContext, useState } from 'react';
1010
import { ScrollView, StyleSheet, View } from 'react-native';
11-
import { showMessage } from 'react-native-flash-message';
1211

1312
export const HomeScreen = ({
1413
navigation,
1514
}: NavigationScreenProps<'Customer.io'>) => {
1615
const [user] = useState(Storage.instance.getUser());
16+
const { onTrackEvent } = useContext(NavigationCallbackContext);
1717
return (
1818
<>
1919
<ScrollView>
@@ -25,11 +25,10 @@ export const HomeScreen = ({
2525
<Button
2626
title="Send Random Event"
2727
onPress={() => {
28-
showMessage({
29-
message: 'Random event sent',
30-
type: 'success',
28+
onTrackEvent({
29+
name: 'random_event',
30+
properties: { random: Math.random() },
3131
});
32-
CustomerIO.track('random_event', { random: Math.random() });
3332
}}
3433
/>
3534
<Button

example/src/screens/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ export * from './content-navigator';
22
export * from './custom-attribute';
33
export * from './home';
44
export * from './login';
5+
export * from './qa-settings';
56
export * from './settings';
67
export * from './track';

example/src/screens/qa-settings.tsx

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import {
2+
Button,
3+
ButtonExperience,
4+
LargeBoldText,
5+
TextField,
6+
} from '@components';
7+
import { QASettings, Storage } from '@services';
8+
import React, { useState } from 'react';
9+
import { ScrollView, StyleSheet, View } from 'react-native';
10+
import { showMessage } from 'react-native-flash-message';
11+
12+
const validateSettings = (config: QASettings) => {
13+
if (!config.apiHost || !config.cdnHost) {
14+
throw Error('CDN and API host values are missing');
15+
} else if (!config.cdnHost) {
16+
throw Error('CDB Host value is missing');
17+
} else if (!config.apiHost) {
18+
throw Error('API Host value is missing');
19+
}
20+
};
21+
22+
export const QASettingsScreen = () => {
23+
const [config, setConfig] = useState<QASettings>(
24+
Storage.instance.getQaConfig()
25+
);
26+
27+
return (
28+
<ScrollView>
29+
<View style={styles.container}>
30+
<LargeBoldText>CustomerIO QA Settings</LargeBoldText>
31+
<TextField
32+
onChangeText={(cdnHost) => {
33+
setConfig({ ...config, cdnHost });
34+
}}
35+
label="CDN Host"
36+
placeholder="Enter the CDN Host"
37+
defaultValue={config.cdnHost ?? ''}
38+
/>
39+
40+
<TextField
41+
onChangeText={(apiHost) => {
42+
setConfig({ ...config, apiHost });
43+
}}
44+
label="API Host"
45+
placeholder="Enter the API host"
46+
defaultValue={config.apiHost ?? ''}
47+
/>
48+
49+
<Button
50+
title="Save"
51+
experience={ButtonExperience.callToAction}
52+
disabled={!config.apiHost || !config.cdnHost}
53+
onPress={async () => {
54+
try {
55+
validateSettings(config);
56+
Storage.instance.setQaConfig(config);
57+
58+
showMessage({
59+
message: 'QA settings saved successfully',
60+
type: 'success',
61+
});
62+
} catch (error: unknown) {
63+
showMessage({
64+
message: (error as Error).message,
65+
type: 'warning',
66+
});
67+
return;
68+
}
69+
}}
70+
/>
71+
</View>
72+
</ScrollView>
73+
);
74+
};
75+
76+
const styles = StyleSheet.create({
77+
container: {
78+
flex: 1,
79+
padding: 16,
80+
flexDirection: 'column',
81+
gap: 16,
82+
},
83+
});

example/src/screens/settings.tsx

+3-10
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,9 @@ import React, { useState } from 'react';
1818
import { ScrollView, StyleSheet, View } from 'react-native';
1919
import { showMessage } from 'react-native-flash-message';
2020

21-
const defaultCioConfig: Partial<CioConfig> = {
22-
region: CioRegion.US,
23-
logLevel: CioLogLevel.Error,
24-
trackApplicationLifecycleEvents: true,
25-
};
26-
2721
export const SettingsScreen = () => {
2822
const [config, setConfig] = useState<Partial<CioConfig>>(
29-
Storage.instance.getCioConfig() ?? defaultCioConfig
23+
Storage.instance.getCioConfig() ?? Storage.instance.getDefaultCioConfig()
3024
);
3125

3226
return (
@@ -119,7 +113,7 @@ export const SettingsScreen = () => {
119113
CustomerIO.initialize(config as CioConfig);
120114
showMessage({
121115
message:
122-
'CustomerIO settings saved successfully CustomerIO.initialize() has been called with the new settings',
116+
'CustomerIO settings saved successfully and CustomerIO.initialize() has been called with the new settings',
123117
type: 'success',
124118
});
125119
}
@@ -130,8 +124,7 @@ export const SettingsScreen = () => {
130124
title="Reset to Default"
131125
experience={ButtonExperience.secondary}
132126
onPress={() => {
133-
setConfig(defaultCioConfig);
134-
Storage.instance.setCioConfig(defaultCioConfig as CioConfig);
127+
Storage.instance.resetCioConfig();
135128
showMessage({
136129
message: 'CustomerIO settings has been reset!',
137130
type: 'success',

example/src/services/storage.ts

+66-10
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,37 @@
11
import AsyncStorage from '@react-native-async-storage/async-storage';
22
import { User } from '@utils';
3-
import { CioConfig } from 'customerio-reactnative';
3+
import { CioConfig, CioLogLevel, CioRegion } from 'customerio-reactnative';
44

55
const USER_STORAGE_KEY = 'user';
66
const CIO_CONFIG_STORAGE_KEY = 'cioConfig';
77

8+
export type QASettings = {
9+
cdnHost: string;
10+
apiHost: string;
11+
};
12+
13+
type Config = Partial<CioConfig> & { qa: QASettings };
14+
15+
const defaultConfig: Config = {
16+
region: CioRegion.US,
17+
logLevel: CioLogLevel.Error,
18+
trackApplicationLifecycleEvents: true,
19+
qa: {
20+
cdnHost: 'https://cdp.customer.io/v1',
21+
apiHost: 'https://cdp.customer.io/v1',
22+
},
23+
};
24+
825
export class Storage {
926
private user: User | null = null;
10-
private cioConfig: CioConfig | null = null;
27+
private config: Config | null = null;
1128

1229
static readonly instance: Storage = new Storage();
1330

1431
private constructor() {}
1532

1633
readonly loadAll = async () => {
17-
if (!this.user || !this.cioConfig) {
34+
if (!this.user || !this.config) {
1835
await this.loadFromStorage();
1936
}
2037
};
@@ -24,8 +41,9 @@ export class Storage {
2441
const cioConfigJsonPayload = await AsyncStorage.getItem(
2542
CIO_CONFIG_STORAGE_KEY
2643
);
44+
2745
this.user = userJsonPayload ? JSON.parse(userJsonPayload) : null;
28-
this.cioConfig = cioConfigJsonPayload
46+
this.config = cioConfigJsonPayload
2947
? JSON.parse(cioConfigJsonPayload)
3048
: null;
3149
};
@@ -40,17 +58,55 @@ export class Storage {
4058
};
4159

4260
readonly removeUser = async () => {
43-
await AsyncStorage.removeItem(USER_STORAGE_KEY);
4461
this.user = null;
62+
await AsyncStorage.removeItem(USER_STORAGE_KEY);
63+
};
64+
65+
readonly setCioConfig = async (cioConfig: CioConfig) => {
66+
const config = cioConfig as Config;
67+
this.config = { ...config, qa: config.qa ?? defaultConfig.qa };
68+
await AsyncStorage.setItem(
69+
CIO_CONFIG_STORAGE_KEY,
70+
JSON.stringify(this.config)
71+
);
72+
};
73+
74+
readonly getCioConfig = (): CioConfig => {
75+
return this.config as CioConfig;
4576
};
4677

47-
readonly setCioConfig = async (config: CioConfig) => {
48-
await AsyncStorage.setItem(CIO_CONFIG_STORAGE_KEY, JSON.stringify(config));
49-
this.cioConfig = config;
78+
readonly getDefaultCioConfig = (): Partial<CioConfig> => {
79+
return defaultConfig;
5080
};
5181

52-
readonly getCioConfig = () => {
53-
return this.cioConfig;
82+
readonly resetCioConfig = async () => {
83+
if (this.config === null) {
84+
this.config = defaultConfig;
85+
} else {
86+
this.config = { ...this.config, ...defaultConfig, qa: this.config.qa };
87+
}
88+
89+
await AsyncStorage.setItem(
90+
CIO_CONFIG_STORAGE_KEY,
91+
JSON.stringify(this.config)
92+
);
93+
};
94+
95+
readonly getQaConfig = (): QASettings => {
96+
return this.config?.qa ?? defaultConfig.qa;
97+
};
98+
99+
readonly setQaConfig = async (qa: QASettings) => {
100+
if (this.config === null) {
101+
this.config = { ...defaultConfig, qa };
102+
} else {
103+
this.config = { ...this.config, qa };
104+
}
105+
106+
await AsyncStorage.setItem(
107+
CIO_CONFIG_STORAGE_KEY,
108+
JSON.stringify(this.config)
109+
);
54110
};
55111

56112
readonly clear = async () => {

0 commit comments

Comments
 (0)