@anker-in/campaign-ui 0.5.0-beta.4 → 0.5.0-beta.6
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/dist/cjs/components/credits/context/hooks/useCountries.js +1 -1
- package/dist/cjs/components/credits/context/hooks/useCountries.js.map +3 -3
- package/dist/cjs/components/memberPopup/MemberPopup.js +1 -1
- package/dist/cjs/components/memberPopup/MemberPopup.js.map +3 -3
- package/dist/cjs/components/memberPopup/panels/ActivitiesSubscribePanel.d.ts +10 -0
- package/dist/cjs/components/memberPopup/panels/ActivitiesSubscribePanel.js +2 -0
- package/dist/cjs/components/memberPopup/panels/ActivitiesSubscribePanel.js.map +7 -0
- package/dist/cjs/components/memberPopup/panels/ConsumeCredits/RedeemCouponPanel.d.ts +2 -1
- package/dist/cjs/components/memberPopup/panels/ConsumeCredits/RedeemCouponPanel.js +1 -1
- package/dist/cjs/components/memberPopup/panels/ConsumeCredits/RedeemCouponPanel.js.map +3 -3
- package/dist/cjs/components/memberPopup/panels/ConsumeCredits/index.js +1 -1
- package/dist/cjs/components/memberPopup/panels/ConsumeCredits/index.js.map +3 -3
- package/dist/cjs/components/memberPopup/panels/EarnCredits/ActivitiesSubscribePanel.d.ts +11 -0
- package/dist/cjs/components/memberPopup/panels/EarnCredits/ActivitiesSubscribePanel.js +2 -0
- package/dist/cjs/components/memberPopup/panels/EarnCredits/ActivitiesSubscribePanel.js.map +7 -0
- package/dist/cjs/components/memberPopup/panels/EarnCredits/SubscribeModal.js +1 -1
- package/dist/cjs/components/memberPopup/panels/EarnCredits/SubscribeModal.js.map +2 -2
- package/dist/cjs/components/memberPopup/panels/EarnCredits/index.js +1 -1
- package/dist/cjs/components/memberPopup/panels/EarnCredits/index.js.map +2 -2
- package/dist/cjs/components/memberPopup/panels/Referral/index.js +1 -1
- package/dist/cjs/components/memberPopup/panels/Referral/index.js.map +2 -2
- package/dist/cjs/components/memberPopup/type.d.ts +14 -2
- package/dist/cjs/components/memberPopup/type.js +1 -1
- package/dist/cjs/components/memberPopup/type.js.map +2 -2
- package/dist/esm/components/credits/context/hooks/useCountries.js +1 -1
- package/dist/esm/components/credits/context/hooks/useCountries.js.map +3 -3
- package/dist/esm/components/memberPopup/MemberPopup.js +1 -1
- package/dist/esm/components/memberPopup/MemberPopup.js.map +3 -3
- package/dist/esm/components/memberPopup/panels/ActivitiesSubscribePanel.d.ts +10 -0
- package/dist/esm/components/memberPopup/panels/ActivitiesSubscribePanel.js +2 -0
- package/dist/esm/components/memberPopup/panels/ActivitiesSubscribePanel.js.map +7 -0
- package/dist/esm/components/memberPopup/panels/ConsumeCredits/RedeemCouponPanel.d.ts +2 -1
- package/dist/esm/components/memberPopup/panels/ConsumeCredits/RedeemCouponPanel.js +1 -1
- package/dist/esm/components/memberPopup/panels/ConsumeCredits/RedeemCouponPanel.js.map +3 -3
- package/dist/esm/components/memberPopup/panels/ConsumeCredits/index.js +1 -1
- package/dist/esm/components/memberPopup/panels/ConsumeCredits/index.js.map +3 -3
- package/dist/esm/components/memberPopup/panels/EarnCredits/ActivitiesSubscribePanel.d.ts +11 -0
- package/dist/esm/components/memberPopup/panels/EarnCredits/ActivitiesSubscribePanel.js +2 -0
- package/dist/esm/components/memberPopup/panels/EarnCredits/ActivitiesSubscribePanel.js.map +7 -0
- package/dist/esm/components/memberPopup/panels/EarnCredits/SubscribeModal.js +1 -1
- package/dist/esm/components/memberPopup/panels/EarnCredits/SubscribeModal.js.map +2 -2
- package/dist/esm/components/memberPopup/panels/EarnCredits/index.js +1 -1
- package/dist/esm/components/memberPopup/panels/EarnCredits/index.js.map +2 -2
- package/dist/esm/components/memberPopup/panels/Referral/index.js +1 -1
- package/dist/esm/components/memberPopup/panels/Referral/index.js.map +2 -2
- package/dist/esm/components/memberPopup/type.d.ts +14 -2
- package/dist/esm/components/memberPopup/type.js +1 -1
- package/dist/esm/components/memberPopup/type.js.map +2 -2
- package/package.json +1 -1
- package/src/components/credits/context/hooks/useCountries.ts +1 -2
- package/src/components/memberPopup/MemberPopup.tsx +18 -13
- package/src/components/memberPopup/panels/ActivitiesSubscribePanel.tsx +194 -0
- package/src/components/memberPopup/panels/ConsumeCredits/RedeemCouponPanel.tsx +61 -28
- package/src/components/memberPopup/panels/ConsumeCredits/index.tsx +40 -14
- package/src/components/memberPopup/panels/EarnCredits/index.tsx +94 -64
- package/src/components/memberPopup/panels/Referral/index.tsx +1 -1
- package/src/components/memberPopup/type.ts +10 -1
- package/src/components/memberPopup/panels/ConsumeCredits/RedeemCouponModal.tsx +0 -150
- package/src/components/memberPopup/panels/EarnCredits/SubscribeModal.tsx +0 -144
- package/src/components/memberPopup/panels/EarnCredits/SubscribePanel.tsx +0 -147
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
2
|
+
import { Button, Checkbox, Picture, Text } from '@anker-in/headless-ui'
|
|
3
|
+
import { classNames as cn, useHeadlessContext } from '@anker-in/lib'
|
|
4
|
+
import Cookies from 'js-cookie'
|
|
5
|
+
import { parse } from 'query-string'
|
|
6
|
+
import { emailValidate } from '../../credits/context/utils'
|
|
7
|
+
import { useMemberPopupContext } from '../context/provider'
|
|
8
|
+
import type { SubscribeModalCopy } from '../type'
|
|
9
|
+
import { ROUNDED_BRANDS } from '../../../constants'
|
|
10
|
+
|
|
11
|
+
type Props = {
|
|
12
|
+
onSuccess?: () => void
|
|
13
|
+
copy: SubscribeModalCopy
|
|
14
|
+
email?: string
|
|
15
|
+
hideForm: boolean
|
|
16
|
+
onHideForm: () => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getAdCookie() {
|
|
20
|
+
if (typeof window === 'undefined') return { register_source: '' }
|
|
21
|
+
const { search, href } = window.location
|
|
22
|
+
const query = parse(search)
|
|
23
|
+
return {
|
|
24
|
+
fbuy_ref_code: Cookies.get('fbuy_ref_code'),
|
|
25
|
+
ref: Cookies.get('ref_ads'),
|
|
26
|
+
inviter_code: query.ic ?? query.inviter_code ?? Cookies.get('inviter_code'),
|
|
27
|
+
register_source: query.redirect ?? Cookies.get('reg_source') ?? href,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function getRecaptchaToken(sitekey: string): Promise<string> {
|
|
32
|
+
if (typeof window === 'undefined' || !window?.grecaptcha?.execute) return ''
|
|
33
|
+
if (!sitekey) return ''
|
|
34
|
+
try {
|
|
35
|
+
return await window.grecaptcha.execute(sitekey, { action: 'activity' })
|
|
36
|
+
} catch {
|
|
37
|
+
return ''
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default function ActivitiesSubscribeModal({
|
|
42
|
+
onSuccess,
|
|
43
|
+
copy,
|
|
44
|
+
email: initialEmail,
|
|
45
|
+
hideForm,
|
|
46
|
+
onHideForm,
|
|
47
|
+
}: Props) {
|
|
48
|
+
const { alpcConfig } = useMemberPopupContext()
|
|
49
|
+
const { locale } = alpcConfig
|
|
50
|
+
const { brand: headlessBrand, storeDomain, recaptchaSitekey } = useHeadlessContext()
|
|
51
|
+
const rounded = ROUNDED_BRANDS.includes(headlessBrand)
|
|
52
|
+
const [policy, setPolicy] = useState(false)
|
|
53
|
+
const [email, setEmail] = useState(initialEmail ?? '')
|
|
54
|
+
const [errorMessage, setErrorMessage] = useState('')
|
|
55
|
+
const [successMessage, setSuccessMessage] = useState('')
|
|
56
|
+
const [loading, setLoading] = useState(false)
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (initialEmail) setEmail(initialEmail)
|
|
60
|
+
}, [initialEmail])
|
|
61
|
+
|
|
62
|
+
const handleSubmit = useCallback(async () => {
|
|
63
|
+
setErrorMessage('')
|
|
64
|
+
if (!email) return setErrorMessage('Please fill in your email')
|
|
65
|
+
if (!emailValidate(email)) return setErrorMessage('Invalid email address')
|
|
66
|
+
if (!policy) return setErrorMessage('Please agree to the policy')
|
|
67
|
+
|
|
68
|
+
setLoading(true)
|
|
69
|
+
const { register_source } = getAdCookie()
|
|
70
|
+
const recaptchaToken = await getRecaptchaToken(recaptchaSitekey || '')
|
|
71
|
+
try {
|
|
72
|
+
const response = await fetch('/api/multipass/rainbowbridge/activities', {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: {
|
|
75
|
+
'Content-Type': 'application/json',
|
|
76
|
+
'current-language': locale,
|
|
77
|
+
'X-Recaptcha-Token': recaptchaToken,
|
|
78
|
+
},
|
|
79
|
+
body: JSON.stringify({
|
|
80
|
+
email,
|
|
81
|
+
register_source,
|
|
82
|
+
shopify_domain: storeDomain,
|
|
83
|
+
single_brand_subscribe: copy.single_brand_subscribe ?? true,
|
|
84
|
+
genre: copy.genre ?? copy.dealsType ?? '',
|
|
85
|
+
sub_brand_type: copy.sub_brand_type ?? '',
|
|
86
|
+
}),
|
|
87
|
+
})
|
|
88
|
+
const result = await response.json()
|
|
89
|
+
if (!result.errors) {
|
|
90
|
+
setSuccessMessage(copy.successTips ?? 'Subscribed successfully!')
|
|
91
|
+
onSuccess?.()
|
|
92
|
+
setTimeout(() => {
|
|
93
|
+
onHideForm()
|
|
94
|
+
setSuccessMessage('')
|
|
95
|
+
}, 3000)
|
|
96
|
+
} else {
|
|
97
|
+
setErrorMessage(result.errors ?? 'Error occurred')
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
setErrorMessage('Network error')
|
|
101
|
+
} finally {
|
|
102
|
+
setLoading(false)
|
|
103
|
+
}
|
|
104
|
+
}, [email, policy, locale, storeDomain, copy, onSuccess, onHideForm])
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<div className="flex flex-col gap-[16px] text-center">
|
|
108
|
+
{!hideForm && (
|
|
109
|
+
<>
|
|
110
|
+
<Text className="text-[22px] font-bold" html={copy.title} />
|
|
111
|
+
{copy.desc && <Text className="text-left text-[16px] font-semibold text-[#1f2021]" html={copy.desc} />}
|
|
112
|
+
|
|
113
|
+
<input
|
|
114
|
+
className={cn(
|
|
115
|
+
'h-[46px] w-full border px-[14px] text-[16px] outline-none placeholder:text-[#999]',
|
|
116
|
+
errorMessage ? 'border-[#f84d4f]' : 'border-[#d8d8d8]',
|
|
117
|
+
rounded ? 'rounded-[4px]' : 'rounded-none'
|
|
118
|
+
)}
|
|
119
|
+
placeholder={copy.placeholder}
|
|
120
|
+
value={email}
|
|
121
|
+
onChange={e => {
|
|
122
|
+
setErrorMessage('')
|
|
123
|
+
setEmail(e.target.value.trim())
|
|
124
|
+
}}
|
|
125
|
+
/>
|
|
126
|
+
|
|
127
|
+
<div className="flex w-full items-start gap-[8px]">
|
|
128
|
+
<Checkbox checked={policy} onCheckedChange={() => setPolicy(v => !v)} className="border-[#1d1d1f]" />
|
|
129
|
+
<label
|
|
130
|
+
className="text-left text-[14px] font-semibold text-[#777] [&_a]:underline"
|
|
131
|
+
dangerouslySetInnerHTML={{ __html: copy.policy ?? '' }}
|
|
132
|
+
/>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<button
|
|
136
|
+
className={cn(
|
|
137
|
+
'w-full cursor-pointer bg-[#080a0f] py-[14px] text-[14px] font-[700] text-white disabled:opacity-60',
|
|
138
|
+
rounded ? 'rounded-[4px]' : 'rounded-none'
|
|
139
|
+
)}
|
|
140
|
+
disabled={loading}
|
|
141
|
+
onClick={handleSubmit}
|
|
142
|
+
>
|
|
143
|
+
{copy.buttonLabel ?? 'Subscribe'}
|
|
144
|
+
</button>
|
|
145
|
+
|
|
146
|
+
{errorMessage && (
|
|
147
|
+
<div className="text-left text-[16px] font-semibold text-[#f84d4f]">
|
|
148
|
+
<Text html={errorMessage} />
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
<div className="text-left text-[16px] font-semibold text-[#52c41a]">
|
|
152
|
+
<Text html={successMessage} />
|
|
153
|
+
</div>
|
|
154
|
+
</>
|
|
155
|
+
)}
|
|
156
|
+
|
|
157
|
+
{copy.promo && (
|
|
158
|
+
<div className="flex flex-col text-left">
|
|
159
|
+
<div className="flex flex-col gap-[8px]">
|
|
160
|
+
{copy.promo?.title && (
|
|
161
|
+
<h3 className="text-[20px] font-[700] leading-[1.2] text-[#080a0f]">{copy.promo?.title}</h3>
|
|
162
|
+
)}
|
|
163
|
+
{copy.promo.description && (
|
|
164
|
+
<p className="text-[14px] leading-[1.5] text-[#1e2024]">{copy.promo.description}</p>
|
|
165
|
+
)}
|
|
166
|
+
{copy.promo.learnMoreLabel && copy.promo.learnMoreUrl && (
|
|
167
|
+
<a
|
|
168
|
+
href={copy.promo.learnMoreUrl}
|
|
169
|
+
target="_blank"
|
|
170
|
+
rel="noopener noreferrer"
|
|
171
|
+
className="flex items-center gap-[4px] text-[14px] font-[700] text-[#080a0f] underline"
|
|
172
|
+
>
|
|
173
|
+
{copy.promo.learnMoreLabel}
|
|
174
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
175
|
+
<path
|
|
176
|
+
d="M5.52827 3.52876C5.78862 3.26841 6.21063 3.26841 6.47098 3.52876L10.471 7.52876C10.7313 7.78911 10.7313 8.21112 10.471 8.47147L6.47098 12.4715C6.21063 12.7318 5.78862 12.7318 5.52827 12.4715C5.26792 12.2111 5.26792 11.7891 5.52827 11.5288L9.05692 8.00011L5.52827 4.47147C5.26792 4.21112 5.26792 3.78911 5.52827 3.52876Z"
|
|
177
|
+
fill="#080A0F"
|
|
178
|
+
/>
|
|
179
|
+
</svg>
|
|
180
|
+
</a>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
{copy.promo.image?.url && (
|
|
184
|
+
<Picture
|
|
185
|
+
source={copy.promo.image.url}
|
|
186
|
+
alt={copy.promo.image.alt ?? ''}
|
|
187
|
+
className={cn('h-[100px] mt-[8px] w-full object-cover', rounded ? 'rounded-[8px]' : 'rounded-none')}
|
|
188
|
+
/>
|
|
189
|
+
)}
|
|
190
|
+
</div>
|
|
191
|
+
)}
|
|
192
|
+
</div>
|
|
193
|
+
)
|
|
194
|
+
}
|
|
@@ -14,6 +14,7 @@ type Props = {
|
|
|
14
14
|
itemImage?: { url: string; alt?: string }
|
|
15
15
|
itemValue?: string
|
|
16
16
|
itemOff?: string
|
|
17
|
+
itemRules?: string[]
|
|
17
18
|
redeemId: number
|
|
18
19
|
userId?: string
|
|
19
20
|
copy: RedeemCouponModalCopy
|
|
@@ -29,6 +30,7 @@ export default function RedeemCouponPanel({
|
|
|
29
30
|
itemImage,
|
|
30
31
|
itemValue,
|
|
31
32
|
itemOff,
|
|
33
|
+
itemRules,
|
|
32
34
|
redeemId,
|
|
33
35
|
userId,
|
|
34
36
|
copy,
|
|
@@ -80,38 +82,45 @@ export default function RedeemCouponPanel({
|
|
|
80
82
|
setDisabled(false)
|
|
81
83
|
}, [redeemId])
|
|
82
84
|
|
|
85
|
+
const rulesSection = itemRules && itemRules.length > 0 && (
|
|
86
|
+
<div className="w-full text-[16px] font-bold leading-[1.4] text-[#6D6D6F] md:mt-[12px] md:text-[14px]">
|
|
87
|
+
<ul className="ml-4 grid grid-flow-row gap-[4px]">
|
|
88
|
+
{itemRules?.map((text, index) => (
|
|
89
|
+
<li className="list-disc" key={index}>
|
|
90
|
+
<Text html={text} className="font-bold" />
|
|
91
|
+
</li>
|
|
92
|
+
))}
|
|
93
|
+
</ul>
|
|
94
|
+
</div>
|
|
95
|
+
)
|
|
96
|
+
|
|
83
97
|
return (
|
|
84
|
-
<div className="flex flex-col
|
|
98
|
+
<div className="flex flex-col">
|
|
85
99
|
{itemImage?.url && (
|
|
86
|
-
<div className=
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
100
|
+
<div className="relative w-full px-[24px]">
|
|
101
|
+
<div className={cn('relative mx-auto overflow-hidden', rounded ? 'rounded-[8px]' : 'rounded-none')}>
|
|
102
|
+
<img src={itemImage.url} alt={itemImage.alt ?? ''} className="mx-auto h-[140px] w-full object-cover" />
|
|
103
|
+
{itemValue && (
|
|
104
|
+
<div className="absolute bottom-[20px] left-[20px] text-[32px] font-[800] leading-none text-white">
|
|
105
|
+
{itemValue}
|
|
106
|
+
{itemOff ? ` ${itemOff}` : ''}
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
94
110
|
</div>
|
|
95
111
|
)}
|
|
96
112
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
<Text className="block text-[20px] font-[700]" html={copy.confirmTitle} />
|
|
104
|
-
<Button
|
|
105
|
-
className={cn('w-full py-[12px] text-[14px] font-[700] text-white disabled:opacity-60 bg-[#080a0f]', rounded ? 'rounded-[4px]' : 'rounded-none')}
|
|
106
|
-
disabled={disabled}
|
|
107
|
-
loading={loading}
|
|
108
|
-
onClick={handleConfirm}
|
|
109
|
-
>
|
|
110
|
-
{copy.confirmButton}
|
|
111
|
-
</Button>
|
|
113
|
+
{!couponCode && (
|
|
114
|
+
<div className="flex flex-col gap-[16px] px-[24px] py-[20px]">
|
|
115
|
+
<p className="text-center text-[24px] font-[700] text-[#1d1d1f]">
|
|
116
|
+
{itemCredits} {pointUnit}
|
|
117
|
+
</p>
|
|
118
|
+
{rulesSection}
|
|
112
119
|
</div>
|
|
113
|
-
)
|
|
114
|
-
|
|
120
|
+
)}
|
|
121
|
+
|
|
122
|
+
{couponCode && (
|
|
123
|
+
<div className="flex flex-col gap-[8px] text-center">
|
|
115
124
|
<div className="flex items-center justify-center gap-[10px]">
|
|
116
125
|
<span className="text-[24px] font-[700]">{couponCode}</span>
|
|
117
126
|
<button
|
|
@@ -125,7 +134,10 @@ export default function RedeemCouponPanel({
|
|
|
125
134
|
<Text className="text-[20px] font-[700]" html={copy.successTitle} />
|
|
126
135
|
<Text className="text-[14px] font-[700] text-[#6d6d6f]" html={copy.successDesc} />
|
|
127
136
|
<Button
|
|
128
|
-
className={cn(
|
|
137
|
+
className={cn(
|
|
138
|
+
'w-full py-[12px] text-[14px] font-[700] text-white bg-[#080a0f]',
|
|
139
|
+
rounded ? 'rounded-[4px]' : 'rounded-none'
|
|
140
|
+
)}
|
|
129
141
|
onClick={() => {
|
|
130
142
|
if (copy.successButtonUrl) window.location.href = copy.successButtonUrl
|
|
131
143
|
onClose()
|
|
@@ -136,7 +148,28 @@ export default function RedeemCouponPanel({
|
|
|
136
148
|
</div>
|
|
137
149
|
)}
|
|
138
150
|
|
|
139
|
-
{
|
|
151
|
+
{!couponCode && (
|
|
152
|
+
<div className="sticky bottom-0 bg-white border-t border-[#e5e5e5] pt-[10px]">
|
|
153
|
+
<div className="flex flex-col gap-[8px] text-center">
|
|
154
|
+
<Text className="block text-[16px] font-[700] leading-[1.3]" html={copy.confirmTitle} />
|
|
155
|
+
<Button
|
|
156
|
+
className={cn(
|
|
157
|
+
'w-full py-[12px] text-[14px] font-[700] text-white disabled:opacity-60 bg-[#080a0f]',
|
|
158
|
+
rounded ? 'rounded-[4px]' : 'rounded-none'
|
|
159
|
+
)}
|
|
160
|
+
disabled={disabled}
|
|
161
|
+
loading={loading}
|
|
162
|
+
onClick={handleConfirm}
|
|
163
|
+
>
|
|
164
|
+
{copy.confirmButton}
|
|
165
|
+
</Button>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
{errorInfo && (
|
|
169
|
+
<Text className="mt-[8px] text-center text-[14px] font-[700] text-[#ff0000]" html={errorInfo} />
|
|
170
|
+
)}
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
140
173
|
</div>
|
|
141
174
|
)
|
|
142
175
|
}
|
|
@@ -56,10 +56,14 @@ function ConsumeCredits({ copy, onClose }: Props) {
|
|
|
56
56
|
|
|
57
57
|
let product
|
|
58
58
|
let variant
|
|
59
|
-
if (
|
|
59
|
+
if (
|
|
60
|
+
(item.type === ConsumeCreditsItemType.RedeemAndBuy ||
|
|
61
|
+
item.type === ConsumeCreditsItemType.Product) &&
|
|
62
|
+
item.product
|
|
63
|
+
) {
|
|
60
64
|
product = products?.find((p: any) => p.handle === item.product!.handle)
|
|
61
65
|
variant = product?.variants?.find((v: any) => v.sku === item.product!.sku) ?? product?.variants?.[0]
|
|
62
|
-
if (!product || !variant) return null
|
|
66
|
+
if (item.type === ConsumeCreditsItemType.RedeemAndBuy && (!product || !variant)) return null
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
return { ...item, alpcData, insufficient, soldOut, product, variant }
|
|
@@ -74,6 +78,12 @@ function ConsumeCredits({ copy, onClose }: Props) {
|
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
function isDisabled(item: (typeof list)[number]) {
|
|
81
|
+
if (
|
|
82
|
+
item.type === ConsumeCreditsItemType.GiftCard ||
|
|
83
|
+
item.type === ConsumeCreditsItemType.Product
|
|
84
|
+
) {
|
|
85
|
+
return item.insufficient
|
|
86
|
+
}
|
|
77
87
|
return item.soldOut || item.insufficient
|
|
78
88
|
}
|
|
79
89
|
|
|
@@ -82,6 +92,19 @@ function ConsumeCredits({ copy, onClose }: Props) {
|
|
|
82
92
|
openSignUpPopup()
|
|
83
93
|
return
|
|
84
94
|
}
|
|
95
|
+
if (!profile?.activated) {
|
|
96
|
+
pushView({ type: MemberPopupPanelType.Activate })
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
if (
|
|
100
|
+
item.type === ConsumeCreditsItemType.GiftCard ||
|
|
101
|
+
item.type === ConsumeCreditsItemType.Product
|
|
102
|
+
) {
|
|
103
|
+
if (copy.creditsPage?.url) {
|
|
104
|
+
window.open(copy.creditsPage.url, '_blank', 'noopener,noreferrer')
|
|
105
|
+
}
|
|
106
|
+
return
|
|
107
|
+
}
|
|
85
108
|
if (item.type === ConsumeCreditsItemType.Coupon) {
|
|
86
109
|
pushView({
|
|
87
110
|
type: MemberPopupPanelType.RedeemCoupon,
|
|
@@ -93,6 +116,7 @@ function ConsumeCredits({ copy, onClose }: Props) {
|
|
|
93
116
|
: item.image,
|
|
94
117
|
itemValue: item.value,
|
|
95
118
|
itemOff: copy.off,
|
|
119
|
+
itemRules: item.rules,
|
|
96
120
|
redeemId: Number(item.redeemId),
|
|
97
121
|
userId: profile?.user_id,
|
|
98
122
|
alpcConfig,
|
|
@@ -136,17 +160,18 @@ function ConsumeCredits({ copy, onClose }: Props) {
|
|
|
136
160
|
</div>
|
|
137
161
|
) : (
|
|
138
162
|
<div className="flex flex-col">
|
|
139
|
-
{list.map((item, idx) =>
|
|
163
|
+
{list.map((item, idx) => {
|
|
164
|
+
const hasProduct = !!item.product
|
|
165
|
+
return (
|
|
140
166
|
<div key={idx} className="relative py-[16px]">
|
|
141
167
|
{idx > 0 && <div className="absolute inset-x-0 top-0 h-px bg-[#dadce0]" />}
|
|
142
168
|
<div className="flex items-center gap-[12px]">
|
|
143
169
|
{(() => {
|
|
144
|
-
const isRedeemAndBuy = item.type === ConsumeCreditsItemType.RedeemAndBuy
|
|
145
170
|
const mappedImgUrl = copy.imageMapping?.[item.type]?.url
|
|
146
|
-
const imgUrl =
|
|
171
|
+
const imgUrl = hasProduct
|
|
147
172
|
? (item.variant?.image?.url ?? item.product?.images?.[0]?.url ?? item.image?.url)
|
|
148
173
|
: (mappedImgUrl ?? item.image?.url)
|
|
149
|
-
const imgAlt =
|
|
174
|
+
const imgAlt = hasProduct
|
|
150
175
|
? (item.variant?.image?.altText ?? item.product?.images?.[0]?.alt ?? item.image?.alt ?? '')
|
|
151
176
|
: (item.image?.alt ?? '')
|
|
152
177
|
if (!imgUrl) return null
|
|
@@ -156,9 +181,9 @@ function ConsumeCredits({ copy, onClose }: Props) {
|
|
|
156
181
|
source={imgUrl}
|
|
157
182
|
alt={imgAlt}
|
|
158
183
|
className="flex size-full items-center justify-center"
|
|
159
|
-
imgClassName={
|
|
184
|
+
imgClassName={hasProduct ? 'object-cover' : 'h-full w-auto object-contain'}
|
|
160
185
|
/>
|
|
161
|
-
{!
|
|
186
|
+
{!hasProduct && item.value && (
|
|
162
187
|
<div className="absolute bottom-[20px] left-[4px] text-[14px] font-[800] leading-none text-white">
|
|
163
188
|
{item.value} {copy.off}
|
|
164
189
|
</div>
|
|
@@ -168,12 +193,12 @@ function ConsumeCredits({ copy, onClose }: Props) {
|
|
|
168
193
|
})()}
|
|
169
194
|
<div className="min-w-0 flex-1">
|
|
170
195
|
<div className="text-[16px] font-[700] text-[#1e2024] line-clamp-2">
|
|
171
|
-
{
|
|
196
|
+
{hasProduct && item.product?.title
|
|
172
197
|
? item.product.title
|
|
173
198
|
: item.title}
|
|
174
199
|
</div>
|
|
175
200
|
<div className="mt-[4px] text-[14px] text-[#666]">
|
|
176
|
-
{
|
|
201
|
+
{hasProduct && item.price != null
|
|
177
202
|
? `${numberFormat(item.alpcData.consume_credits)} ${copy.pointUnit ?? ''} + $${Number(item.price).toFixed(2)}`
|
|
178
203
|
: `${numberFormat(item.alpcData.consume_credits)} ${copy.pointUnit ?? ''}`}
|
|
179
204
|
</div>
|
|
@@ -188,17 +213,18 @@ function ConsumeCredits({ copy, onClose }: Props) {
|
|
|
188
213
|
{getButtonLabel(item)}
|
|
189
214
|
</Button>
|
|
190
215
|
{item.insufficient && (
|
|
191
|
-
<div className="pointer-events-none absolute
|
|
192
|
-
<div className="relative bg-[#6D6D6F] p-[16px] text-[14px] font-[700] text-white shadow
|
|
216
|
+
<div className="pointer-events-none absolute right-0 top-full z-[100] mt-[10px] hidden w-[200px] group-hover:block">
|
|
217
|
+
<div className="relative bg-[#6D6D6F] p-[16px] text-[14px] font-[700] text-white shadow">
|
|
193
218
|
<p className="text-pretty tracking-[-0.02em] text-[14px]">{copy.insufficientCredits}</p>
|
|
194
|
-
<div className="absolute -top-[8px]
|
|
219
|
+
<div className="absolute -top-[8px] right-[20px] size-[16px] origin-top-left rotate-45 rounded-[2px] bg-[#6D6D6F]" />
|
|
195
220
|
</div>
|
|
196
221
|
</div>
|
|
197
222
|
)}
|
|
198
223
|
</div>
|
|
199
224
|
</div>
|
|
200
225
|
</div>
|
|
201
|
-
|
|
226
|
+
)
|
|
227
|
+
})}
|
|
202
228
|
</div>
|
|
203
229
|
)}
|
|
204
230
|
|
|
@@ -48,66 +48,90 @@ function EarnCredits({
|
|
|
48
48
|
const { brand } = useHeadlessContext()
|
|
49
49
|
const rounded = ROUNDED_BRANDS.includes(brand)
|
|
50
50
|
|
|
51
|
+
console.log('creditInfo', creditInfo)
|
|
52
|
+
|
|
51
53
|
const isLogin = !!profile
|
|
52
54
|
|
|
53
55
|
const isSubscribed = useSubscribed({
|
|
54
56
|
userId: profile?.user_id,
|
|
55
|
-
brand:
|
|
57
|
+
brand: brand,
|
|
56
58
|
email: profile?.email,
|
|
57
59
|
locale,
|
|
58
60
|
apiBaseUrl: alpcConfig.apiBaseUrl,
|
|
59
61
|
})
|
|
60
62
|
|
|
61
63
|
const profileCompleted = useMemo(() => {
|
|
62
|
-
return
|
|
64
|
+
return (
|
|
65
|
+
isProfileCompleteSuccess ||
|
|
66
|
+
(!!(profile?.first_name || profile?.last_name) && !!profile?.phone_number && !!profile?.dob)
|
|
67
|
+
)
|
|
63
68
|
}, [profile, isProfileCompleteSuccess])
|
|
64
69
|
|
|
65
|
-
const taskStates: Record<EarnCreditsTaskType, { finished: boolean; onClick?: () => void; link?: string }> = useMemo(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
70
|
+
const taskStates: Record<EarnCreditsTaskType, { finished: boolean; onClick?: () => void; link?: string }> = useMemo(
|
|
71
|
+
() => ({
|
|
72
|
+
[EarnCreditsTaskType.Activate]: {
|
|
73
|
+
finished: !!profile?.activated || isActivateSuccess,
|
|
74
|
+
onClick: isLogin
|
|
75
|
+
? () =>
|
|
76
|
+
pushView({
|
|
77
|
+
type: MemberPopupPanelType.Activate,
|
|
78
|
+
props: { onSuccess: onActivateSuccess },
|
|
79
|
+
})
|
|
80
|
+
: openSignUpPopup,
|
|
81
|
+
},
|
|
82
|
+
[EarnCreditsTaskType.CompleteProfile]: {
|
|
83
|
+
finished: profileCompleted,
|
|
84
|
+
onClick: isLogin
|
|
85
|
+
? () =>
|
|
86
|
+
pushView({
|
|
87
|
+
type: MemberPopupPanelType.CompleteProfile,
|
|
88
|
+
props: { onSuccess: onProfileCompleteSuccess },
|
|
89
|
+
})
|
|
90
|
+
: openSignUpPopup,
|
|
91
|
+
},
|
|
92
|
+
[EarnCreditsTaskType.Subscribe]: {
|
|
93
|
+
finished: isSubscribed || isSubscribeSuccess,
|
|
94
|
+
onClick: isLogin
|
|
95
|
+
? () =>
|
|
96
|
+
pushView({
|
|
97
|
+
type: MemberPopupPanelType.SubscribeEmail,
|
|
98
|
+
props: { onSuccess: onSubscribeSuccess },
|
|
99
|
+
})
|
|
100
|
+
: openSignUpPopup,
|
|
101
|
+
},
|
|
102
|
+
[EarnCreditsTaskType.Shop]: {
|
|
103
|
+
finished: false,
|
|
104
|
+
link: copy.goShopLink,
|
|
105
|
+
onClick: isLogin ? undefined : openSignUpPopup,
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
108
|
+
[
|
|
109
|
+
profile,
|
|
110
|
+
isLogin,
|
|
111
|
+
isActivateSuccess,
|
|
112
|
+
isProfileCompleteSuccess,
|
|
113
|
+
profileCompleted,
|
|
114
|
+
isSubscribed,
|
|
115
|
+
isSubscribeSuccess,
|
|
116
|
+
openSignUpPopup,
|
|
117
|
+
pushView,
|
|
118
|
+
copy.goShopLink,
|
|
119
|
+
]
|
|
120
|
+
)
|
|
99
121
|
|
|
100
122
|
const list = useMemo(() => {
|
|
101
|
-
return copy.list
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
123
|
+
return copy.list
|
|
124
|
+
.map(item => {
|
|
125
|
+
const state = taskStates[item.id]
|
|
126
|
+
if (!state) return null
|
|
127
|
+
const buttonLabel = state.finished
|
|
128
|
+
? copy.completed
|
|
129
|
+
: isLogin
|
|
130
|
+
? getNotFinishedLabel(item.id, copy)
|
|
131
|
+
: copy.joinNow
|
|
132
|
+
return { ...item, ...state, buttonLabel }
|
|
133
|
+
})
|
|
134
|
+
.filter(Boolean) as ((typeof copy.list)[number] & {
|
|
111
135
|
finished: boolean
|
|
112
136
|
onClick?: () => void
|
|
113
137
|
link?: string
|
|
@@ -122,15 +146,18 @@ function EarnCredits({
|
|
|
122
146
|
{copy.title && <h2 className="text-[24px] font-[700] leading-[1.2] text-[#080a0f]">{copy.title}</h2>}
|
|
123
147
|
</div>
|
|
124
148
|
|
|
125
|
-
{isLogin &&
|
|
126
|
-
<div
|
|
149
|
+
{isLogin && (
|
|
150
|
+
<div
|
|
151
|
+
className={cn(
|
|
152
|
+
'flex items-center gap-[12px] bg-[#f5f6f7] p-[16px]',
|
|
153
|
+
rounded ? 'rounded-[8px]' : 'rounded-none'
|
|
154
|
+
)}
|
|
155
|
+
>
|
|
127
156
|
<div className="flex flex-1 flex-col gap-[4px]">
|
|
128
|
-
<div className="text-[14px] font-[700] text-[#1e2024]">
|
|
129
|
-
{copy.balanceLabel ?? 'AnkerCredits Balance'}
|
|
130
|
-
</div>
|
|
157
|
+
<div className="text-[14px] font-[700] text-[#1e2024]">{copy.balanceLabel ?? 'AnkerCredits Balance'}</div>
|
|
131
158
|
<div className="flex items-end gap-[4px]">
|
|
132
159
|
<span className="text-[24px] font-[700] leading-[1.2] text-[#1e2024]">
|
|
133
|
-
{numberFormat(creditInfo
|
|
160
|
+
{numberFormat(creditInfo?.available_credit ?? 0)}
|
|
134
161
|
</span>
|
|
135
162
|
<span className="text-[14px] font-[700] leading-[1.4] text-[#1e2024]">
|
|
136
163
|
{copy.creditUnit ?? 'ankercredits'}
|
|
@@ -139,12 +166,10 @@ function EarnCredits({
|
|
|
139
166
|
</div>
|
|
140
167
|
<div className="h-full w-px bg-[#dadce0]" />
|
|
141
168
|
<div className="flex flex-1 flex-col gap-[4px]">
|
|
142
|
-
<div className="text-[14px] font-[700] text-[#1e2024]">
|
|
143
|
-
{copy.comingSoonLabel ?? 'Coming Soon'}
|
|
144
|
-
</div>
|
|
169
|
+
<div className="text-[14px] font-[700] text-[#1e2024]">{copy.comingSoonLabel ?? 'Coming Soon'}</div>
|
|
145
170
|
<div className="flex items-end gap-[4px]">
|
|
146
171
|
<span className="text-[24px] font-[700] leading-[1.2] text-[#1e2024]">
|
|
147
|
-
{numberFormat(creditInfo
|
|
172
|
+
{numberFormat(creditInfo?.pending_credit ?? 0)}
|
|
148
173
|
</span>
|
|
149
174
|
<span className="text-[14px] font-[700] leading-[1.4] text-[#1e2024]">
|
|
150
175
|
{copy.creditUnit ?? 'ankercredits'}
|
|
@@ -156,9 +181,7 @@ function EarnCredits({
|
|
|
156
181
|
</div>
|
|
157
182
|
|
|
158
183
|
<div className="flex flex-col gap-[16px]">
|
|
159
|
-
{copy.subtitle &&
|
|
160
|
-
<p className="text-[16px] font-[700] text-[#1e2024]">{copy.subtitle}</p>
|
|
161
|
-
)}
|
|
184
|
+
{copy.subtitle && <p className="text-[16px] font-[700] text-[#1e2024]">{copy.subtitle}</p>}
|
|
162
185
|
|
|
163
186
|
<div className="flex flex-col">
|
|
164
187
|
{list.map((item, idx) => (
|
|
@@ -177,7 +200,10 @@ function EarnCredits({
|
|
|
177
200
|
href={item.link}
|
|
178
201
|
target="_blank"
|
|
179
202
|
rel="noopener noreferrer"
|
|
180
|
-
className={cn(
|
|
203
|
+
className={cn(
|
|
204
|
+
'cursor-pointer bg-[#080a0f] min-w-[118px] px-[20px] pb-[10px] pt-[11px] text-[14px] font-[700] leading-[1.2] text-white',
|
|
205
|
+
rounded ? 'rounded-[4px]' : 'rounded-none'
|
|
206
|
+
)}
|
|
181
207
|
>
|
|
182
208
|
{item.buttonLabel}
|
|
183
209
|
</a>
|
|
@@ -205,10 +231,14 @@ function EarnCredits({
|
|
|
205
231
|
|
|
206
232
|
function getNotFinishedLabel(id: EarnCreditsTaskType, copy: EarnCreditsCopy): string {
|
|
207
233
|
switch (id) {
|
|
208
|
-
case EarnCreditsTaskType.Activate:
|
|
209
|
-
|
|
210
|
-
case EarnCreditsTaskType.
|
|
211
|
-
|
|
234
|
+
case EarnCreditsTaskType.Activate:
|
|
235
|
+
return copy.activate
|
|
236
|
+
case EarnCreditsTaskType.CompleteProfile:
|
|
237
|
+
return copy.completeProfileLabel
|
|
238
|
+
case EarnCreditsTaskType.Subscribe:
|
|
239
|
+
return copy.subscribeLabel
|
|
240
|
+
case EarnCreditsTaskType.Shop:
|
|
241
|
+
return copy.shopNow
|
|
212
242
|
}
|
|
213
243
|
}
|
|
214
244
|
|