@app-brew/appbrew 1.0.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.
- package/analytics/app-users.ts +107 -0
- package/analytics/config.dev.ts +10 -0
- package/analytics/config.ts +10 -0
- package/analytics/tracker.ts +136 -0
- package/analytics/trackerV2.ts +154 -0
- package/analytics/utils.ts +9 -0
- package/analytics/webhook-tracker.ts +203 -0
- package/currency/index.ts +1 -0
- package/currency/provider.ts +211 -0
- package/estimated-delivery-date/blocks/estimated-delivery-date.tsx +229 -0
- package/estimated-delivery-date/index.ts +2 -0
- package/estimated-delivery-date/provider.ts +45 -0
- package/native-wishlist/src/index.ts +1 -0
- package/native-wishlist/src/provider.ts +88 -0
- package/package.json +23 -0
- package/src/index.ts +7 -0
- package/src/lib/integrations-appbrew.spec.ts +7 -0
- package/src/lib/integrations-appbrew.ts +3 -0
- package/tsconfig.paths.json +10 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { removeUndefined } from '@gauntlet/analytics'
|
|
2
|
+
import { LocalStorage } from '@gauntlet/local-storage'
|
|
3
|
+
import { useAppStore } from '@gauntlet/state'
|
|
4
|
+
import { AppConfig } from '@gauntlet/types'
|
|
5
|
+
import messaging from '@react-native-firebase/messaging'
|
|
6
|
+
import EnvConfig from 'react-native-config'
|
|
7
|
+
|
|
8
|
+
interface AppUserInterface {
|
|
9
|
+
email?: string
|
|
10
|
+
phoneNumber?: string
|
|
11
|
+
firstName?: string
|
|
12
|
+
lastName?: string
|
|
13
|
+
pushToken: string
|
|
14
|
+
deviceInstanceId: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class AppUser {
|
|
18
|
+
static USER_KEY = 'app-user'
|
|
19
|
+
static BASE_URL = `${EnvConfig.APP_SERVICE_URL}/users`
|
|
20
|
+
static USER_EXPIRY_IN_MILLISECONDS = 1000 * 60 * 60 * 24 * 15
|
|
21
|
+
static USER_PROPERTIES: (keyof AppUserInterface)[] = [
|
|
22
|
+
'email',
|
|
23
|
+
'phoneNumber',
|
|
24
|
+
'firstName',
|
|
25
|
+
'lastName',
|
|
26
|
+
'pushToken',
|
|
27
|
+
'deviceInstanceId',
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
static async init(config?: AppConfig) {
|
|
31
|
+
try {
|
|
32
|
+
this.USER_EXPIRY_IN_MILLISECONDS =
|
|
33
|
+
config?.settings?.global?.appUserExpiryInMS ||
|
|
34
|
+
this.USER_EXPIRY_IN_MILLISECONDS
|
|
35
|
+
const userLoggedIn = useAppStore.getState().user.isUserAuthenticated()
|
|
36
|
+
if (!userLoggedIn) {
|
|
37
|
+
await this.updateUser()
|
|
38
|
+
}
|
|
39
|
+
} catch (e) {
|
|
40
|
+
console.log(e)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static async updateUser(user?: any) {
|
|
45
|
+
const getInstanceId = useAppStore.getState().analytics.getInstanceId
|
|
46
|
+
const token = await messaging().getToken()
|
|
47
|
+
if (token) {
|
|
48
|
+
const updatedUser = {
|
|
49
|
+
pushToken: token,
|
|
50
|
+
deviceInstanceId: getInstanceId(),
|
|
51
|
+
email: user?.email,
|
|
52
|
+
phoneNumber: user?.phoneNumber,
|
|
53
|
+
firstName: user?.firstName,
|
|
54
|
+
lastName: user?.lastName,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
removeUndefined(updatedUser)
|
|
58
|
+
|
|
59
|
+
const userUpdated = this.checkIfUserUpdated(updatedUser)
|
|
60
|
+
if (userUpdated) {
|
|
61
|
+
const response = await fetch(`${this.BASE_URL}`, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: {
|
|
64
|
+
'Content-Type': 'application/json',
|
|
65
|
+
},
|
|
66
|
+
body: JSON.stringify({ ...updatedUser, appId: EnvConfig.APP_ID }),
|
|
67
|
+
})
|
|
68
|
+
const isUserUpdated = !(await response.json()).error
|
|
69
|
+
if (isUserUpdated) {
|
|
70
|
+
this.updateUserInLocalStorage(updatedUser)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private static checkIfUserUpdated(updatedUser: AppUserInterface) {
|
|
77
|
+
const localStorage = LocalStorage.getInstance()
|
|
78
|
+
const currentUser:
|
|
79
|
+
| {
|
|
80
|
+
data: AppUserInterface
|
|
81
|
+
lastSyncedAt: number
|
|
82
|
+
}
|
|
83
|
+
| undefined = localStorage.getJson(this.USER_KEY)
|
|
84
|
+
if (!currentUser || !currentUser.data || !currentUser.lastSyncedAt) {
|
|
85
|
+
return true
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const expired =
|
|
89
|
+
(currentUser.lastSyncedAt as number) <
|
|
90
|
+
Date.now() - this.USER_EXPIRY_IN_MILLISECONDS
|
|
91
|
+
|
|
92
|
+
const anyChangeInUserProperty = !this.USER_PROPERTIES.every((key) => {
|
|
93
|
+
return updatedUser[key] === currentUser.data[key]
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const userUpdated = expired || anyChangeInUserProperty
|
|
97
|
+
return userUpdated
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private static updateUserInLocalStorage(updatedUser: AppUserInterface) {
|
|
101
|
+
const localStorage = LocalStorage.getInstance()
|
|
102
|
+
localStorage.set(this.USER_KEY, {
|
|
103
|
+
data: updatedUser,
|
|
104
|
+
lastSyncedAt: Date.now(),
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const trackerConfig = {
|
|
2
|
+
android: {
|
|
3
|
+
firebaseAppId: '1:444706537999:android:11b0d89a996d60c927197a',
|
|
4
|
+
apiKey: 'SYv0XlMnTBONYAo0KSAiAg',
|
|
5
|
+
},
|
|
6
|
+
ios: {
|
|
7
|
+
firebaseAppId: '1:444706537999:ios:6c7563bcd6e3276227197a',
|
|
8
|
+
apiKey: '3ip0WZ0xSl-gUSqwVEYaNQ',
|
|
9
|
+
},
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const trackerConfig = {
|
|
2
|
+
android: {
|
|
3
|
+
firebaseAppId: '1:234880715042:android:56fd9849f0883448913d7b',
|
|
4
|
+
apiKey: 'c4EGflbiQIeD2wopPukKsg',
|
|
5
|
+
},
|
|
6
|
+
ios: {
|
|
7
|
+
firebaseAppId: '1:234880715042:ios:e602756a2a4e6c0f913d7b',
|
|
8
|
+
apiKey: '2rzELbueTgORxUF_utePnQ',
|
|
9
|
+
},
|
|
10
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { AnalyticsEvent, AnalyticsPayload, AppConfig } from '@gauntlet/types'
|
|
2
|
+
import analytics from '@react-native-firebase/analytics'
|
|
3
|
+
import { Platform } from 'react-native'
|
|
4
|
+
import base64 from 'react-native-base64'
|
|
5
|
+
// To push data to dev BigQuery, update the import to: import { trackerConfig } from './config.dev'
|
|
6
|
+
import { trackerConfig } from './config'
|
|
7
|
+
import { useAppStore } from '@gauntlet/state'
|
|
8
|
+
import { execute } from './utils'
|
|
9
|
+
import { AnalyticsTracker, trimParameters } from '@gauntlet/analytics'
|
|
10
|
+
import { AppUser } from './app-users'
|
|
11
|
+
|
|
12
|
+
export class AppbrewTracker extends AnalyticsTracker {
|
|
13
|
+
ITEMS_PARAMETER_CHARACTERS_LIMIT = 100
|
|
14
|
+
BASE_URL = 'https://www.google-analytics.com/mp/collect'
|
|
15
|
+
appInstanceId: string | null = null
|
|
16
|
+
instanceId: string | null = null
|
|
17
|
+
userId: string | undefined = undefined
|
|
18
|
+
deviceInfo: any = {}
|
|
19
|
+
appInfo: any = {}
|
|
20
|
+
geoInfo: any = {}
|
|
21
|
+
installSourceUtmParams: any = {}
|
|
22
|
+
userProperties: any = {}
|
|
23
|
+
config =
|
|
24
|
+
Platform.OS === 'android' ? trackerConfig['android'] : trackerConfig['ios']
|
|
25
|
+
|
|
26
|
+
async initTracker(config?: AppConfig) {
|
|
27
|
+
try {
|
|
28
|
+
await AppUser.init(config)
|
|
29
|
+
const getUserId = useAppStore.getState().analytics.getUserId
|
|
30
|
+
const getInstanceId = useAppStore.getState().analytics.getInstanceId
|
|
31
|
+
const getDeviceInfo = useAppStore.getState().analytics.getDeviceInfo
|
|
32
|
+
const getGeoInfo = useAppStore.getState().analytics.getGeoInfo
|
|
33
|
+
const getInstallSourceUtmParams =
|
|
34
|
+
useAppStore.getState().analytics.getInstallSourceUtmParams
|
|
35
|
+
const getAppInfo = useAppStore.getState().analytics.getAppInfo
|
|
36
|
+
;[
|
|
37
|
+
this.userId,
|
|
38
|
+
this.appInstanceId,
|
|
39
|
+
this.instanceId,
|
|
40
|
+
this.deviceInfo,
|
|
41
|
+
this.geoInfo,
|
|
42
|
+
this.installSourceUtmParams,
|
|
43
|
+
this.appInfo,
|
|
44
|
+
] = await Promise.all([
|
|
45
|
+
execute(getUserId),
|
|
46
|
+
analytics().getAppInstanceId(),
|
|
47
|
+
execute(getInstanceId),
|
|
48
|
+
execute(getDeviceInfo),
|
|
49
|
+
execute(getGeoInfo),
|
|
50
|
+
execute(getInstallSourceUtmParams),
|
|
51
|
+
execute(getAppInfo),
|
|
52
|
+
])
|
|
53
|
+
const { app_name, app_id, ...appInfo } = this.appInfo
|
|
54
|
+
|
|
55
|
+
const originalUserProperties = {
|
|
56
|
+
...this.geoInfo,
|
|
57
|
+
...this.deviceInfo,
|
|
58
|
+
...appInfo,
|
|
59
|
+
...this.installSourceUtmParams,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Object.keys(originalUserProperties).forEach((key) => {
|
|
63
|
+
this.userProperties[key] = {
|
|
64
|
+
value: originalUserProperties[key],
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.log('Error occurred while getting instance id')
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async sendEvent(e: AnalyticsEvent, p: AnalyticsPayload) {
|
|
73
|
+
this.sendMeasurementEvent(e, p)
|
|
74
|
+
}
|
|
75
|
+
async sendScreenView(screenName: string) {
|
|
76
|
+
this.sendMeasurementEvent(AnalyticsEvent.PAGE_VIEW, { screenName })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async setUserDetails(user: any) {
|
|
80
|
+
this.userId = user?.email && base64.encode(user?.email)
|
|
81
|
+
await AppUser.updateUser(user)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private async sendMeasurementEvent(
|
|
85
|
+
event: AnalyticsEvent,
|
|
86
|
+
payload: AnalyticsPayload
|
|
87
|
+
) {
|
|
88
|
+
const formattedPayload = trimParameters(
|
|
89
|
+
payload,
|
|
90
|
+
this.ITEMS_PARAMETER_CHARACTERS_LIMIT
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
const getSessionId = useAppStore.getState().analytics.getSessionId
|
|
94
|
+
const getTriggeredFromPush =
|
|
95
|
+
useAppStore.getState().analytics.getTriggeredFromPush
|
|
96
|
+
const sessionId = getSessionId()
|
|
97
|
+
const triggeredByPush = getTriggeredFromPush()
|
|
98
|
+
if (this.appInstanceId && this.instanceId && sessionId) {
|
|
99
|
+
try {
|
|
100
|
+
const { app_name, app_id } = this.appInfo
|
|
101
|
+
const eventSourceUtmParams = await useAppStore
|
|
102
|
+
.getState()
|
|
103
|
+
.analytics.getEventSourceUtmParams()
|
|
104
|
+
await fetch(
|
|
105
|
+
`${this.BASE_URL}?api_secret=${this.config.apiKey}&firebase_app_id=${this.config.firebaseAppId}`,
|
|
106
|
+
{
|
|
107
|
+
method: 'POST',
|
|
108
|
+
body: JSON.stringify({
|
|
109
|
+
app_instance_id: this.appInstanceId,
|
|
110
|
+
user_id: this.userId || this.instanceId,
|
|
111
|
+
non_personalized_ads: true,
|
|
112
|
+
user_properties: this.userProperties,
|
|
113
|
+
events: [
|
|
114
|
+
{
|
|
115
|
+
name: event,
|
|
116
|
+
params: {
|
|
117
|
+
instance_id: this.instanceId,
|
|
118
|
+
session_id: sessionId,
|
|
119
|
+
appbrew_session_id: sessionId,
|
|
120
|
+
triggered_by_push: triggeredByPush,
|
|
121
|
+
app_id,
|
|
122
|
+
app_name,
|
|
123
|
+
...eventSourceUtmParams,
|
|
124
|
+
...formattedPayload,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
}),
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
} catch (err) {
|
|
132
|
+
// ignore
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AnalyticsEvent,
|
|
3
|
+
AnalyticsEventParams,
|
|
4
|
+
AnalyticsPayload,
|
|
5
|
+
AppConfig,
|
|
6
|
+
} from '@gauntlet/types'
|
|
7
|
+
import analytics from '@react-native-firebase/analytics'
|
|
8
|
+
import { Platform } from 'react-native'
|
|
9
|
+
import base64 from 'react-native-base64'
|
|
10
|
+
// To push data to dev BigQuery, update the import to: import { trackerConfig } from './config.dev'
|
|
11
|
+
import { trackerConfig } from './config'
|
|
12
|
+
import { useAppStore } from '@gauntlet/state'
|
|
13
|
+
import { execute } from './utils'
|
|
14
|
+
import { AnalyticsTrackerV2, trimParameters } from '@gauntlet/analytics'
|
|
15
|
+
import { AppUser } from './app-users'
|
|
16
|
+
|
|
17
|
+
const defaultEventsWhitelist = Object.values(AnalyticsEvent)
|
|
18
|
+
const defaultParamsWhitelist = Object.values(AnalyticsEventParams)
|
|
19
|
+
|
|
20
|
+
export class AppbrewTrackerV2 extends AnalyticsTrackerV2 {
|
|
21
|
+
SESSION_EXPIRY_IN_SECONDS = 30 * 60
|
|
22
|
+
SESSION_KEY = 'appbrewSession'
|
|
23
|
+
ITEMS_PARAMETER_CHARACTERS_LIMIT = 100
|
|
24
|
+
BASE_URL = 'https://www.google-analytics.com/mp/collect'
|
|
25
|
+
appInstanceId: string | null = null
|
|
26
|
+
instanceId: string | null = null
|
|
27
|
+
userId: string | undefined = undefined
|
|
28
|
+
deviceInfo: any = {}
|
|
29
|
+
appInfo: any = {}
|
|
30
|
+
geoInfo: any = {}
|
|
31
|
+
installSourceUtmParams: any = {}
|
|
32
|
+
userProperties: any = {}
|
|
33
|
+
config =
|
|
34
|
+
Platform.OS === 'android' ? trackerConfig['android'] : trackerConfig['ios']
|
|
35
|
+
|
|
36
|
+
async initTracker(config?: AppConfig) {
|
|
37
|
+
try {
|
|
38
|
+
await AppUser.init(config)
|
|
39
|
+
|
|
40
|
+
this.eventsMapper = {}
|
|
41
|
+
this.paramsMapper = {}
|
|
42
|
+
this.eventsWhitelist = defaultEventsWhitelist
|
|
43
|
+
this.paramsWhitelist = defaultParamsWhitelist
|
|
44
|
+
|
|
45
|
+
const getUserId = useAppStore.getState().analytics.getUserId
|
|
46
|
+
const getInstanceId = useAppStore.getState().analytics.getInstanceId
|
|
47
|
+
const getDeviceInfo = useAppStore.getState().analytics.getDeviceInfo
|
|
48
|
+
const getGeoInfo = useAppStore.getState().analytics.getGeoInfo
|
|
49
|
+
const getInstallSourceUtmParams =
|
|
50
|
+
useAppStore.getState().analytics.getInstallSourceUtmParams
|
|
51
|
+
const getAppInfo = useAppStore.getState().analytics.getAppInfo
|
|
52
|
+
;[
|
|
53
|
+
this.userId,
|
|
54
|
+
this.appInstanceId,
|
|
55
|
+
this.instanceId,
|
|
56
|
+
this.deviceInfo,
|
|
57
|
+
this.geoInfo,
|
|
58
|
+
this.installSourceUtmParams,
|
|
59
|
+
this.appInfo,
|
|
60
|
+
] = await Promise.all([
|
|
61
|
+
execute(getUserId),
|
|
62
|
+
analytics().getAppInstanceId(),
|
|
63
|
+
execute(getInstanceId),
|
|
64
|
+
execute(getDeviceInfo),
|
|
65
|
+
execute(getGeoInfo),
|
|
66
|
+
execute(getInstallSourceUtmParams),
|
|
67
|
+
execute(getAppInfo),
|
|
68
|
+
])
|
|
69
|
+
const { app_name, app_id, ...appInfo } = this.appInfo
|
|
70
|
+
|
|
71
|
+
const originalUserProperties = {
|
|
72
|
+
...this.geoInfo,
|
|
73
|
+
...this.deviceInfo,
|
|
74
|
+
...appInfo,
|
|
75
|
+
...this.installSourceUtmParams,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
Object.keys(originalUserProperties).forEach((key) => {
|
|
79
|
+
this.userProperties[key] = {
|
|
80
|
+
value: originalUserProperties[key],
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
} catch (err) {
|
|
84
|
+
console.log('Error occurred while getting instance id')
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async sendEvent(e: AnalyticsEvent, p: AnalyticsPayload) {
|
|
89
|
+
this.sendMeasurementEvent(e, p)
|
|
90
|
+
}
|
|
91
|
+
async sendScreenView(screenName: string) {
|
|
92
|
+
this.sendMeasurementEvent(AnalyticsEvent.PAGE_VIEW, {
|
|
93
|
+
[AnalyticsEventParams.SCREEN_NAME]: screenName,
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async setUserDetails(user: any) {
|
|
98
|
+
this.userId = user?.email && base64.encode(user?.email)
|
|
99
|
+
await AppUser.updateUser(user)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private async sendMeasurementEvent(
|
|
103
|
+
event: AnalyticsEvent,
|
|
104
|
+
payload: AnalyticsPayload
|
|
105
|
+
) {
|
|
106
|
+
const formattedPayload = trimParameters(
|
|
107
|
+
payload,
|
|
108
|
+
this.ITEMS_PARAMETER_CHARACTERS_LIMIT
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
const getSessionId = useAppStore.getState().analytics.getSessionId
|
|
112
|
+
const getTriggeredFromPush =
|
|
113
|
+
useAppStore.getState().analytics.getTriggeredFromPush
|
|
114
|
+
const sessionId = getSessionId()
|
|
115
|
+
const triggeredByPush = getTriggeredFromPush()
|
|
116
|
+
if (this.appInstanceId && this.instanceId && sessionId) {
|
|
117
|
+
try {
|
|
118
|
+
const { app_name, app_id } = this.appInfo
|
|
119
|
+
const eventSourceUtmParams = await useAppStore
|
|
120
|
+
.getState()
|
|
121
|
+
.analytics.getEventSourceUtmParams()
|
|
122
|
+
await fetch(
|
|
123
|
+
`${this.BASE_URL}?api_secret=${this.config.apiKey}&firebase_app_id=${this.config.firebaseAppId}`,
|
|
124
|
+
{
|
|
125
|
+
method: 'POST',
|
|
126
|
+
body: JSON.stringify({
|
|
127
|
+
app_instance_id: this.appInstanceId,
|
|
128
|
+
user_id: this.userId || this.instanceId,
|
|
129
|
+
non_personalized_ads: true,
|
|
130
|
+
user_properties: this.userProperties,
|
|
131
|
+
events: [
|
|
132
|
+
{
|
|
133
|
+
name: event,
|
|
134
|
+
params: {
|
|
135
|
+
instance_id: this.instanceId,
|
|
136
|
+
session_id: sessionId,
|
|
137
|
+
appbrew_session_id: sessionId,
|
|
138
|
+
triggered_by_push: triggeredByPush,
|
|
139
|
+
app_id,
|
|
140
|
+
app_name,
|
|
141
|
+
...eventSourceUtmParams,
|
|
142
|
+
...formattedPayload,
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
}),
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
} catch (err) {
|
|
150
|
+
// ignore
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AnalyticsEvent,
|
|
3
|
+
AnalyticsEventParams,
|
|
4
|
+
AnalyticsPayload,
|
|
5
|
+
AppConfig,
|
|
6
|
+
} from '@gauntlet/types'
|
|
7
|
+
import { AnalyticsTrackerV2 } from '@gauntlet/analytics'
|
|
8
|
+
import { useAppStore } from '@gauntlet/state'
|
|
9
|
+
|
|
10
|
+
export interface WebhookTrackerConfig {
|
|
11
|
+
apiUrl?: string
|
|
12
|
+
eventUrls?: Record<string, string>
|
|
13
|
+
eventsWhitelist?: AnalyticsEvent[]
|
|
14
|
+
paramsWhitelist?: AnalyticsEventParams[]
|
|
15
|
+
headers?: Record<string, string>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class WebhookTracker extends AnalyticsTrackerV2 {
|
|
19
|
+
private trackerId: string
|
|
20
|
+
private defaultApiUrl = ''
|
|
21
|
+
private eventUrls: Record<string, string> = {}
|
|
22
|
+
private customHeaders: Record<string, string> = {}
|
|
23
|
+
private userDetails: {
|
|
24
|
+
email?: string
|
|
25
|
+
phone?: string
|
|
26
|
+
firstName?: string
|
|
27
|
+
lastName?: string
|
|
28
|
+
customerId?: string
|
|
29
|
+
} = {}
|
|
30
|
+
private instanceId: string | null = null
|
|
31
|
+
private sessionId: string | null = null
|
|
32
|
+
private deviceInfo: any = {}
|
|
33
|
+
private appInfo: any = {}
|
|
34
|
+
|
|
35
|
+
constructor(trackerId: string) {
|
|
36
|
+
super()
|
|
37
|
+
this.trackerId = trackerId
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override async initTracker(config?: AppConfig) {
|
|
41
|
+
// Look for config by tracker ID: config.integrations.webhookTrackers[trackerId]
|
|
42
|
+
const trackerConfig =
|
|
43
|
+
config?.integrations?.[this.trackerId] ??
|
|
44
|
+
(config?.integrations?.webhookTrackers?.[this.trackerId] as
|
|
45
|
+
| WebhookTrackerConfig
|
|
46
|
+
| undefined)
|
|
47
|
+
|
|
48
|
+
if (!trackerConfig) {
|
|
49
|
+
console.warn(`WebhookTracker [${this.trackerId}]: No config found`)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!trackerConfig.apiUrl && !trackerConfig.eventUrls) {
|
|
54
|
+
console.warn(
|
|
55
|
+
`WebhookTracker [${this.trackerId}]: No API URL or event URLs configured`
|
|
56
|
+
)
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.defaultApiUrl = trackerConfig.apiUrl || ''
|
|
61
|
+
this.eventUrls = trackerConfig.eventUrls || {}
|
|
62
|
+
this.customHeaders = trackerConfig.headers || {}
|
|
63
|
+
|
|
64
|
+
// Set whitelists from config
|
|
65
|
+
this.eventsWhitelist = trackerConfig.eventsWhitelist || []
|
|
66
|
+
this.paramsWhitelist = trackerConfig.paramsWhitelist || []
|
|
67
|
+
|
|
68
|
+
// Get instance and device info
|
|
69
|
+
const getInstanceId = useAppStore.getState().analytics.getInstanceId
|
|
70
|
+
const getDeviceInfo = useAppStore.getState().analytics.getDeviceInfo
|
|
71
|
+
const getAppInfo = useAppStore.getState().analytics.getAppInfo
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const [instanceId, deviceInfo, appInfo] = await Promise.all([
|
|
75
|
+
getInstanceId(),
|
|
76
|
+
getDeviceInfo(),
|
|
77
|
+
getAppInfo(),
|
|
78
|
+
])
|
|
79
|
+
|
|
80
|
+
this.instanceId = instanceId
|
|
81
|
+
this.deviceInfo = deviceInfo
|
|
82
|
+
this.appInfo = appInfo
|
|
83
|
+
} catch (err) {
|
|
84
|
+
console.error(
|
|
85
|
+
`WebhookTracker [${this.trackerId}]: Error initializing tracker`,
|
|
86
|
+
err
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
override async setUserDetails(user: any) {
|
|
92
|
+
if (user) {
|
|
93
|
+
this.userDetails = {
|
|
94
|
+
email: user.email,
|
|
95
|
+
phone: user.phone || user.phoneNumber,
|
|
96
|
+
firstName: user.firstName,
|
|
97
|
+
lastName: user.lastName,
|
|
98
|
+
customerId: user.id?.replace('gid://shopify/Customer/', ''),
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
override async sendEvent(event: AnalyticsEvent, payload: AnalyticsPayload) {
|
|
104
|
+
const url = this.getUrlForEvent(event)
|
|
105
|
+
|
|
106
|
+
if (!url || !event) return
|
|
107
|
+
|
|
108
|
+
// Check whitelist - if configured, only send whitelisted events
|
|
109
|
+
if (
|
|
110
|
+
this.eventsWhitelist.length > 0 &&
|
|
111
|
+
!this.eventsWhitelist.includes(event)
|
|
112
|
+
) {
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const getSessionId = useAppStore.getState().analytics.getSessionId
|
|
118
|
+
this.sessionId = getSessionId()
|
|
119
|
+
|
|
120
|
+
const eventPayload = {
|
|
121
|
+
trackerId: this.trackerId,
|
|
122
|
+
event,
|
|
123
|
+
timestamp: new Date().toISOString(),
|
|
124
|
+
user: this.userDetails,
|
|
125
|
+
session: {
|
|
126
|
+
sessionId: this.sessionId,
|
|
127
|
+
instanceId: this.instanceId,
|
|
128
|
+
},
|
|
129
|
+
device: this.deviceInfo,
|
|
130
|
+
app: this.appInfo,
|
|
131
|
+
payload,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
await fetch(url, {
|
|
135
|
+
method: 'POST',
|
|
136
|
+
headers: {
|
|
137
|
+
'Content-Type': 'application/json',
|
|
138
|
+
...this.customHeaders,
|
|
139
|
+
},
|
|
140
|
+
body: JSON.stringify(eventPayload),
|
|
141
|
+
})
|
|
142
|
+
} catch (err) {
|
|
143
|
+
console.error(
|
|
144
|
+
`WebhookTracker [${this.trackerId}]: Error sending event`,
|
|
145
|
+
err
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
override async sendScreenView(screenName: string) {
|
|
151
|
+
const url = this.getUrlForEvent(AnalyticsEvent.SCREEN_VIEW)
|
|
152
|
+
|
|
153
|
+
if (!url) return
|
|
154
|
+
|
|
155
|
+
// Check whitelist - if configured, only send if screen_view is whitelisted
|
|
156
|
+
if (
|
|
157
|
+
this.eventsWhitelist.length > 0 &&
|
|
158
|
+
!this.eventsWhitelist.includes(AnalyticsEvent.SCREEN_VIEW)
|
|
159
|
+
) {
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
const getSessionId = useAppStore.getState().analytics.getSessionId
|
|
165
|
+
this.sessionId = getSessionId()
|
|
166
|
+
|
|
167
|
+
const screenViewPayload = {
|
|
168
|
+
trackerId: this.trackerId,
|
|
169
|
+
event: AnalyticsEvent.SCREEN_VIEW,
|
|
170
|
+
timestamp: new Date().toISOString(),
|
|
171
|
+
user: this.userDetails,
|
|
172
|
+
session: {
|
|
173
|
+
sessionId: this.sessionId,
|
|
174
|
+
instanceId: this.instanceId,
|
|
175
|
+
},
|
|
176
|
+
device: this.deviceInfo,
|
|
177
|
+
app: this.appInfo,
|
|
178
|
+
payload: {
|
|
179
|
+
screen_name: screenName,
|
|
180
|
+
},
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
await fetch(url, {
|
|
184
|
+
method: 'POST',
|
|
185
|
+
headers: {
|
|
186
|
+
'Content-Type': 'application/json',
|
|
187
|
+
...this.customHeaders,
|
|
188
|
+
},
|
|
189
|
+
body: JSON.stringify(screenViewPayload),
|
|
190
|
+
})
|
|
191
|
+
} catch (err) {
|
|
192
|
+
console.error(
|
|
193
|
+
`WebhookTracker [${this.trackerId}]: Error sending screen view`,
|
|
194
|
+
err
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private getUrlForEvent(event: string): string | null {
|
|
200
|
+
// Check for event-specific URL first, then fall back to default
|
|
201
|
+
return this.eventUrls[event] || this.defaultApiUrl || null
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './provider'
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AppConfig,
|
|
3
|
+
AsyncData,
|
|
4
|
+
ConversionRates,
|
|
5
|
+
Country,
|
|
6
|
+
CountryCode,
|
|
7
|
+
CountryCodeToCountryMap,
|
|
8
|
+
CurrencyCode,
|
|
9
|
+
CurrencyCodeToSymbolMap,
|
|
10
|
+
IAvailableCountryResponse,
|
|
11
|
+
ICurrencyProvider,
|
|
12
|
+
} from '@gauntlet/types'
|
|
13
|
+
import EnvConfig from 'react-native-config'
|
|
14
|
+
|
|
15
|
+
type Settings = {
|
|
16
|
+
defaultCountry: string
|
|
17
|
+
supportedCountries: Array<string>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const BASE_URL = `${EnvConfig['APP_SERVICE_URL']}/integrations/appbrew-currency/conversion-rates`
|
|
21
|
+
export class AppbrewCurrencyProvider implements ICurrencyProvider {
|
|
22
|
+
static instance: AppbrewCurrencyProvider
|
|
23
|
+
static country: Country
|
|
24
|
+
private response: AsyncData<any>
|
|
25
|
+
private fetchPromise: Promise<any> | undefined
|
|
26
|
+
|
|
27
|
+
static getInstance(appConfig: AppConfig): AppbrewCurrencyProvider {
|
|
28
|
+
if (!AppbrewCurrencyProvider.instance) {
|
|
29
|
+
AppbrewCurrencyProvider.instance = new AppbrewCurrencyProvider(appConfig)
|
|
30
|
+
}
|
|
31
|
+
return AppbrewCurrencyProvider.instance
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
constructor(private readonly appConfig: AppConfig) {
|
|
35
|
+
this.response = { status: 'init' }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async getAvailableCountries(
|
|
39
|
+
appConfig: AppConfig
|
|
40
|
+
): Promise<AsyncData<IAvailableCountryResponse>> {
|
|
41
|
+
const moduleSettings = getCurrencySettings(appConfig)
|
|
42
|
+
if (!moduleSettings) {
|
|
43
|
+
return {
|
|
44
|
+
status: 'error',
|
|
45
|
+
error: {
|
|
46
|
+
message: `Appbrew Currency module not configured`,
|
|
47
|
+
rootCause:
|
|
48
|
+
'Appbrew Currency module not configured at getAvailableCountries',
|
|
49
|
+
code: `APPBREW_CURRENCY_MODULE_NOT_CONFIGURED`,
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const defaultCountryCode = moduleSettings.defaultCountryCode
|
|
54
|
+
const supportedCountries = moduleSettings.supportedCountries
|
|
55
|
+
const currencyCodeMapping = moduleSettings?.currencyCodeMapping // This is an optional mapping for particular country - country code for showing currency, if not provided, it will use the default currency code from CountryCodeToCountryMap
|
|
56
|
+
|
|
57
|
+
const availableCountries = supportedCountries?.map((c: string) => {
|
|
58
|
+
return {
|
|
59
|
+
isoCode: c as CountryCode,
|
|
60
|
+
name: CountryCodeToCountryMap[c].countryName,
|
|
61
|
+
currency: {
|
|
62
|
+
isoCode:
|
|
63
|
+
CountryCodeToCountryMap[currencyCodeMapping?.[c]]?.currencyCode ??
|
|
64
|
+
(CountryCodeToCountryMap[c].currencyCode as CurrencyCode),
|
|
65
|
+
symbol:
|
|
66
|
+
CurrencyCodeToSymbolMap[
|
|
67
|
+
CountryCodeToCountryMap[currencyCodeMapping?.[c]]?.currencyCode
|
|
68
|
+
] ??
|
|
69
|
+
CurrencyCodeToSymbolMap[
|
|
70
|
+
CountryCodeToCountryMap?.[c].currencyCode
|
|
71
|
+
] ??
|
|
72
|
+
CountryCodeToCountryMap[c].currencyCode,
|
|
73
|
+
name:
|
|
74
|
+
CountryCodeToCountryMap[currencyCodeMapping?.[c]]?.currencyCode ??
|
|
75
|
+
CountryCodeToCountryMap[c].currencyCode,
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
const defaultCountry = {
|
|
80
|
+
isoCode: defaultCountryCode as CountryCode,
|
|
81
|
+
name: CountryCodeToCountryMap[defaultCountryCode].countryName,
|
|
82
|
+
currency: {
|
|
83
|
+
isoCode: CountryCodeToCountryMap[defaultCountryCode]
|
|
84
|
+
.currencyCode as CurrencyCode,
|
|
85
|
+
symbol:
|
|
86
|
+
CurrencyCodeToSymbolMap[
|
|
87
|
+
CountryCodeToCountryMap[defaultCountryCode].currencyCode
|
|
88
|
+
] ?? CountryCodeToCountryMap[defaultCountryCode].currencyCode,
|
|
89
|
+
name: CountryCodeToCountryMap[defaultCountryCode].currencyCode,
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
AppbrewCurrencyProvider.country = defaultCountry
|
|
93
|
+
|
|
94
|
+
const res: AsyncData<IAvailableCountryResponse> = {
|
|
95
|
+
status: 'idle',
|
|
96
|
+
data: {
|
|
97
|
+
availableCountries: availableCountries,
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
return res
|
|
101
|
+
}
|
|
102
|
+
async updateCountryCode(appConfig: AppConfig, countryCode: CountryCode) {
|
|
103
|
+
const country = {
|
|
104
|
+
isoCode: countryCode,
|
|
105
|
+
name: CountryCodeToCountryMap[countryCode].countryName,
|
|
106
|
+
currency: {
|
|
107
|
+
isoCode: CountryCodeToCountryMap[countryCode]
|
|
108
|
+
.currencyCode as CurrencyCode,
|
|
109
|
+
symbol:
|
|
110
|
+
CurrencyCodeToSymbolMap[
|
|
111
|
+
CountryCodeToCountryMap[countryCode].currencyCode
|
|
112
|
+
] ?? CountryCodeToCountryMap[countryCode].currencyCode,
|
|
113
|
+
name: CountryCodeToCountryMap[countryCode].currencyCode,
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
AppbrewCurrencyProvider.country = country
|
|
117
|
+
return country.isoCode
|
|
118
|
+
}
|
|
119
|
+
async getConversionrates(
|
|
120
|
+
appConfig: AppConfig
|
|
121
|
+
): Promise<AsyncData<ConversionRates>> {
|
|
122
|
+
if (this.response.status === 'idle') return this.response
|
|
123
|
+
const moduleSettings = getCurrencySettings(appConfig)
|
|
124
|
+
if (!moduleSettings) {
|
|
125
|
+
return {
|
|
126
|
+
status: 'error',
|
|
127
|
+
error: {
|
|
128
|
+
message: `Appbrew Currency module not configured`,
|
|
129
|
+
rootCause:
|
|
130
|
+
'Appbrew Currency module not configured at getConversionrates',
|
|
131
|
+
code: `APPBREW_CURRENCY_MODULE_NOT_CONFIGURED`,
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const defaultCountryCode = moduleSettings.defaultCountryCode
|
|
136
|
+
const supportedCountries = moduleSettings.supportedCountries
|
|
137
|
+
const bCurrency = CountryCodeToCountryMap[defaultCountryCode].currencyCode
|
|
138
|
+
const cCodes = supportedCountries.map(
|
|
139
|
+
(countryCode) => CountryCodeToCountryMap[countryCode].currencyCode
|
|
140
|
+
)
|
|
141
|
+
if (this.response.status === 'loading') {
|
|
142
|
+
if (this.fetchPromise) {
|
|
143
|
+
const result = await this.fetchPromise
|
|
144
|
+
return result
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
this.response = {
|
|
148
|
+
status: 'loading',
|
|
149
|
+
}
|
|
150
|
+
this.fetchPromise = fetchConversionRate(BASE_URL, bCurrency, cCodes)
|
|
151
|
+
const result = await this.fetchPromise
|
|
152
|
+
this.response = result
|
|
153
|
+
this.fetchPromise = undefined
|
|
154
|
+
return this.response
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function getCurrencySettings(
|
|
159
|
+
appConfig: AppConfig
|
|
160
|
+
): AppConfig['integrations']['appbrew-currency'] {
|
|
161
|
+
if (!appConfig) return
|
|
162
|
+
return (
|
|
163
|
+
appConfig.settings?.['integrations']?.['appbrew-currency'] ??
|
|
164
|
+
appConfig.settings?.['appbrew-currency']
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
async function fetchConversionRate(
|
|
168
|
+
baseUrl: string,
|
|
169
|
+
baseCurrencyCode: string,
|
|
170
|
+
currencyCodes: Array<string>
|
|
171
|
+
) {
|
|
172
|
+
let url = `${BASE_URL}?baseCurrencyCode=${baseCurrencyCode}`
|
|
173
|
+
|
|
174
|
+
if (currencyCodes) {
|
|
175
|
+
url = url.concat(`¤cyCodes=${currencyCodes.join(',')}`)
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const res = await fetch(url, {
|
|
179
|
+
method: 'GET',
|
|
180
|
+
headers: {
|
|
181
|
+
'Content-Type': 'application/json',
|
|
182
|
+
'x-integration-app-name': 'AppBrew',
|
|
183
|
+
'x-app-id': EnvConfig.APP_ID,
|
|
184
|
+
},
|
|
185
|
+
})
|
|
186
|
+
if (res.status >= 400) {
|
|
187
|
+
return {
|
|
188
|
+
status: 'error',
|
|
189
|
+
error: {
|
|
190
|
+
message: `Server error, Please try after some time`,
|
|
191
|
+
rootCause: 'API error',
|
|
192
|
+
code: `API_ERROR_${res.status}`,
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const result = await res.json()
|
|
197
|
+
return {
|
|
198
|
+
status: 'idle',
|
|
199
|
+
data: result,
|
|
200
|
+
}
|
|
201
|
+
} catch (e: any) {
|
|
202
|
+
return {
|
|
203
|
+
status: 'error',
|
|
204
|
+
error: {
|
|
205
|
+
message: 'fetch faile with exception',
|
|
206
|
+
rootCause: e.message,
|
|
207
|
+
code: `NETWORK_ERROR`,
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { Column, Row } from '@gauntlet/components/atoms/flex'
|
|
2
|
+
import {
|
|
3
|
+
useBlockSettings,
|
|
4
|
+
useBlockStyle,
|
|
5
|
+
} from '@gauntlet/components/style-utils'
|
|
6
|
+
import { useBlock, useEdd } from '@gauntlet/state'
|
|
7
|
+
import { BaseBlockProps } from '@gauntlet/types'
|
|
8
|
+
import { Text } from '@gauntlet/components/atoms/text'
|
|
9
|
+
import React from 'react'
|
|
10
|
+
import { Input } from '@gauntlet/components/atoms/input'
|
|
11
|
+
import { Button } from '@gauntlet/ui-builder'
|
|
12
|
+
import { Keyboard } from 'react-native'
|
|
13
|
+
import { add, format } from 'date-fns'
|
|
14
|
+
|
|
15
|
+
export type AppbrewEstimatedDeliveryDateProps = BaseBlockProps
|
|
16
|
+
|
|
17
|
+
export function AppbrewEstimatedDeliveryDate({
|
|
18
|
+
componentId,
|
|
19
|
+
instanceId,
|
|
20
|
+
screenId,
|
|
21
|
+
}: AppbrewEstimatedDeliveryDateProps) {
|
|
22
|
+
const block = useBlock<any>(screenId, componentId, instanceId)
|
|
23
|
+
const style = useBlockStyle(block)
|
|
24
|
+
const { source, options } = useBlockSettings(block)
|
|
25
|
+
const pincodeRef = React.useRef<string>('')
|
|
26
|
+
|
|
27
|
+
const [pincode, setPincode] = React.useState('')
|
|
28
|
+
|
|
29
|
+
const [loading, setLoading] = React.useState(false)
|
|
30
|
+
|
|
31
|
+
const defaultEdd = {
|
|
32
|
+
codAvailable: null,
|
|
33
|
+
serviceable: null,
|
|
34
|
+
pincodeValidity: true,
|
|
35
|
+
minDeliveryDate: '',
|
|
36
|
+
maxDeliveryDate: '',
|
|
37
|
+
express: false,
|
|
38
|
+
pincodeRequired: false,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const [
|
|
42
|
+
{
|
|
43
|
+
codAvailable,
|
|
44
|
+
pincodeValidity,
|
|
45
|
+
serviceable,
|
|
46
|
+
minDeliveryDate,
|
|
47
|
+
maxDeliveryDate,
|
|
48
|
+
pincodeRequired,
|
|
49
|
+
},
|
|
50
|
+
setEddRes,
|
|
51
|
+
] = React.useState<{
|
|
52
|
+
codAvailable: boolean | null
|
|
53
|
+
serviceable: boolean | null
|
|
54
|
+
pincodeValidity: boolean
|
|
55
|
+
minDeliveryDate: string
|
|
56
|
+
maxDeliveryDate: string
|
|
57
|
+
express: boolean
|
|
58
|
+
pincodeRequired: boolean
|
|
59
|
+
}>({
|
|
60
|
+
...defaultEdd,
|
|
61
|
+
})
|
|
62
|
+
const { fetchEstimatedDeliveryDate } = useEdd()
|
|
63
|
+
|
|
64
|
+
const handleSubmit = React.useCallback(() => {
|
|
65
|
+
Keyboard.dismiss()
|
|
66
|
+
|
|
67
|
+
if (!pincodeRef?.current) {
|
|
68
|
+
setEddRes((res) => ({
|
|
69
|
+
...res,
|
|
70
|
+
pincodeRequired: true,
|
|
71
|
+
}))
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (
|
|
76
|
+
(pincodeRef?.current?.length !== options?.pincodeLength ?? 6) ||
|
|
77
|
+
/^-?\d+$/.test(pincodeRef?.current) === false
|
|
78
|
+
) {
|
|
79
|
+
setEddRes((res) => ({
|
|
80
|
+
...res,
|
|
81
|
+
pincodeValidity: false,
|
|
82
|
+
serviceable: true,
|
|
83
|
+
codAvailable: true,
|
|
84
|
+
}))
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
setLoading(true)
|
|
89
|
+
|
|
90
|
+
return fetchEstimatedDeliveryDate(pincodeRef?.current)
|
|
91
|
+
.then((res) => {
|
|
92
|
+
if (res.isServiceable === false) {
|
|
93
|
+
setEddRes((res) => ({
|
|
94
|
+
...res,
|
|
95
|
+
pincodeValidity: true,
|
|
96
|
+
serviceable: false,
|
|
97
|
+
}))
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const cutoffDate = new Date(res.defaultCutoffTime * 1000)
|
|
102
|
+
const cutoffHours = cutoffDate.getUTCHours()
|
|
103
|
+
const cutoffMinutes = cutoffDate.getUTCMinutes()
|
|
104
|
+
|
|
105
|
+
// Get the current time
|
|
106
|
+
const currentDate = new Date()
|
|
107
|
+
const currentHours = currentDate.getUTCHours()
|
|
108
|
+
const currentMinutes = currentDate.getUTCMinutes()
|
|
109
|
+
|
|
110
|
+
// Compare the time components
|
|
111
|
+
let hasCutoffTimePassed = false
|
|
112
|
+
if (currentHours > cutoffHours) {
|
|
113
|
+
hasCutoffTimePassed = true
|
|
114
|
+
} else if (currentHours === cutoffHours) {
|
|
115
|
+
if (currentMinutes > cutoffMinutes) {
|
|
116
|
+
hasCutoffTimePassed = true
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const minDate = format(
|
|
121
|
+
add(new Date(), {
|
|
122
|
+
days: Number(res.maxSLAInDays) + (hasCutoffTimePassed ? 1 : 0),
|
|
123
|
+
}),
|
|
124
|
+
'EEEE, LLL d'
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
const maxDate = format(
|
|
128
|
+
add(new Date(), {
|
|
129
|
+
days: Number(res.maxSLAInDays) + (hasCutoffTimePassed ? 1 : 0),
|
|
130
|
+
}),
|
|
131
|
+
'EEEE, LLL d'
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
const serviceable = res.isServiceable.toLowerCase() === 'true' ?? true
|
|
135
|
+
const codAvailable = res.isCODAvailable.toLowerCase() === 'true'
|
|
136
|
+
|
|
137
|
+
const pincodeValidity = true
|
|
138
|
+
const eddRes = {
|
|
139
|
+
minDeliveryDate: minDate,
|
|
140
|
+
maxDeliveryDate: maxDate,
|
|
141
|
+
codAvailable,
|
|
142
|
+
serviceable,
|
|
143
|
+
pincodeValidity,
|
|
144
|
+
express: Boolean(res.hyperlocal),
|
|
145
|
+
}
|
|
146
|
+
setEddRes(eddRes)
|
|
147
|
+
})
|
|
148
|
+
.catch((err) => {
|
|
149
|
+
console.error({ err })
|
|
150
|
+
})
|
|
151
|
+
.finally(() => {
|
|
152
|
+
setLoading(false)
|
|
153
|
+
})
|
|
154
|
+
}, [
|
|
155
|
+
fetchEstimatedDeliveryDate,
|
|
156
|
+
options?.additionalDays,
|
|
157
|
+
options?.pincodeLength,
|
|
158
|
+
])
|
|
159
|
+
|
|
160
|
+
return (
|
|
161
|
+
<Column style={style?.container}>
|
|
162
|
+
<Text style={style?.title}>{source?.title}</Text>
|
|
163
|
+
<Row style={{ marginTop: 16, ...style?.inputContainer }}>
|
|
164
|
+
<Input
|
|
165
|
+
placeholder={source?.inputPlaceholder}
|
|
166
|
+
placeholderTextColor={style?.inputPlaceholderColor}
|
|
167
|
+
onChangeText={(text) => {
|
|
168
|
+
pincodeRef.current = text
|
|
169
|
+
setPincode(text)
|
|
170
|
+
setEddRes(defaultEdd)
|
|
171
|
+
}}
|
|
172
|
+
style={{
|
|
173
|
+
borderColor: pincodeValidity ? 'black' : 'red',
|
|
174
|
+
borderWidth: 1,
|
|
175
|
+
...style?.input,
|
|
176
|
+
}}
|
|
177
|
+
keyboardType={'numeric'}
|
|
178
|
+
onSubmitEditing={handleSubmit}
|
|
179
|
+
/>
|
|
180
|
+
|
|
181
|
+
<Button
|
|
182
|
+
onPress={handleSubmit}
|
|
183
|
+
text={source?.buttonText}
|
|
184
|
+
style={{
|
|
185
|
+
root: style.button.root,
|
|
186
|
+
text: style.button.text,
|
|
187
|
+
...style.button,
|
|
188
|
+
}}
|
|
189
|
+
// disabled={pincode?.length <= 0}
|
|
190
|
+
loading={loading}
|
|
191
|
+
/>
|
|
192
|
+
</Row>
|
|
193
|
+
{Boolean(
|
|
194
|
+
serviceable !== false &&
|
|
195
|
+
pincodeValidity &&
|
|
196
|
+
(minDeliveryDate || maxDeliveryDate)
|
|
197
|
+
) && (
|
|
198
|
+
<Text style={style?.deliveryDateText}>
|
|
199
|
+
{Boolean(source?.template) && (
|
|
200
|
+
<Text style={style?.template}>{source?.template}</Text>
|
|
201
|
+
)}
|
|
202
|
+
{minDeliveryDate === maxDeliveryDate
|
|
203
|
+
? `Expected delivery by ${minDeliveryDate}`
|
|
204
|
+
: `Expected delivery between ${minDeliveryDate} and ${maxDeliveryDate}`}
|
|
205
|
+
</Text>
|
|
206
|
+
)}
|
|
207
|
+
|
|
208
|
+
{Boolean(codAvailable && serviceable !== false && pincodeValidity) && (
|
|
209
|
+
<Text style={style?.codAvailableText}>{source?.codAvailableText}</Text>
|
|
210
|
+
)}
|
|
211
|
+
|
|
212
|
+
{Boolean(serviceable === false) && (
|
|
213
|
+
<Text style={style?.notServiceableText}>
|
|
214
|
+
{source?.notServiceableText}
|
|
215
|
+
</Text>
|
|
216
|
+
)}
|
|
217
|
+
|
|
218
|
+
{Boolean(!pincodeValidity) && (
|
|
219
|
+
<Text style={style?.invalidPincodeText}>
|
|
220
|
+
{source?.invalidPincodeText}
|
|
221
|
+
</Text>
|
|
222
|
+
)}
|
|
223
|
+
|
|
224
|
+
{Boolean(pincodeRequired) && (
|
|
225
|
+
<Text style={style?.pincodeRequired}>{source?.pincodeRequired}</Text>
|
|
226
|
+
)}
|
|
227
|
+
</Column>
|
|
228
|
+
)
|
|
229
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { EstimatedDeliveryDateProvider } from '@gauntlet/types'
|
|
2
|
+
import EnvConfig from 'react-native-config'
|
|
3
|
+
|
|
4
|
+
export class AppbrewEstimatedDeliveryDateProvider
|
|
5
|
+
implements EstimatedDeliveryDateProvider
|
|
6
|
+
{
|
|
7
|
+
BASE_URL
|
|
8
|
+
private responseCache = new Map<string, any>()
|
|
9
|
+
static instance: AppbrewEstimatedDeliveryDateProvider
|
|
10
|
+
|
|
11
|
+
static getInstance() {
|
|
12
|
+
if (!AppbrewEstimatedDeliveryDateProvider.instance) {
|
|
13
|
+
AppbrewEstimatedDeliveryDateProvider.instance =
|
|
14
|
+
new AppbrewEstimatedDeliveryDateProvider()
|
|
15
|
+
}
|
|
16
|
+
return AppbrewEstimatedDeliveryDateProvider.instance
|
|
17
|
+
}
|
|
18
|
+
constructor() {
|
|
19
|
+
this.BASE_URL = `${EnvConfig['APP_SERVICE_URL']}/integrations/appbrew-edd/estimated-delivery-details`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
estimateDeliveryDate = async (dropPincode: string, appId: string) => {
|
|
23
|
+
if (this.responseCache.has(dropPincode)) {
|
|
24
|
+
return this.responseCache.get(dropPincode)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const res = await fetch(`${this.BASE_URL}`, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: {
|
|
30
|
+
'Content-Type': 'application/json',
|
|
31
|
+
'x-app-id': appId,
|
|
32
|
+
},
|
|
33
|
+
body: JSON.stringify({
|
|
34
|
+
zipCode: dropPincode,
|
|
35
|
+
}),
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const jsonRes = await res.json()
|
|
39
|
+
|
|
40
|
+
if (res.ok) {
|
|
41
|
+
this.responseCache.set(dropPincode, jsonRes)
|
|
42
|
+
return jsonRes
|
|
43
|
+
} else throw new Error('Could not fetch estimated delivery date')
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./provider"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { LocalStorage } from '@gauntlet/local-storage'
|
|
2
|
+
import { WishlistItem, WishlistProvider } from '@gauntlet/types'
|
|
3
|
+
|
|
4
|
+
export class LocalWishlistProvider implements WishlistProvider {
|
|
5
|
+
static instance: LocalWishlistProvider
|
|
6
|
+
|
|
7
|
+
static getInstance() {
|
|
8
|
+
if (LocalWishlistProvider.instance) {
|
|
9
|
+
return LocalWishlistProvider.instance
|
|
10
|
+
}
|
|
11
|
+
LocalWishlistProvider.instance = new LocalWishlistProvider()
|
|
12
|
+
|
|
13
|
+
return LocalWishlistProvider.instance
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
WISHLIST_NATIVE_APPBREW_KEY = {
|
|
17
|
+
wishlistItems: 'wishlist-items',
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
init() {
|
|
21
|
+
if (!this.isInitialised()) {
|
|
22
|
+
LocalStorage.getInstance().set(
|
|
23
|
+
this.WISHLIST_NATIVE_APPBREW_KEY.wishlistItems,
|
|
24
|
+
JSON.stringify([])
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
return Promise.resolve()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getWishlist() {
|
|
31
|
+
const wishlistItems = getStoredValue(
|
|
32
|
+
this.WISHLIST_NATIVE_APPBREW_KEY.wishlistItems
|
|
33
|
+
)
|
|
34
|
+
return Promise.resolve(wishlistItems)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
addToWishlist(products: WishlistItem[]) {
|
|
38
|
+
const wishlistItemsJson = getStoredValue(
|
|
39
|
+
this.WISHLIST_NATIVE_APPBREW_KEY.wishlistItems
|
|
40
|
+
)
|
|
41
|
+
if (wishlistItemsJson) {
|
|
42
|
+
const wishlistItemsList = Array.from(
|
|
43
|
+
new Set([...wishlistItemsJson.concat(products)])
|
|
44
|
+
)
|
|
45
|
+
LocalStorage.getInstance().set(
|
|
46
|
+
this.WISHLIST_NATIVE_APPBREW_KEY.wishlistItems,
|
|
47
|
+
wishlistItemsList as any
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
return Promise.resolve()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
removeFromWishlist(itemsToBeRemoved: WishlistItem[]) {
|
|
54
|
+
const itemsInWishlist = getStoredValue(
|
|
55
|
+
this.WISHLIST_NATIVE_APPBREW_KEY.wishlistItems
|
|
56
|
+
)
|
|
57
|
+
const updatedList: Array<WishlistItem> = []
|
|
58
|
+
if (Array.isArray(itemsInWishlist)) {
|
|
59
|
+
for (const item of itemsInWishlist) {
|
|
60
|
+
const itemToBeRemoved = itemsToBeRemoved.find(
|
|
61
|
+
(product) => product.productId === item.productId
|
|
62
|
+
)
|
|
63
|
+
if (!itemToBeRemoved) {
|
|
64
|
+
updatedList.push(item)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
LocalStorage.getInstance().set(
|
|
69
|
+
this.WISHLIST_NATIVE_APPBREW_KEY.wishlistItems,
|
|
70
|
+
updatedList as any
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
return Promise.resolve()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
isInitialised() {
|
|
77
|
+
const wishlist = getStoredValue(
|
|
78
|
+
this.WISHLIST_NATIVE_APPBREW_KEY.wishlistItems
|
|
79
|
+
)
|
|
80
|
+
return Array.isArray(wishlist)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getStoredValue(key: string) {
|
|
85
|
+
return LocalStorage.getInstance().getJson(key) as
|
|
86
|
+
| Array<WishlistItem>
|
|
87
|
+
| undefined
|
|
88
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@app-brew/appbrew",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"files": [
|
|
5
|
+
"analytics/",
|
|
6
|
+
"currency/",
|
|
7
|
+
"estimated-delivery-date/",
|
|
8
|
+
"native-wishlist/",
|
|
9
|
+
"src/",
|
|
10
|
+
"tsconfig.paths.json"
|
|
11
|
+
],
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"@react-native-firebase/analytics": "23.3.1",
|
|
14
|
+
"@react-native-firebase/messaging": "23.3.1",
|
|
15
|
+
"@sentry/react-native": "7.8.0",
|
|
16
|
+
"date-fns": "2.30.0",
|
|
17
|
+
"react": "19.1.0",
|
|
18
|
+
"react-native": "0.81.0",
|
|
19
|
+
"react-native-base64": "^0.2.1",
|
|
20
|
+
"react-native-config": "1.5.9",
|
|
21
|
+
"@app-brew/brewery": ">=1.0.0"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './lib/integrations-appbrew'
|
|
2
|
+
export * from '../native-wishlist/src'
|
|
3
|
+
export * from '../analytics/tracker'
|
|
4
|
+
export * from '../analytics/trackerV2'
|
|
5
|
+
export * from '../analytics/webhook-tracker'
|
|
6
|
+
export * from '../currency'
|
|
7
|
+
export * from '../estimated-delivery-date'
|