@app-brew/judgeme 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/package.json +17 -0
- package/src/index.ts +14 -0
- package/src/judgeme-review-provider.ts +42 -0
- package/src/judgeme-reviews.tsx +148 -0
- package/src/judgeme-screen.tsx +267 -0
- package/src/judgeme-star-ratings.tsx +74 -0
- package/src/ratings-count.tsx +46 -0
- package/tsconfig.paths.json +10 -0
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@app-brew/judgeme",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"files": [
|
|
5
|
+
"src/",
|
|
6
|
+
"tsconfig.paths.json"
|
|
7
|
+
],
|
|
8
|
+
"peerDependencies": {
|
|
9
|
+
"@fastify/deepmerge": "^1.3.0",
|
|
10
|
+
"axios": "^1.8.3",
|
|
11
|
+
"react": "19.1.0",
|
|
12
|
+
"react-native": "0.81.0",
|
|
13
|
+
"react-native-config": "1.5.9",
|
|
14
|
+
"react-native-webview": "13.16.0",
|
|
15
|
+
"@app-brew/brewery": ">=1.0.0"
|
|
16
|
+
}
|
|
17
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { JudgemeReviewsV2 } from './judgeme-reviews'
|
|
2
|
+
import { JudgemeReviewScreen } from './judgeme-screen'
|
|
3
|
+
|
|
4
|
+
export * from './judgeme-review-provider'
|
|
5
|
+
export * from './judgeme-star-ratings'
|
|
6
|
+
export * from './ratings-count'
|
|
7
|
+
|
|
8
|
+
export function registerJudgemeBlocks(r: Map<string, any>) {
|
|
9
|
+
r.set('judgeme-reviews-v2', JudgemeReviewsV2)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function registerJudgemeScreen(r: Map<string, any>) {
|
|
13
|
+
r.set('product-reviews', JudgemeReviewScreen)
|
|
14
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import axios from 'axios'
|
|
2
|
+
import EnvConfig from 'react-native-config'
|
|
3
|
+
import { ReviewDetails, ReviewProvider } from '@gauntlet/types'
|
|
4
|
+
|
|
5
|
+
export class JudgemeReviewProvider implements ReviewProvider {
|
|
6
|
+
static instance: JudgemeReviewProvider
|
|
7
|
+
static getInstance() {
|
|
8
|
+
if (!JudgemeReviewProvider.instance) {
|
|
9
|
+
JudgemeReviewProvider.instance = new JudgemeReviewProvider()
|
|
10
|
+
}
|
|
11
|
+
return JudgemeReviewProvider.instance
|
|
12
|
+
}
|
|
13
|
+
async submitReview(
|
|
14
|
+
reviewDetails: ReviewDetails,
|
|
15
|
+
customerDetails: {
|
|
16
|
+
customerId: string
|
|
17
|
+
customerAccessToken: string
|
|
18
|
+
email: string
|
|
19
|
+
displayName: string
|
|
20
|
+
}
|
|
21
|
+
) {
|
|
22
|
+
const appId = EnvConfig.APP_ID
|
|
23
|
+
await axios.post(
|
|
24
|
+
`${EnvConfig['APP_SERVICE_URL']}/integrations/judgeme/reviews`,
|
|
25
|
+
reviewDetails,
|
|
26
|
+
{
|
|
27
|
+
headers: {
|
|
28
|
+
'X-App-Id': appId,
|
|
29
|
+
'X-Customer-Access-Token': customerDetails.customerAccessToken,
|
|
30
|
+
'X-Customer-Id': customerDetails.customerId,
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
async getReviewsSummary(productId: string) {
|
|
37
|
+
return {}
|
|
38
|
+
}
|
|
39
|
+
async getReviews(productId: string, page: number) {
|
|
40
|
+
return {}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
ScreenContext,
|
|
4
|
+
useAppStore,
|
|
5
|
+
useBlock,
|
|
6
|
+
useDeviceDimensions,
|
|
7
|
+
useProductByHandle,
|
|
8
|
+
useUser,
|
|
9
|
+
} from '@gauntlet/state'
|
|
10
|
+
import {
|
|
11
|
+
BaseBlockProps,
|
|
12
|
+
JudgemeReviewsConfig,
|
|
13
|
+
ProductId,
|
|
14
|
+
} from '@gauntlet/types'
|
|
15
|
+
import { Section } from '@gauntlet/components/atoms/section'
|
|
16
|
+
import { Text } from '@gauntlet/components/atoms/text'
|
|
17
|
+
import { View } from 'react-native'
|
|
18
|
+
import { Loader } from '@gauntlet/components/organisms/payment-web-view'
|
|
19
|
+
|
|
20
|
+
import { useBlockSettings } from '@gauntlet/components/style-utils'
|
|
21
|
+
import { WebView } from 'react-native-webview'
|
|
22
|
+
import { useLink } from '@gauntlet/state'
|
|
23
|
+
|
|
24
|
+
export type JudgemeReviewsProps = BaseBlockProps & {
|
|
25
|
+
productHandle: string
|
|
26
|
+
}
|
|
27
|
+
export function JudgemeReviewsV2({
|
|
28
|
+
screenId,
|
|
29
|
+
componentId,
|
|
30
|
+
instanceId,
|
|
31
|
+
productHandle,
|
|
32
|
+
}: JudgemeReviewsProps) {
|
|
33
|
+
const { gotoLink } = useLink()
|
|
34
|
+
const webViewRef = React.useRef<any>(null)
|
|
35
|
+
const { width, height } = useDeviceDimensions()
|
|
36
|
+
const screenContext = React.useContext(ScreenContext)
|
|
37
|
+
const contentHeight = screenContext?.value?.contentHeight ?? 710
|
|
38
|
+
|
|
39
|
+
const block = useBlock<JudgemeReviewsConfig>(
|
|
40
|
+
screenId,
|
|
41
|
+
componentId,
|
|
42
|
+
instanceId
|
|
43
|
+
)
|
|
44
|
+
const settings = useBlockSettings(block)
|
|
45
|
+
|
|
46
|
+
const product = useProductByHandle(productHandle)
|
|
47
|
+
const judgemeConfig = useAppStore.getState().config.data?.integrations.judgeme
|
|
48
|
+
const options = settings?.options
|
|
49
|
+
|
|
50
|
+
const { isUserAuthenticated } = useUser()
|
|
51
|
+
|
|
52
|
+
if (product.status !== 'idle' || !judgemeConfig)
|
|
53
|
+
return (
|
|
54
|
+
<Section
|
|
55
|
+
style={{
|
|
56
|
+
flex: 1,
|
|
57
|
+
alignItems: 'center',
|
|
58
|
+
...block?.style?.root,
|
|
59
|
+
height: height,
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<Text>Loading...</Text>
|
|
63
|
+
</Section>
|
|
64
|
+
)
|
|
65
|
+
if (!product.data)
|
|
66
|
+
console.log(
|
|
67
|
+
`Expected product.data to be defined for productHandle ${productHandle} and status ${product.status}`
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
const onMessageReceive = (event: any) => {
|
|
71
|
+
const data = JSON.parse(event.nativeEvent.data)
|
|
72
|
+
if (data.action === 'navigate') {
|
|
73
|
+
if (data.path === 'add-review') {
|
|
74
|
+
if (isUserAuthenticated()) {
|
|
75
|
+
gotoLink({
|
|
76
|
+
kind: 'screen',
|
|
77
|
+
value: `${data.path}/${productHandle}`,
|
|
78
|
+
})
|
|
79
|
+
} else {
|
|
80
|
+
gotoLink({
|
|
81
|
+
kind: 'screen',
|
|
82
|
+
value: options?.signInPath ?? 'signin',
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
gotoLink({
|
|
89
|
+
kind: 'screen',
|
|
90
|
+
value: data.path,
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!judgemeConfig) return null
|
|
96
|
+
|
|
97
|
+
const uri = `https://appbrew.pages.dev/judgeme-reviews?productId=${getJudgemeProductId(
|
|
98
|
+
product.data.id
|
|
99
|
+
)}&domain=${judgemeConfig.storeDomain}&token=${
|
|
100
|
+
judgemeConfig.publicToken
|
|
101
|
+
}&platform=${judgemeConfig.platform ?? 'shopify'}&stylesheet=${
|
|
102
|
+
judgemeConfig.stylesheet
|
|
103
|
+
}`
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<WebView
|
|
107
|
+
ref={webViewRef}
|
|
108
|
+
style={{
|
|
109
|
+
height: contentHeight,
|
|
110
|
+
width: width,
|
|
111
|
+
...block?.style?.root,
|
|
112
|
+
}}
|
|
113
|
+
androidLayerType="hardware"
|
|
114
|
+
startInLoadingState={true}
|
|
115
|
+
androidHardwareAccelerationDisabled={true}
|
|
116
|
+
renderLoading={() => (
|
|
117
|
+
<View
|
|
118
|
+
style={{
|
|
119
|
+
position: 'absolute',
|
|
120
|
+
alignItems: 'center',
|
|
121
|
+
justifyContent: 'center',
|
|
122
|
+
left: 0,
|
|
123
|
+
right: 0,
|
|
124
|
+
top: 0,
|
|
125
|
+
bottom: 0,
|
|
126
|
+
}}
|
|
127
|
+
>
|
|
128
|
+
<Loader colors={['#000', '#000', '#000', '#000']} />
|
|
129
|
+
</View>
|
|
130
|
+
)}
|
|
131
|
+
automaticallyAdjustContentInsets={true}
|
|
132
|
+
scrollEnabled={true}
|
|
133
|
+
javaScriptEnabled={true}
|
|
134
|
+
domStorageEnabled={true}
|
|
135
|
+
nestedScrollEnabled={true}
|
|
136
|
+
source={{
|
|
137
|
+
uri,
|
|
138
|
+
}}
|
|
139
|
+
onMessage={onMessageReceive}
|
|
140
|
+
/>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function getJudgemeProductId(id: ProductId) {
|
|
145
|
+
if (!id.startsWith('gid')) return null
|
|
146
|
+
const judgemeId = id.replace(/[^0-9]/g, '')
|
|
147
|
+
return judgemeId
|
|
148
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { CustomScreenProps } from '@gauntlet/types'
|
|
2
|
+
import { SafeAreaContainer } from '@gauntlet/components/atoms/safe-area-container'
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import {
|
|
5
|
+
ScreenContext,
|
|
6
|
+
useAppStore,
|
|
7
|
+
useDeviceDimensions,
|
|
8
|
+
useProductByHandle,
|
|
9
|
+
useUser,
|
|
10
|
+
} from '@gauntlet/state'
|
|
11
|
+
import { ActivityIndicator, View } from 'react-native'
|
|
12
|
+
|
|
13
|
+
import { WebView } from 'react-native-webview'
|
|
14
|
+
import { useLink } from '@gauntlet/state'
|
|
15
|
+
import { EnhancedProduct, IdleData, ProductId } from '@gauntlet/types'
|
|
16
|
+
import { appbarVariants } from '@gauntlet/components/molecules/appbar-v2'
|
|
17
|
+
import deepmerge from '@fastify/deepmerge'
|
|
18
|
+
import { AppbarTemplate } from '@gauntlet/components/molecules/appbar-template'
|
|
19
|
+
import { Text } from '@gauntlet/components/index'
|
|
20
|
+
|
|
21
|
+
const BASE_URL = 'https://appbrew.pages.dev'
|
|
22
|
+
|
|
23
|
+
export function JudgemeReviewScreen({
|
|
24
|
+
id,
|
|
25
|
+
options: screenOptions,
|
|
26
|
+
}: CustomScreenProps) {
|
|
27
|
+
const { productHandle } = screenOptions
|
|
28
|
+
const { gotoLink } = useLink()
|
|
29
|
+
const webViewRef = React.useRef<any>(null)
|
|
30
|
+
const { width } = useDeviceDimensions()
|
|
31
|
+
const screenContext = React.useContext(ScreenContext)
|
|
32
|
+
const contentHeight = screenContext?.value?.contentHeight ?? 710
|
|
33
|
+
|
|
34
|
+
const product = useProductByHandle(productHandle ?? '')
|
|
35
|
+
const judgemeConfig = useAppStore.getState().config.data?.integrations.judgeme
|
|
36
|
+
const reviewsScreen =
|
|
37
|
+
useAppStore.getState().config?.data?.screens?.['product-reviews']
|
|
38
|
+
const judgemeMetafieldExists = Boolean(
|
|
39
|
+
useAppStore.getState().metafield.data.product?.[productHandle]?.judgeme
|
|
40
|
+
?.badge
|
|
41
|
+
)
|
|
42
|
+
const settings = reviewsScreen?.settings ?? {
|
|
43
|
+
source: {},
|
|
44
|
+
options: {},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const cssVariables = React.useMemo(() => {
|
|
48
|
+
if (!judgemeConfig) return {}
|
|
49
|
+
const defaultPrimaryColor =
|
|
50
|
+
useAppStore.getState().config.data?.theme?.base?.colors?.brandprimary ??
|
|
51
|
+
'#000000'
|
|
52
|
+
|
|
53
|
+
const defaultTextOnPrimaryColor =
|
|
54
|
+
useAppStore.getState().config.data?.theme?.base?.colors
|
|
55
|
+
?.textprimaryinverse ?? '#ffffff'
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* primary color: will change the background color of the button and the verified badge
|
|
59
|
+
* text-dark: title and content of the reviews
|
|
60
|
+
* star-color: color of the stars
|
|
61
|
+
* bg-very-light: background color of the reviews
|
|
62
|
+
* text-on-primary: text color on the primary color (button and verified badge)
|
|
63
|
+
* button-border-radius: border radius of the button
|
|
64
|
+
* font-family: font family of the text
|
|
65
|
+
*
|
|
66
|
+
*/
|
|
67
|
+
const cssVariablesMap: Record<string, string | number> = {
|
|
68
|
+
primaryColor: judgemeConfig.primaryColor || defaultPrimaryColor,
|
|
69
|
+
starColor: judgemeConfig.starColor || '#ffa41c',
|
|
70
|
+
textDark: judgemeConfig.textDark || '#1a1b18',
|
|
71
|
+
reviewsBackgroundColor: judgemeConfig.bgVeryLight || '#f9f9f9',
|
|
72
|
+
textOnPrimary: judgemeConfig.textOnPrimary || defaultTextOnPrimaryColor,
|
|
73
|
+
buttonBorderRadius: judgemeConfig.buttonBorderRadius ?? 999,
|
|
74
|
+
fontFamily: judgemeConfig.fontFamily || 'Roboto',
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (const key in cssVariablesMap) {
|
|
78
|
+
const value = String(cssVariablesMap[key])
|
|
79
|
+
if (value.startsWith('#')) {
|
|
80
|
+
cssVariablesMap[key] = value.replace('#', '')
|
|
81
|
+
} else {
|
|
82
|
+
cssVariablesMap[key] = value
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return cssVariablesMap
|
|
87
|
+
}, [judgemeConfig])
|
|
88
|
+
|
|
89
|
+
const style = reviewsScreen?.style ?? {}
|
|
90
|
+
const variant = settings?.options?.appbar?.variant ?? 'minimal'
|
|
91
|
+
const appbarVariant = appbarVariants[variant]
|
|
92
|
+
|
|
93
|
+
const { isUserAuthenticated } = useUser()
|
|
94
|
+
const mergedStyle = deepmerge({ all: true })(
|
|
95
|
+
appbarVariant?.style,
|
|
96
|
+
style?.appbar ?? {}
|
|
97
|
+
)
|
|
98
|
+
const finalSettings = settings?.['appbar'] ?? appbarVariant?.settings
|
|
99
|
+
|
|
100
|
+
if (!judgemeMetafieldExists) {
|
|
101
|
+
return (
|
|
102
|
+
<SafeAreaContainer>
|
|
103
|
+
<AppbarTemplate
|
|
104
|
+
options={finalSettings?.options}
|
|
105
|
+
style={mergedStyle}
|
|
106
|
+
source={finalSettings?.source}
|
|
107
|
+
variant={variant}
|
|
108
|
+
/>
|
|
109
|
+
<View style={style.error?.root}>
|
|
110
|
+
<Text style={style.error?.text}>
|
|
111
|
+
{settings?.options?.errorMessage ?? 'No reviews to display'}
|
|
112
|
+
</Text>
|
|
113
|
+
</View>
|
|
114
|
+
</SafeAreaContainer>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (product.status !== 'idle' || !judgemeConfig)
|
|
119
|
+
return (
|
|
120
|
+
<SafeAreaContainer>
|
|
121
|
+
<AppbarTemplate
|
|
122
|
+
options={finalSettings?.options}
|
|
123
|
+
style={mergedStyle}
|
|
124
|
+
source={finalSettings?.source}
|
|
125
|
+
variant={variant}
|
|
126
|
+
/>
|
|
127
|
+
<ActivityIndicator
|
|
128
|
+
size={'large'}
|
|
129
|
+
color={'#000'}
|
|
130
|
+
style={{
|
|
131
|
+
position: 'absolute',
|
|
132
|
+
alignItems: 'center',
|
|
133
|
+
justifyContent: 'center',
|
|
134
|
+
left: 0,
|
|
135
|
+
right: 0,
|
|
136
|
+
top: 0,
|
|
137
|
+
bottom: 0,
|
|
138
|
+
...mergedStyle?.loader,
|
|
139
|
+
}}
|
|
140
|
+
/>
|
|
141
|
+
</SafeAreaContainer>
|
|
142
|
+
)
|
|
143
|
+
if (!product.data)
|
|
144
|
+
console.log(
|
|
145
|
+
`Expected product.data to be defined for productHandle ${productHandle} and status ${product.status}`
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
const onMessageReceive = (event: any) => {
|
|
149
|
+
const data = JSON.parse(event.nativeEvent.data)
|
|
150
|
+
if (data.action === 'navigate') {
|
|
151
|
+
if (data.path === 'add-review') {
|
|
152
|
+
if (isUserAuthenticated()) {
|
|
153
|
+
gotoLink({
|
|
154
|
+
kind: 'screen',
|
|
155
|
+
value: `${data.path}`,
|
|
156
|
+
params: { productHandle },
|
|
157
|
+
})
|
|
158
|
+
} else {
|
|
159
|
+
gotoLink({
|
|
160
|
+
kind: 'screen',
|
|
161
|
+
value: settings?.options?.signInPath ?? 'signin',
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
gotoLink({
|
|
168
|
+
kind: 'screen',
|
|
169
|
+
value: data.path,
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!judgemeConfig) return null
|
|
175
|
+
|
|
176
|
+
const uri = gerUri(product, judgemeConfig, cssVariables)
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<SafeAreaContainer style={style?.screen}>
|
|
180
|
+
<AppbarTemplate
|
|
181
|
+
options={finalSettings?.options}
|
|
182
|
+
style={mergedStyle}
|
|
183
|
+
source={finalSettings?.source}
|
|
184
|
+
variant={variant}
|
|
185
|
+
/>
|
|
186
|
+
<WebView
|
|
187
|
+
ref={webViewRef}
|
|
188
|
+
style={{
|
|
189
|
+
height: contentHeight,
|
|
190
|
+
width: width,
|
|
191
|
+
...mergedStyle?.webview,
|
|
192
|
+
}}
|
|
193
|
+
androidLayerType="hardware"
|
|
194
|
+
startInLoadingState={true}
|
|
195
|
+
androidHardwareAccelerationDisabled={true}
|
|
196
|
+
renderLoading={() => (
|
|
197
|
+
<View
|
|
198
|
+
style={{
|
|
199
|
+
position: 'absolute',
|
|
200
|
+
alignItems: 'center',
|
|
201
|
+
justifyContent: 'center',
|
|
202
|
+
left: 0,
|
|
203
|
+
right: 0,
|
|
204
|
+
top: 0,
|
|
205
|
+
bottom: 0,
|
|
206
|
+
}}
|
|
207
|
+
>
|
|
208
|
+
<ActivityIndicator
|
|
209
|
+
size={'large'}
|
|
210
|
+
color={'#000'}
|
|
211
|
+
style={{
|
|
212
|
+
position: 'absolute',
|
|
213
|
+
alignItems: 'center',
|
|
214
|
+
justifyContent: 'center',
|
|
215
|
+
left: 0,
|
|
216
|
+
right: 0,
|
|
217
|
+
top: 0,
|
|
218
|
+
bottom: 0,
|
|
219
|
+
...style?.loader,
|
|
220
|
+
}}
|
|
221
|
+
/>
|
|
222
|
+
</View>
|
|
223
|
+
)}
|
|
224
|
+
automaticallyAdjustContentInsets={true}
|
|
225
|
+
scrollEnabled={true}
|
|
226
|
+
javaScriptEnabled={true}
|
|
227
|
+
domStorageEnabled={true}
|
|
228
|
+
nestedScrollEnabled={true}
|
|
229
|
+
source={{
|
|
230
|
+
uri,
|
|
231
|
+
}}
|
|
232
|
+
onMessage={onMessageReceive}
|
|
233
|
+
/>
|
|
234
|
+
</SafeAreaContainer>
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function getJudgemeProductId(id: ProductId) {
|
|
239
|
+
if (!id.startsWith('gid')) return null
|
|
240
|
+
const judgemeId = id.replace(/[^0-9]/g, '')
|
|
241
|
+
return judgemeId
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function gerUri(
|
|
245
|
+
product: IdleData<EnhancedProduct>,
|
|
246
|
+
judgemeConfig: {
|
|
247
|
+
storeDomain: string
|
|
248
|
+
platform: string
|
|
249
|
+
publicToken: string
|
|
250
|
+
stylesheet?: string | undefined
|
|
251
|
+
customHtmlPageUrl?: string | undefined
|
|
252
|
+
},
|
|
253
|
+
cssVariables: Record<string, string | number>
|
|
254
|
+
) {
|
|
255
|
+
const defaultJudgemeUrl = `${BASE_URL}/judgeme-reviews/index.html`
|
|
256
|
+
const storeDomain = useAppStore.getState().config.data?.store?.shopifyDomain
|
|
257
|
+
const platform = judgemeConfig.platform ?? 'shopify'
|
|
258
|
+
|
|
259
|
+
const uri = judgemeConfig?.customHtmlPageUrl ?? defaultJudgemeUrl
|
|
260
|
+
return `${uri}?productId=${getJudgemeProductId(
|
|
261
|
+
product.data.id
|
|
262
|
+
)}&domain=${storeDomain}&token=${judgemeConfig.publicToken
|
|
263
|
+
}&platform=${platform}&primaryColor=${cssVariables.primaryColor}&textDark=${cssVariables.textDark
|
|
264
|
+
}&starColor=${cssVariables.starColor}&reviewsBackgroundColor=${cssVariables.reviewsBackgroundColor
|
|
265
|
+
}&textOnPrimary=${cssVariables.textOnPrimary}&buttonBorderRadius=${cssVariables.buttonBorderRadius
|
|
266
|
+
}&fontFamily=${cssVariables.fontFamily}`
|
|
267
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { BaseIconStyle, BaseStyle, BaseTextStyle } from '@gauntlet/types'
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { ProductComponentsProps } from '@gauntlet/components/molecules/product-components'
|
|
4
|
+
import { View } from 'react-native'
|
|
5
|
+
import StarRating from '@gauntlet/components/atoms/stars'
|
|
6
|
+
import { useAppStore } from '@gauntlet/state'
|
|
7
|
+
|
|
8
|
+
interface JudgemeStarRatingsStyle {
|
|
9
|
+
root: BaseStyle
|
|
10
|
+
text: BaseTextStyle
|
|
11
|
+
star: BaseIconStyle
|
|
12
|
+
container?: BaseStyle
|
|
13
|
+
}
|
|
14
|
+
interface JudgemeStarRatingsOptions {}
|
|
15
|
+
|
|
16
|
+
interface JudgemeStarRatingsProps extends ProductComponentsProps {
|
|
17
|
+
style: JudgemeStarRatingsStyle
|
|
18
|
+
options: JudgemeStarRatingsOptions
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function JudgemeStarRatings({
|
|
22
|
+
productData,
|
|
23
|
+
style,
|
|
24
|
+
options,
|
|
25
|
+
}: JudgemeStarRatingsProps) {
|
|
26
|
+
const judgemeBadge = useAppStore(
|
|
27
|
+
(s) => s.metafield.data.product[productData.handle]?.judgeme?.badge
|
|
28
|
+
)
|
|
29
|
+
const badgeValue = judgemeBadge?.value
|
|
30
|
+
if (!judgemeBadge) return null
|
|
31
|
+
if (!badgeValue) return null
|
|
32
|
+
const { averageRating, numberOfReviews } = parseRatingsAndReviews(badgeValue)
|
|
33
|
+
if (!averageRating || !numberOfReviews) return null
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<View style={style.root}>
|
|
37
|
+
{!!(averageRating && Number(averageRating)) && (
|
|
38
|
+
<StarRating
|
|
39
|
+
ratings={averageRating}
|
|
40
|
+
views={numberOfReviews}
|
|
41
|
+
style={{
|
|
42
|
+
root: style.root,
|
|
43
|
+
ratingCount: style.text,
|
|
44
|
+
icon: style.star,
|
|
45
|
+
container: style?.container,
|
|
46
|
+
}}
|
|
47
|
+
handle={productData?.handle}
|
|
48
|
+
options={options}
|
|
49
|
+
/>
|
|
50
|
+
)}
|
|
51
|
+
</View>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// const htmlString = `<div style='display:none' class='jdgm-prev-badge' data-average-rating='4.64' data-number-of-reviews='208' data-number-of-questions='0'> <span class='jdgm-prev-badge__stars' data-score='4.64' tabindex='0' aria-label='4.64 stars' role='button'> <span class='jdgm-star jdgm--on'></span><span class='jdgm-star jdgm--on'></span><span class='jdgm-star jdgm--on'></span><span class='jdgm-star jdgm--on'></span><span class='jdgm-star jdgm--half'></span> </span> <span class='jdgm-prev-badge__text'> 208 reviews </span> </div>`
|
|
56
|
+
export function parseRatingsAndReviews(htmlString: string): {
|
|
57
|
+
averageRating: number | null
|
|
58
|
+
numberOfReviews: number | null
|
|
59
|
+
} {
|
|
60
|
+
const averageRatingRegex = /data-average-rating=['"]([\d.]+)['"]/
|
|
61
|
+
const numberOfReviewsRegex = /data-number-of-reviews=['"](\d+)['"]/
|
|
62
|
+
|
|
63
|
+
const averageRatingMatch = htmlString.match(averageRatingRegex)
|
|
64
|
+
const numberOfReviewsMatch = htmlString.match(numberOfReviewsRegex)
|
|
65
|
+
|
|
66
|
+
const averageRating = averageRatingMatch
|
|
67
|
+
? parseFloat(averageRatingMatch[1])
|
|
68
|
+
: null
|
|
69
|
+
const numberOfReviews = numberOfReviewsMatch
|
|
70
|
+
? parseInt(numberOfReviewsMatch[1], 10)
|
|
71
|
+
: null
|
|
72
|
+
|
|
73
|
+
return { averageRating, numberOfReviews }
|
|
74
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { BaseIconStyle, BaseStyle, BaseTextStyle } from '@gauntlet/types'
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { ProductComponentsProps } from '@gauntlet/components/molecules/product-components'
|
|
4
|
+
import { View } from 'react-native'
|
|
5
|
+
import { useAppStore } from '@gauntlet/state'
|
|
6
|
+
import { parseRatingsAndReviews } from './judgeme-star-ratings'
|
|
7
|
+
import { Text } from '@gauntlet/components/atoms/text'
|
|
8
|
+
|
|
9
|
+
interface RatingsCountStyle {
|
|
10
|
+
root: BaseStyle
|
|
11
|
+
text: BaseTextStyle
|
|
12
|
+
star: BaseIconStyle
|
|
13
|
+
container?: BaseStyle
|
|
14
|
+
}
|
|
15
|
+
interface RatingsCountOptions {
|
|
16
|
+
template?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface RatingsCountProps extends ProductComponentsProps {
|
|
20
|
+
style: RatingsCountStyle
|
|
21
|
+
options: RatingsCountOptions
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function RatingsCount({
|
|
25
|
+
productData,
|
|
26
|
+
style,
|
|
27
|
+
options,
|
|
28
|
+
}: RatingsCountProps) {
|
|
29
|
+
const judgemeBadge = useAppStore(
|
|
30
|
+
(s) => s.metafield.data.product[productData.handle]?.judgeme?.badge
|
|
31
|
+
)
|
|
32
|
+
const badgeValue = judgemeBadge?.value
|
|
33
|
+
if (!judgemeBadge) return null
|
|
34
|
+
if (!badgeValue) return null
|
|
35
|
+
const { numberOfReviews } = parseRatingsAndReviews(badgeValue)
|
|
36
|
+
if (!numberOfReviews) return null
|
|
37
|
+
if (Number(numberOfReviews) === 0) return null
|
|
38
|
+
const template = options.template ?? '{{count}}'
|
|
39
|
+
const displayText = template.replace('{{count}}', String(numberOfReviews))
|
|
40
|
+
return (
|
|
41
|
+
<View style={style.root}>
|
|
42
|
+
<Text style={style.text}>{displayText}</Text>
|
|
43
|
+
</View>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|