@anker-in/campaign-ui 0.5.22 → 0.5.23
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/README.md +21 -1
- package/dist/cjs/components/credits/modal/SubscribeModal.d.ts +1 -0
- package/dist/cjs/components/credits/modal/SubscribeModal.js +1 -1
- package/dist/cjs/components/credits/modal/SubscribeModal.js.map +3 -3
- package/dist/esm/components/credits/modal/SubscribeModal.d.ts +1 -0
- package/dist/esm/components/credits/modal/SubscribeModal.js +1 -1
- package/dist/esm/components/credits/modal/SubscribeModal.js.map +3 -3
- package/package.json +1 -1
- package/src/components/credits/modal/SubscribeModal.tsx +75 -63
- package/dist/cjs/stories/LiveChatWidget.stories.d.ts +0 -14
- package/dist/cjs/stories/LiveChatWidget.stories.js +0 -52
- package/dist/cjs/stories/LiveChatWidget.stories.js.map +0 -7
- package/dist/cjs/stories/chat.stories.d.ts +0 -7
- package/dist/cjs/stories/chat.stories.js +0 -2
- package/dist/cjs/stories/chat.stories.js.map +0 -7
- package/dist/esm/stories/LiveChatWidget.stories.d.ts +0 -14
- package/dist/esm/stories/LiveChatWidget.stories.js +0 -52
- package/dist/esm/stories/LiveChatWidget.stories.js.map +0 -7
- package/dist/esm/stories/chat.stories.d.ts +0 -7
- package/dist/esm/stories/chat.stories.js +0 -2
- package/dist/esm/stories/chat.stories.js.map +0 -7
- package/src/stories/LiveChatWidget.stories.tsx +0 -317
- package/src/stories/chat.stories.tsx +0 -41
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ import '@anker-in/campaign-ui/livechat.css'
|
|
|
57
57
|
/>
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
支持自定义渲染器、品牌色、Logo
|
|
60
|
+
支持自定义渲染器、品牌色、Logo 等。本仓库没有 Storybook / 本地预览环境(拆分时配置未随仓库带出),调试请将组件接入宿主项目运行。
|
|
61
61
|
|
|
62
62
|
### Credits(积分)
|
|
63
63
|
|
|
@@ -109,7 +109,27 @@ pnpm run lint # eslint(不含 src/lib)
|
|
|
109
109
|
|
|
110
110
|
## 发布
|
|
111
111
|
|
|
112
|
+
本仓库通过 CI(Jenkins)自动发布,测试环境对应 `beta` 分支,线上环境对应 `main` 分支。
|
|
113
|
+
|
|
114
|
+
- **`beta`(测试环境)**:合并到该分支即自动触发发布,无需手动改动 `package.json` 版本号——CI 会基于当前版本号自动生成一个带时间戳的 `alpha` 版本(如 `0.5.22-alpha.1789381484328`)并发布到 npm 的 `beta` tag。发布完成后会通过飞书机器人推送通知,包含最终发布的版本号。
|
|
115
|
+
- **`main`(线上环境)**:合并前需要手动更新 `package.json` 中的 `version` 字段,合并后 CI 会读取该版本号自动发布到 npm 的 `latest` tag。
|
|
116
|
+
|
|
117
|
+
### 手动本地发布(不走 CI 时)
|
|
118
|
+
|
|
119
|
+
**发到 beta(测试环境)**:不需要手动改版本号,本地跑一次 bump 脚本生成临时 alpha 版本再发布:
|
|
120
|
+
|
|
121
|
+
```shell
|
|
122
|
+
node ./scripts/bump-beta-version.cjs # 自动生成形如 0.5.22-alpha.<timestamp> 的版本号
|
|
123
|
+
pnpm run build
|
|
124
|
+
npm publish --tag beta
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
发布完成后建议 `git checkout package.json` 撤销本地这次版本号改动,避免误提交。
|
|
128
|
+
|
|
129
|
+
**发到线上(main,latest tag)**:先手动把 `package.json` 的 `version` 改成正式版本号,再发布:
|
|
130
|
+
|
|
112
131
|
```shell
|
|
132
|
+
# 手动编辑 package.json 的 version 字段
|
|
113
133
|
pnpm run build
|
|
114
134
|
npm publish
|
|
115
135
|
```
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var I=Object.create;var u=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var B=Object.getOwnPropertyNames;var O=Object.getPrototypeOf,z=Object.prototype.hasOwnProperty;var U=(e,s)=>{for(var r in s)u(e,r,{get:s[r],enumerable:!0})},T=(e,s,r,d)=>{if(s&&typeof s=="object"||typeof s=="function")for(let l of B(s))!z.call(e,l)&&l!==r&&u(e,l,{get:()=>s[l],enumerable:!(d=R(s,l))||d.enumerable});return e};var S=(e,s,r)=>(r=e!=null?I(O(e)):{},T(s||!e||!e.__esModule?u(r,"default",{value:e,enumerable:!0}):r,e)),V=e=>T(u({},"__esModule",{value:!0}),e);var K={};U(K,{CreditsSubscribeModal:()=>J});module.exports=V(K);var t=require("react/jsx-runtime"),i=require("react"),o=require("@anker-in/headless-ui"),a=require("../../../lib"),n=S(require("js-cookie")),M=require("query-string"),k=S(require("crypto-js/sha256")),w=require("../context/provider"),E=require("../../../constants"),A=require("./ModalContainer");const G=()=>{if(typeof window>"u")return{register_source:""};const{search:e,href:s}=window.location||{},r=(0,M.parse)(e);return{fbuy_ref_code:n.default.get("fbuy_ref_code"),affiliate:n.default.get("affiliate"),ref:n.default.get("ref_ads"),inviter_code:r.ic||r.inviter_code||n.default.get("inviter_code"),register_source:r.redirect||n.default.get("reg_source")||s,deals_type:n.default.get("deal"),transfer_id:n.default.get("transfer_id")}};function J({copy:e,onSuccess:s,...r}){const{brand:d,locale:l}=(0,a.useHeadlessContext)(),p=E.ROUNDED_BRANDS.includes(d),[g,P]=(0,i.useState)(!1),[h,L]=(0,i.useState)(!1),[c,v]=(0,i.useState)(""),[_,f]=(0,i.useState)(""),[C,y]=(0,i.useState)(""),[D,N]=(0,i.useState)(!1),{profile:m}=(0,w.useCreditsContext)(),F=(0,i.useCallback)(b=>{f(""),v(b.target?.value?.trim())},[]),H=(0,i.useCallback)(async b=>{b.preventDefault(),f(""),N(!0);const{register_source:q}=G(),x=await(0,a.fetcher)({locale:l,action:"subscribe",url:"/api/multipass/subscribe/subscribe_emails",method:"POST",body:{email:c,register_source:q,brand:d,deals_type:e.dealsType||"vip_subscribe"},headers:{},type:""});N(!1),x.errors?f(x.errors||x.statusText):(y(e.successTips||"Subscribed successfully!"),s&&s(),setTimeout(()=>{y("")},3e3),(0,a.gaTrack)({subscribe_hashed_email:c?(0,k.default)(c):""}),(0,a.gaTrack)({event:"ga4Event",event_name:"subscribe",event_parameters:{page_group:e.dealsType||"vip_subscribe",position:"pop_up"}}),(0,a.gaTrack)({event:"uaEvent",eventCategory:"subscribe",eventAction:e.dealsType||"vip_subscribe",eventLabel:"pop_up",nonInteraction:!1}))},[c,l,d,e.dealsType,e.successTips,s]);return(0,i.useEffect)(()=>{m&&m?.email&&v(m?.email)},[m]),(0,t.jsx)(A.CreditsModalContainer,{className:"w-[540px] md:h-auto",scrollClassName:"mb-[40px] min-l:px-[48px] md:mb-[24px]",titleClassName:"h-[56px]",useAnimation:!0,animationClassName:"md:translate-y-[100vh]",...r,children:(0,t.jsxs)("form",{className:"min-l:px-[16px] flex flex-col gap-[16px] text-center",onSubmit:H,noValidate:!1,children:[(0,t.jsx)("div",{children:(0,t.jsx)(o.Text,{className:(0,a.classNames)("mb-[24px] text-[22px] font-bold"),html:e.title})}),e.desc&&(0,t.jsx)(o.Text,{className:"mb-[8px] text-left text-[16px] font-semibold leading-[1.4] text-[#1F2021]",html:e.desc}),(0,t.jsxs)("div",{className:(0,a.classNames)("relative flex h-[52px] w-full overflow-hidden rounded-[2px] md:grid md:h-auto md:grid-rows-1 md:gap-[16px]",!p&&"rounded-none"),children:[(0,t.jsx)("input",{className:(0,a.classNames)("h-full flex-1 rounded-l-[2px] border-[1px] border-[#d8d8d8] px-[14px] py-[17px] text-[16px] font-semibold leading-[1] outline-none placeholder:text-[#999] md:h-[52px] md:rounded-[2px]",!p&&"rounded-none"),type:"email",required:!0,placeholder:e.placeholder,value:c,onChange:F}),(0,t.jsx)(o.Button,{type:"submit",variant:"primary",loading:D,className:(0,a.classNames)("legacy-headless-ui-primary-button !h-full !min-w-0 !rounded-l-none !rounded-r-[2px] !px-[40px] !py-[13px]",!p&&"rounded-none"),children:(0,t.jsx)(o.Picture,{source:"https://cdn.shopify.com/s/files/1/0512/8568/8505/files/icon_email.png?v=1697527383",className:"size-[24px]",alt:"email"})})]}),e?.oneankerSubscribeAgree&&(0,t.jsxs)("div",{className:"flex w-full",children:[(0,t.jsx)(o.Checkbox,{checked:h,onCheckedChange:()=>L(!h),required:!0,className:"border-[#1d1d1f]"}),(0,t.jsx)("label",{className:"ml-2 text-left text-[14px] font-semibold text-[#777] [&_a]:underline",dangerouslySetInnerHTML:{__html:e.oneankerSubscribeAgree}})]}),(0,t.jsxs)("div",{className:"flex w-full",children:[(0,t.jsx)(o.Checkbox,{checked:g,onCheckedChange:()=>P(!g),required:!0,className:"border-[#1d1d1f]"}),(0,t.jsx)("label",{className:"ml-2 text-left text-[14px] font-semibold text-[#777] [&_a]:underline",dangerouslySetInnerHTML:{__html:e?.policy||""}})]}),(0,t.jsx)("div",{className:(0,a.classNames)("w-full text-left text-[16px] font-semibold",_&&"text-[#F84D4F]",C&&"text-[#52C41A]"),children:(0,t.jsx)(o.Text,{html:C||_||""})})]})})}
|
|
2
2
|
//# sourceMappingURL=SubscribeModal.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/components/credits/modal/SubscribeModal.tsx"],
|
|
4
|
-
"sourcesContent": ["import { useCallback, useEffect, useState } from 'react'\nimport { Button, Checkbox, Picture, Text } from '@anker-in/headless-ui'\nimport { classNames, fetcher, gaTrack, useHeadlessContext } from '../../../lib'\nimport Cookies from 'js-cookie'\nimport { parse } from 'query-string'\nimport sha256 from 'crypto-js/sha256'\nimport { useCreditsContext } from '../context/provider'\nimport { ROUNDED_BRANDS } from '../../../constants'\nimport {
|
|
5
|
-
"mappings": "0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,2BAAAE,IAAA,eAAAC,EAAAH,
|
|
6
|
-
"names": ["SubscribeModal_exports", "__export", "CreditsSubscribeModal", "__toCommonJS", "import_jsx_runtime", "import_react", "import_headless_ui", "import_lib", "import_js_cookie", "import_query_string", "import_sha256", "import_provider", "import_constants", "
|
|
4
|
+
"sourcesContent": ["import { useCallback, useEffect, useState } from 'react'\nimport { Button, Checkbox, Picture, Text } from '@anker-in/headless-ui'\nimport { classNames, fetcher, gaTrack, useHeadlessContext } from '../../../lib'\nimport Cookies from 'js-cookie'\nimport { parse } from 'query-string'\nimport sha256 from 'crypto-js/sha256'\nimport { useCreditsContext } from '../context/provider'\nimport { ROUNDED_BRANDS } from '../../../constants'\nimport { CreditsModalContainer, type ModalContainerProps } from './ModalContainer'\n\nexport type CreditsSubscribeModalCopy = {\n title: string\n desc?: string\n placeholder: string\n policy?: string\n oneankerSubscribeAgree?: string\n successTips?: string\n dealsType?: string\n}\n\ninterface CreditsSubscribeModalProps extends ModalContainerProps {\n copy: CreditsSubscribeModalCopy\n onSuccess?: () => void\n}\n\nconst getAdCookie = () => {\n if (typeof window === 'undefined') {\n return { register_source: '' }\n }\n const { search, href } = window.location || {}\n const query = parse(search)\n\n return {\n fbuy_ref_code: Cookies.get('fbuy_ref_code'),\n affiliate: Cookies.get('affiliate'),\n ref: Cookies.get('ref_ads'),\n inviter_code: query.ic || query.inviter_code || Cookies.get('inviter_code'),\n register_source: query.redirect || Cookies.get('reg_source') || href,\n deals_type: Cookies.get('deal'),\n transfer_id: Cookies.get('transfer_id'),\n }\n}\n\nexport function CreditsSubscribeModal({ copy, onSuccess, ...props }: CreditsSubscribeModalProps) {\n const { brand, locale } = useHeadlessContext()\n const rounded = ROUNDED_BRANDS.includes(brand)\n const [policy, setPolicy] = useState(false)\n const [oneankerSubscribeAgree, setOneankerSubscribeAgree] = useState(false)\n const [email, setEmail] = useState('')\n const [errorMessage, setErrorMessage] = useState('')\n const [successMessage, setSuccessMessage] = useState('')\n const [loading, setLoading] = useState(false)\n const { profile } = useCreditsContext()\n\n const handleEmailChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {\n setErrorMessage('')\n setEmail(event.target?.value?.trim())\n }, [])\n\n const handleSubmit = useCallback(\n async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n setErrorMessage('')\n\n setLoading(true)\n const { register_source } = getAdCookie()\n\n const result = await fetcher({\n locale,\n action: 'subscribe',\n url: '/api/multipass/subscribe/subscribe_emails',\n method: 'POST',\n body: {\n email,\n register_source,\n brand,\n deals_type: copy.dealsType || 'vip_subscribe',\n },\n headers: {},\n type: '',\n })\n setLoading(false)\n\n if (!result.errors) {\n setSuccessMessage(copy.successTips || 'Subscribed successfully!')\n if (onSuccess) {\n onSuccess()\n }\n setTimeout(() => {\n setSuccessMessage('')\n }, 3000)\n gaTrack({\n subscribe_hashed_email: email ? sha256(email) : '',\n })\n gaTrack({\n event: 'ga4Event',\n event_name: 'subscribe',\n event_parameters: {\n page_group: copy.dealsType || 'vip_subscribe',\n position: 'pop_up',\n },\n })\n gaTrack({\n event: 'uaEvent',\n eventCategory: 'subscribe',\n eventAction: copy.dealsType || 'vip_subscribe',\n eventLabel: 'pop_up',\n nonInteraction: false,\n })\n } else {\n setErrorMessage(result.errors || result.statusText)\n }\n },\n [email, locale, brand, copy.dealsType, copy.successTips, onSuccess]\n )\n\n useEffect(() => {\n if (profile && profile?.email) {\n setEmail(profile?.email)\n }\n }, [profile])\n\n return (\n <CreditsModalContainer\n className=\"w-[540px] md:h-auto\"\n scrollClassName=\"mb-[40px] min-l:px-[48px] md:mb-[24px]\"\n titleClassName=\"h-[56px]\"\n useAnimation\n animationClassName=\"md:translate-y-[100vh]\"\n {...props}\n >\n <form className=\"min-l:px-[16px] flex flex-col gap-[16px] text-center\" onSubmit={handleSubmit} noValidate={false}>\n <div>\n <Text className={classNames('mb-[24px] text-[22px] font-bold')} html={copy.title}></Text>\n </div>\n {copy.desc && (\n <Text\n className=\"mb-[8px] text-left text-[16px] font-semibold leading-[1.4] text-[#1F2021]\"\n html={copy.desc}\n ></Text>\n )}\n <div\n className={classNames(\n 'relative flex h-[52px] w-full overflow-hidden rounded-[2px] md:grid md:h-auto md:grid-rows-1 md:gap-[16px]',\n !rounded && 'rounded-none'\n )}\n >\n <input\n className={classNames(\n 'h-full flex-1 rounded-l-[2px] border-[1px] border-[#d8d8d8] px-[14px] py-[17px] text-[16px] font-semibold leading-[1] outline-none placeholder:text-[#999] md:h-[52px] md:rounded-[2px]',\n !rounded && 'rounded-none'\n )}\n type=\"email\"\n required\n placeholder={copy.placeholder}\n value={email}\n onChange={handleEmailChange}\n />\n <Button\n type=\"submit\"\n variant=\"primary\"\n loading={loading}\n className={classNames(\n 'legacy-headless-ui-primary-button !h-full !min-w-0 !rounded-l-none !rounded-r-[2px] !px-[40px] !py-[13px]',\n !rounded && 'rounded-none'\n )}\n >\n <Picture\n source=\"https://cdn.shopify.com/s/files/1/0512/8568/8505/files/icon_email.png?v=1697527383\"\n className=\"size-[24px]\"\n alt=\"email\"\n />\n </Button>\n </div>\n {copy?.oneankerSubscribeAgree && (\n <div className=\"flex w-full\">\n <Checkbox\n checked={oneankerSubscribeAgree}\n onCheckedChange={() => setOneankerSubscribeAgree(!oneankerSubscribeAgree)}\n required\n className=\"border-[#1d1d1f]\"\n />\n <label\n className=\"ml-2 text-left text-[14px] font-semibold text-[#777] [&_a]:underline\"\n dangerouslySetInnerHTML={{\n __html: copy.oneankerSubscribeAgree,\n }}\n />\n </div>\n )}\n <div className=\"flex w-full\">\n <Checkbox checked={policy} onCheckedChange={() => setPolicy(!policy)} required className=\"border-[#1d1d1f]\" />\n <label\n className=\"ml-2 text-left text-[14px] font-semibold text-[#777] [&_a]:underline\"\n dangerouslySetInnerHTML={{\n __html: copy?.policy || '',\n }}\n />\n </div>\n <div\n className={classNames(\n 'w-full text-left text-[16px] font-semibold',\n errorMessage && 'text-[#F84D4F]',\n successMessage && 'text-[#52C41A]'\n )}\n >\n <Text html={successMessage || errorMessage || ''} />\n </div>\n </form>\n </CreditsModalContainer>\n )\n}\n"],
|
|
5
|
+
"mappings": "0jBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,2BAAAE,IAAA,eAAAC,EAAAH,GAqIU,IAAAI,EAAA,6BArIVC,EAAiD,iBACjDC,EAAgD,iCAChDC,EAAiE,wBACjEC,EAAoB,wBACpBC,EAAsB,wBACtBC,EAAmB,+BACnBC,EAAkC,+BAClCC,EAA+B,8BAC/BC,EAAgE,4BAiBhE,MAAMC,EAAc,IAAM,CACxB,GAAI,OAAO,OAAW,IACpB,MAAO,CAAE,gBAAiB,EAAG,EAE/B,KAAM,CAAE,OAAAC,EAAQ,KAAAC,CAAK,EAAI,OAAO,UAAY,CAAC,EACvCC,KAAQ,SAAMF,CAAM,EAE1B,MAAO,CACL,cAAe,EAAAG,QAAQ,IAAI,eAAe,EAC1C,UAAW,EAAAA,QAAQ,IAAI,WAAW,EAClC,IAAK,EAAAA,QAAQ,IAAI,SAAS,EAC1B,aAAcD,EAAM,IAAMA,EAAM,cAAgB,EAAAC,QAAQ,IAAI,cAAc,EAC1E,gBAAiBD,EAAM,UAAY,EAAAC,QAAQ,IAAI,YAAY,GAAKF,EAChE,WAAY,EAAAE,QAAQ,IAAI,MAAM,EAC9B,YAAa,EAAAA,QAAQ,IAAI,aAAa,CACxC,CACF,EAEO,SAAShB,EAAsB,CAAE,KAAAiB,EAAM,UAAAC,EAAW,GAAGC,CAAM,EAA+B,CAC/F,KAAM,CAAE,MAAAC,EAAO,OAAAC,CAAO,KAAI,sBAAmB,EACvCC,EAAU,iBAAe,SAASF,CAAK,EACvC,CAACG,EAAQC,CAAS,KAAI,YAAS,EAAK,EACpC,CAACC,EAAwBC,CAAyB,KAAI,YAAS,EAAK,EACpE,CAACC,EAAOC,CAAQ,KAAI,YAAS,EAAE,EAC/B,CAACC,EAAcC,CAAe,KAAI,YAAS,EAAE,EAC7C,CAACC,EAAgBC,CAAiB,KAAI,YAAS,EAAE,EACjD,CAACC,EAASC,CAAU,KAAI,YAAS,EAAK,EACtC,CAAE,QAAAC,CAAQ,KAAI,qBAAkB,EAEhCC,KAAoB,eAAaC,GAA+C,CACpFP,EAAgB,EAAE,EAClBF,EAASS,EAAM,QAAQ,OAAO,KAAK,CAAC,CACtC,EAAG,CAAC,CAAC,EAECC,KAAe,eACnB,MAAOD,GAA4C,CACjDA,EAAM,eAAe,EACrBP,EAAgB,EAAE,EAElBI,EAAW,EAAI,EACf,KAAM,CAAE,gBAAAK,CAAgB,EAAI3B,EAAY,EAElC4B,EAAS,QAAM,WAAQ,CAC3B,OAAAnB,EACA,OAAQ,YACR,IAAK,4CACL,OAAQ,OACR,KAAM,CACJ,MAAAM,EACA,gBAAAY,EACA,MAAAnB,EACA,WAAYH,EAAK,WAAa,eAChC,EACA,QAAS,CAAC,EACV,KAAM,EACR,CAAC,EACDiB,EAAW,EAAK,EAEXM,EAAO,OA2BVV,EAAgBU,EAAO,QAAUA,EAAO,UAAU,GA1BlDR,EAAkBf,EAAK,aAAe,0BAA0B,EAC5DC,GACFA,EAAU,EAEZ,WAAW,IAAM,CACfc,EAAkB,EAAE,CACtB,EAAG,GAAI,KACP,WAAQ,CACN,uBAAwBL,KAAQ,EAAAc,SAAOd,CAAK,EAAI,EAClD,CAAC,KACD,WAAQ,CACN,MAAO,WACP,WAAY,YACZ,iBAAkB,CAChB,WAAYV,EAAK,WAAa,gBAC9B,SAAU,QACZ,CACF,CAAC,KACD,WAAQ,CACN,MAAO,UACP,cAAe,YACf,YAAaA,EAAK,WAAa,gBAC/B,WAAY,SACZ,eAAgB,EAClB,CAAC,EAIL,EACA,CAACU,EAAON,EAAQD,EAAOH,EAAK,UAAWA,EAAK,YAAaC,CAAS,CACpE,EAEA,sBAAU,IAAM,CACViB,GAAWA,GAAS,OACtBP,EAASO,GAAS,KAAK,CAE3B,EAAG,CAACA,CAAO,CAAC,KAGV,OAAC,yBACC,UAAU,sBACV,gBAAgB,yCAChB,eAAe,WACf,aAAY,GACZ,mBAAmB,yBAClB,GAAGhB,EAEJ,oBAAC,QAAK,UAAU,uDAAuD,SAAUmB,EAAc,WAAY,GACzG,oBAAC,OACC,mBAAC,QAAK,aAAW,cAAW,iCAAiC,EAAG,KAAMrB,EAAK,MAAO,EACpF,EACCA,EAAK,SACJ,OAAC,QACC,UAAU,4EACV,KAAMA,EAAK,KACZ,KAEH,QAAC,OACC,aAAW,cACT,6GACA,CAACK,GAAW,cACd,EAEA,oBAAC,SACC,aAAW,cACT,0LACA,CAACA,GAAW,cACd,EACA,KAAK,QACL,SAAQ,GACR,YAAaL,EAAK,YAClB,MAAOU,EACP,SAAUS,EACZ,KACA,OAAC,UACC,KAAK,SACL,QAAQ,UACR,QAASH,EACT,aAAW,cACT,4GACA,CAACX,GAAW,cACd,EAEA,mBAAC,WACC,OAAO,qFACP,UAAU,cACV,IAAI,QACN,EACF,GACF,EACCL,GAAM,2BACL,QAAC,OAAI,UAAU,cACb,oBAAC,YACC,QAASQ,EACT,gBAAiB,IAAMC,EAA0B,CAACD,CAAsB,EACxE,SAAQ,GACR,UAAU,mBACZ,KACA,OAAC,SACC,UAAU,uEACV,wBAAyB,CACvB,OAAQR,EAAK,sBACf,EACF,GACF,KAEF,QAAC,OAAI,UAAU,cACb,oBAAC,YAAS,QAASM,EAAQ,gBAAiB,IAAMC,EAAU,CAACD,CAAM,EAAG,SAAQ,GAAC,UAAU,mBAAmB,KAC5G,OAAC,SACC,UAAU,uEACV,wBAAyB,CACvB,OAAQN,GAAM,QAAU,EAC1B,EACF,GACF,KACA,OAAC,OACC,aAAW,cACT,6CACAY,GAAgB,iBAChBE,GAAkB,gBACpB,EAEA,mBAAC,QAAK,KAAMA,GAAkBF,GAAgB,GAAI,EACpD,GACF,EACF,CAEJ",
|
|
6
|
+
"names": ["SubscribeModal_exports", "__export", "CreditsSubscribeModal", "__toCommonJS", "import_jsx_runtime", "import_react", "import_headless_ui", "import_lib", "import_js_cookie", "import_query_string", "import_sha256", "import_provider", "import_constants", "import_ModalContainer", "getAdCookie", "search", "href", "query", "Cookies", "copy", "onSuccess", "props", "brand", "locale", "rounded", "policy", "setPolicy", "oneankerSubscribeAgree", "setOneankerSubscribeAgree", "email", "setEmail", "errorMessage", "setErrorMessage", "successMessage", "setSuccessMessage", "loading", "setLoading", "profile", "handleEmailChange", "event", "handleSubmit", "register_source", "result", "sha256"]
|
|
7
7
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{jsx as t,jsxs as
|
|
1
|
+
import{jsx as t,jsxs as d}from"react/jsx-runtime";import{useCallback as S,useEffect as D,useState as r}from"react";import{Button as F,Checkbox as M,Picture as H,Text as b}from"@anker-in/headless-ui";import{classNames as l,fetcher as q,gaTrack as x,useHeadlessContext as I}from"../../../lib";import s from"js-cookie";import{parse as R}from"query-string";import B from"crypto-js/sha256";import{useCreditsContext as O}from"../context/provider";import{ROUNDED_BRANDS as z}from"../../../constants";import{CreditsModalContainer as U}from"./ModalContainer";const V=()=>{if(typeof window>"u")return{register_source:""};const{search:e,href:a}=window.location||{},i=R(e);return{fbuy_ref_code:s.get("fbuy_ref_code"),affiliate:s.get("affiliate"),ref:s.get("ref_ads"),inviter_code:i.ic||i.inviter_code||s.get("inviter_code"),register_source:i.redirect||s.get("reg_source")||a,deals_type:s.get("deal"),transfer_id:s.get("transfer_id")}};function j({copy:e,onSuccess:a,...i}){const{brand:c,locale:g}=I(),m=z.includes(c),[h,k]=r(!1),[v,w]=r(!1),[o,_]=r(""),[C,u]=r(""),[y,N]=r(""),[E,T]=r(!1),{profile:n}=O(),A=S(p=>{u(""),_(p.target?.value?.trim())},[]),P=S(async p=>{p.preventDefault(),u(""),T(!0);const{register_source:L}=V(),f=await q({locale:g,action:"subscribe",url:"/api/multipass/subscribe/subscribe_emails",method:"POST",body:{email:o,register_source:L,brand:c,deals_type:e.dealsType||"vip_subscribe"},headers:{},type:""});T(!1),f.errors?u(f.errors||f.statusText):(N(e.successTips||"Subscribed successfully!"),a&&a(),setTimeout(()=>{N("")},3e3),x({subscribe_hashed_email:o?B(o):""}),x({event:"ga4Event",event_name:"subscribe",event_parameters:{page_group:e.dealsType||"vip_subscribe",position:"pop_up"}}),x({event:"uaEvent",eventCategory:"subscribe",eventAction:e.dealsType||"vip_subscribe",eventLabel:"pop_up",nonInteraction:!1}))},[o,g,c,e.dealsType,e.successTips,a]);return D(()=>{n&&n?.email&&_(n?.email)},[n]),t(U,{className:"w-[540px] md:h-auto",scrollClassName:"mb-[40px] min-l:px-[48px] md:mb-[24px]",titleClassName:"h-[56px]",useAnimation:!0,animationClassName:"md:translate-y-[100vh]",...i,children:d("form",{className:"min-l:px-[16px] flex flex-col gap-[16px] text-center",onSubmit:P,noValidate:!1,children:[t("div",{children:t(b,{className:l("mb-[24px] text-[22px] font-bold"),html:e.title})}),e.desc&&t(b,{className:"mb-[8px] text-left text-[16px] font-semibold leading-[1.4] text-[#1F2021]",html:e.desc}),d("div",{className:l("relative flex h-[52px] w-full overflow-hidden rounded-[2px] md:grid md:h-auto md:grid-rows-1 md:gap-[16px]",!m&&"rounded-none"),children:[t("input",{className:l("h-full flex-1 rounded-l-[2px] border-[1px] border-[#d8d8d8] px-[14px] py-[17px] text-[16px] font-semibold leading-[1] outline-none placeholder:text-[#999] md:h-[52px] md:rounded-[2px]",!m&&"rounded-none"),type:"email",required:!0,placeholder:e.placeholder,value:o,onChange:A}),t(F,{type:"submit",variant:"primary",loading:E,className:l("legacy-headless-ui-primary-button !h-full !min-w-0 !rounded-l-none !rounded-r-[2px] !px-[40px] !py-[13px]",!m&&"rounded-none"),children:t(H,{source:"https://cdn.shopify.com/s/files/1/0512/8568/8505/files/icon_email.png?v=1697527383",className:"size-[24px]",alt:"email"})})]}),e?.oneankerSubscribeAgree&&d("div",{className:"flex w-full",children:[t(M,{checked:v,onCheckedChange:()=>w(!v),required:!0,className:"border-[#1d1d1f]"}),t("label",{className:"ml-2 text-left text-[14px] font-semibold text-[#777] [&_a]:underline",dangerouslySetInnerHTML:{__html:e.oneankerSubscribeAgree}})]}),d("div",{className:"flex w-full",children:[t(M,{checked:h,onCheckedChange:()=>k(!h),required:!0,className:"border-[#1d1d1f]"}),t("label",{className:"ml-2 text-left text-[14px] font-semibold text-[#777] [&_a]:underline",dangerouslySetInnerHTML:{__html:e?.policy||""}})]}),t("div",{className:l("w-full text-left text-[16px] font-semibold",C&&"text-[#F84D4F]",y&&"text-[#52C41A]"),children:t(b,{html:y||C||""})})]})})}export{j as CreditsSubscribeModal};
|
|
2
2
|
//# sourceMappingURL=SubscribeModal.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/components/credits/modal/SubscribeModal.tsx"],
|
|
4
|
-
"sourcesContent": ["import { useCallback, useEffect, useState } from 'react'\nimport { Button, Checkbox, Picture, Text } from '@anker-in/headless-ui'\nimport { classNames, fetcher, gaTrack, useHeadlessContext } from '../../../lib'\nimport Cookies from 'js-cookie'\nimport { parse } from 'query-string'\nimport sha256 from 'crypto-js/sha256'\nimport { useCreditsContext } from '../context/provider'\nimport { ROUNDED_BRANDS } from '../../../constants'\nimport {
|
|
5
|
-
"mappings": "
|
|
6
|
-
"names": ["jsx", "jsxs", "useCallback", "useEffect", "useState", "Button", "Checkbox", "Picture", "Text", "classNames", "fetcher", "gaTrack", "useHeadlessContext", "Cookies", "parse", "sha256", "useCreditsContext", "ROUNDED_BRANDS", "
|
|
4
|
+
"sourcesContent": ["import { useCallback, useEffect, useState } from 'react'\nimport { Button, Checkbox, Picture, Text } from '@anker-in/headless-ui'\nimport { classNames, fetcher, gaTrack, useHeadlessContext } from '../../../lib'\nimport Cookies from 'js-cookie'\nimport { parse } from 'query-string'\nimport sha256 from 'crypto-js/sha256'\nimport { useCreditsContext } from '../context/provider'\nimport { ROUNDED_BRANDS } from '../../../constants'\nimport { CreditsModalContainer, type ModalContainerProps } from './ModalContainer'\n\nexport type CreditsSubscribeModalCopy = {\n title: string\n desc?: string\n placeholder: string\n policy?: string\n oneankerSubscribeAgree?: string\n successTips?: string\n dealsType?: string\n}\n\ninterface CreditsSubscribeModalProps extends ModalContainerProps {\n copy: CreditsSubscribeModalCopy\n onSuccess?: () => void\n}\n\nconst getAdCookie = () => {\n if (typeof window === 'undefined') {\n return { register_source: '' }\n }\n const { search, href } = window.location || {}\n const query = parse(search)\n\n return {\n fbuy_ref_code: Cookies.get('fbuy_ref_code'),\n affiliate: Cookies.get('affiliate'),\n ref: Cookies.get('ref_ads'),\n inviter_code: query.ic || query.inviter_code || Cookies.get('inviter_code'),\n register_source: query.redirect || Cookies.get('reg_source') || href,\n deals_type: Cookies.get('deal'),\n transfer_id: Cookies.get('transfer_id'),\n }\n}\n\nexport function CreditsSubscribeModal({ copy, onSuccess, ...props }: CreditsSubscribeModalProps) {\n const { brand, locale } = useHeadlessContext()\n const rounded = ROUNDED_BRANDS.includes(brand)\n const [policy, setPolicy] = useState(false)\n const [oneankerSubscribeAgree, setOneankerSubscribeAgree] = useState(false)\n const [email, setEmail] = useState('')\n const [errorMessage, setErrorMessage] = useState('')\n const [successMessage, setSuccessMessage] = useState('')\n const [loading, setLoading] = useState(false)\n const { profile } = useCreditsContext()\n\n const handleEmailChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {\n setErrorMessage('')\n setEmail(event.target?.value?.trim())\n }, [])\n\n const handleSubmit = useCallback(\n async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n setErrorMessage('')\n\n setLoading(true)\n const { register_source } = getAdCookie()\n\n const result = await fetcher({\n locale,\n action: 'subscribe',\n url: '/api/multipass/subscribe/subscribe_emails',\n method: 'POST',\n body: {\n email,\n register_source,\n brand,\n deals_type: copy.dealsType || 'vip_subscribe',\n },\n headers: {},\n type: '',\n })\n setLoading(false)\n\n if (!result.errors) {\n setSuccessMessage(copy.successTips || 'Subscribed successfully!')\n if (onSuccess) {\n onSuccess()\n }\n setTimeout(() => {\n setSuccessMessage('')\n }, 3000)\n gaTrack({\n subscribe_hashed_email: email ? sha256(email) : '',\n })\n gaTrack({\n event: 'ga4Event',\n event_name: 'subscribe',\n event_parameters: {\n page_group: copy.dealsType || 'vip_subscribe',\n position: 'pop_up',\n },\n })\n gaTrack({\n event: 'uaEvent',\n eventCategory: 'subscribe',\n eventAction: copy.dealsType || 'vip_subscribe',\n eventLabel: 'pop_up',\n nonInteraction: false,\n })\n } else {\n setErrorMessage(result.errors || result.statusText)\n }\n },\n [email, locale, brand, copy.dealsType, copy.successTips, onSuccess]\n )\n\n useEffect(() => {\n if (profile && profile?.email) {\n setEmail(profile?.email)\n }\n }, [profile])\n\n return (\n <CreditsModalContainer\n className=\"w-[540px] md:h-auto\"\n scrollClassName=\"mb-[40px] min-l:px-[48px] md:mb-[24px]\"\n titleClassName=\"h-[56px]\"\n useAnimation\n animationClassName=\"md:translate-y-[100vh]\"\n {...props}\n >\n <form className=\"min-l:px-[16px] flex flex-col gap-[16px] text-center\" onSubmit={handleSubmit} noValidate={false}>\n <div>\n <Text className={classNames('mb-[24px] text-[22px] font-bold')} html={copy.title}></Text>\n </div>\n {copy.desc && (\n <Text\n className=\"mb-[8px] text-left text-[16px] font-semibold leading-[1.4] text-[#1F2021]\"\n html={copy.desc}\n ></Text>\n )}\n <div\n className={classNames(\n 'relative flex h-[52px] w-full overflow-hidden rounded-[2px] md:grid md:h-auto md:grid-rows-1 md:gap-[16px]',\n !rounded && 'rounded-none'\n )}\n >\n <input\n className={classNames(\n 'h-full flex-1 rounded-l-[2px] border-[1px] border-[#d8d8d8] px-[14px] py-[17px] text-[16px] font-semibold leading-[1] outline-none placeholder:text-[#999] md:h-[52px] md:rounded-[2px]',\n !rounded && 'rounded-none'\n )}\n type=\"email\"\n required\n placeholder={copy.placeholder}\n value={email}\n onChange={handleEmailChange}\n />\n <Button\n type=\"submit\"\n variant=\"primary\"\n loading={loading}\n className={classNames(\n 'legacy-headless-ui-primary-button !h-full !min-w-0 !rounded-l-none !rounded-r-[2px] !px-[40px] !py-[13px]',\n !rounded && 'rounded-none'\n )}\n >\n <Picture\n source=\"https://cdn.shopify.com/s/files/1/0512/8568/8505/files/icon_email.png?v=1697527383\"\n className=\"size-[24px]\"\n alt=\"email\"\n />\n </Button>\n </div>\n {copy?.oneankerSubscribeAgree && (\n <div className=\"flex w-full\">\n <Checkbox\n checked={oneankerSubscribeAgree}\n onCheckedChange={() => setOneankerSubscribeAgree(!oneankerSubscribeAgree)}\n required\n className=\"border-[#1d1d1f]\"\n />\n <label\n className=\"ml-2 text-left text-[14px] font-semibold text-[#777] [&_a]:underline\"\n dangerouslySetInnerHTML={{\n __html: copy.oneankerSubscribeAgree,\n }}\n />\n </div>\n )}\n <div className=\"flex w-full\">\n <Checkbox checked={policy} onCheckedChange={() => setPolicy(!policy)} required className=\"border-[#1d1d1f]\" />\n <label\n className=\"ml-2 text-left text-[14px] font-semibold text-[#777] [&_a]:underline\"\n dangerouslySetInnerHTML={{\n __html: copy?.policy || '',\n }}\n />\n </div>\n <div\n className={classNames(\n 'w-full text-left text-[16px] font-semibold',\n errorMessage && 'text-[#F84D4F]',\n successMessage && 'text-[#52C41A]'\n )}\n >\n <Text html={successMessage || errorMessage || ''} />\n </div>\n </form>\n </CreditsModalContainer>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["jsx", "jsxs", "useCallback", "useEffect", "useState", "Button", "Checkbox", "Picture", "Text", "classNames", "fetcher", "gaTrack", "useHeadlessContext", "Cookies", "parse", "sha256", "useCreditsContext", "ROUNDED_BRANDS", "CreditsModalContainer", "getAdCookie", "search", "href", "query", "CreditsSubscribeModal", "copy", "onSuccess", "props", "brand", "locale", "rounded", "policy", "setPolicy", "oneankerSubscribeAgree", "setOneankerSubscribeAgree", "email", "setEmail", "errorMessage", "setErrorMessage", "successMessage", "setSuccessMessage", "loading", "setLoading", "profile", "handleEmailChange", "event", "handleSubmit", "register_source", "result"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -6,7 +6,6 @@ import { parse } from 'query-string'
|
|
|
6
6
|
import sha256 from 'crypto-js/sha256'
|
|
7
7
|
import { useCreditsContext } from '../context/provider'
|
|
8
8
|
import { ROUNDED_BRANDS } from '../../../constants'
|
|
9
|
-
import { emailValidate } from '../context/utils'
|
|
10
9
|
import { CreditsModalContainer, type ModalContainerProps } from './ModalContainer'
|
|
11
10
|
|
|
12
11
|
export type CreditsSubscribeModalCopy = {
|
|
@@ -14,6 +13,7 @@ export type CreditsSubscribeModalCopy = {
|
|
|
14
13
|
desc?: string
|
|
15
14
|
placeholder: string
|
|
16
15
|
policy?: string
|
|
16
|
+
oneankerSubscribeAgree?: string
|
|
17
17
|
successTips?: string
|
|
18
18
|
dealsType?: string
|
|
19
19
|
}
|
|
@@ -45,6 +45,7 @@ export function CreditsSubscribeModal({ copy, onSuccess, ...props }: CreditsSubs
|
|
|
45
45
|
const { brand, locale } = useHeadlessContext()
|
|
46
46
|
const rounded = ROUNDED_BRANDS.includes(brand)
|
|
47
47
|
const [policy, setPolicy] = useState(false)
|
|
48
|
+
const [oneankerSubscribeAgree, setOneankerSubscribeAgree] = useState(false)
|
|
48
49
|
const [email, setEmail] = useState('')
|
|
49
50
|
const [errorMessage, setErrorMessage] = useState('')
|
|
50
51
|
const [successMessage, setSuccessMessage] = useState('')
|
|
@@ -56,69 +57,62 @@ export function CreditsSubscribeModal({ copy, onSuccess, ...props }: CreditsSubs
|
|
|
56
57
|
setEmail(event.target?.value?.trim())
|
|
57
58
|
}, [])
|
|
58
59
|
|
|
59
|
-
const handleSubmit = useCallback(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
60
|
+
const handleSubmit = useCallback(
|
|
61
|
+
async (event: React.FormEvent<HTMLFormElement>) => {
|
|
62
|
+
event.preventDefault()
|
|
63
|
+
setErrorMessage('')
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (!policy) {
|
|
70
|
-
return setErrorMessage('Please agree to the policy')
|
|
71
|
-
}
|
|
65
|
+
setLoading(true)
|
|
66
|
+
const { register_source } = getAdCookie()
|
|
72
67
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
register_source,
|
|
84
|
-
brand,
|
|
85
|
-
deals_type: copy.dealsType || 'vip_subscribe',
|
|
86
|
-
},
|
|
87
|
-
headers: {},
|
|
88
|
-
type: '',
|
|
89
|
-
})
|
|
90
|
-
setLoading(false)
|
|
91
|
-
|
|
92
|
-
if (!result.errors) {
|
|
93
|
-
setSuccessMessage(copy.successTips || 'Subscribed successfully!')
|
|
94
|
-
if (onSuccess) {
|
|
95
|
-
onSuccess()
|
|
96
|
-
}
|
|
97
|
-
setTimeout(() => {
|
|
98
|
-
setSuccessMessage('')
|
|
99
|
-
}, 3000)
|
|
100
|
-
gaTrack({
|
|
101
|
-
subscribe_hashed_email: email ? sha256(email) : '',
|
|
102
|
-
})
|
|
103
|
-
gaTrack({
|
|
104
|
-
event: 'ga4Event',
|
|
105
|
-
event_name: 'subscribe',
|
|
106
|
-
event_parameters: {
|
|
107
|
-
page_group: copy.dealsType || 'vip_subscribe',
|
|
108
|
-
position: 'pop_up',
|
|
68
|
+
const result = await fetcher({
|
|
69
|
+
locale,
|
|
70
|
+
action: 'subscribe',
|
|
71
|
+
url: '/api/multipass/subscribe/subscribe_emails',
|
|
72
|
+
method: 'POST',
|
|
73
|
+
body: {
|
|
74
|
+
email,
|
|
75
|
+
register_source,
|
|
76
|
+
brand,
|
|
77
|
+
deals_type: copy.dealsType || 'vip_subscribe',
|
|
109
78
|
},
|
|
79
|
+
headers: {},
|
|
80
|
+
type: '',
|
|
110
81
|
})
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
82
|
+
setLoading(false)
|
|
83
|
+
|
|
84
|
+
if (!result.errors) {
|
|
85
|
+
setSuccessMessage(copy.successTips || 'Subscribed successfully!')
|
|
86
|
+
if (onSuccess) {
|
|
87
|
+
onSuccess()
|
|
88
|
+
}
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
setSuccessMessage('')
|
|
91
|
+
}, 3000)
|
|
92
|
+
gaTrack({
|
|
93
|
+
subscribe_hashed_email: email ? sha256(email) : '',
|
|
94
|
+
})
|
|
95
|
+
gaTrack({
|
|
96
|
+
event: 'ga4Event',
|
|
97
|
+
event_name: 'subscribe',
|
|
98
|
+
event_parameters: {
|
|
99
|
+
page_group: copy.dealsType || 'vip_subscribe',
|
|
100
|
+
position: 'pop_up',
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
gaTrack({
|
|
104
|
+
event: 'uaEvent',
|
|
105
|
+
eventCategory: 'subscribe',
|
|
106
|
+
eventAction: copy.dealsType || 'vip_subscribe',
|
|
107
|
+
eventLabel: 'pop_up',
|
|
108
|
+
nonInteraction: false,
|
|
109
|
+
})
|
|
110
|
+
} else {
|
|
111
|
+
setErrorMessage(result.errors || result.statusText)
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
[email, locale, brand, copy.dealsType, copy.successTips, onSuccess]
|
|
115
|
+
)
|
|
122
116
|
|
|
123
117
|
useEffect(() => {
|
|
124
118
|
if (profile && profile?.email) {
|
|
@@ -135,7 +129,7 @@ export function CreditsSubscribeModal({ copy, onSuccess, ...props }: CreditsSubs
|
|
|
135
129
|
animationClassName="md:translate-y-[100vh]"
|
|
136
130
|
{...props}
|
|
137
131
|
>
|
|
138
|
-
<
|
|
132
|
+
<form className="min-l:px-[16px] flex flex-col gap-[16px] text-center" onSubmit={handleSubmit} noValidate={false}>
|
|
139
133
|
<div>
|
|
140
134
|
<Text className={classNames('mb-[24px] text-[22px] font-bold')} html={copy.title}></Text>
|
|
141
135
|
</div>
|
|
@@ -156,13 +150,15 @@ export function CreditsSubscribeModal({ copy, onSuccess, ...props }: CreditsSubs
|
|
|
156
150
|
'h-full flex-1 rounded-l-[2px] border-[1px] border-[#d8d8d8] px-[14px] py-[17px] text-[16px] font-semibold leading-[1] outline-none placeholder:text-[#999] md:h-[52px] md:rounded-[2px]',
|
|
157
151
|
!rounded && 'rounded-none'
|
|
158
152
|
)}
|
|
153
|
+
type="email"
|
|
154
|
+
required
|
|
159
155
|
placeholder={copy.placeholder}
|
|
160
156
|
value={email}
|
|
161
157
|
onChange={handleEmailChange}
|
|
162
158
|
/>
|
|
163
159
|
<Button
|
|
160
|
+
type="submit"
|
|
164
161
|
variant="primary"
|
|
165
|
-
onClick={handleSubmit}
|
|
166
162
|
loading={loading}
|
|
167
163
|
className={classNames(
|
|
168
164
|
'legacy-headless-ui-primary-button !h-full !min-w-0 !rounded-l-none !rounded-r-[2px] !px-[40px] !py-[13px]',
|
|
@@ -176,6 +172,22 @@ export function CreditsSubscribeModal({ copy, onSuccess, ...props }: CreditsSubs
|
|
|
176
172
|
/>
|
|
177
173
|
</Button>
|
|
178
174
|
</div>
|
|
175
|
+
{copy?.oneankerSubscribeAgree && (
|
|
176
|
+
<div className="flex w-full">
|
|
177
|
+
<Checkbox
|
|
178
|
+
checked={oneankerSubscribeAgree}
|
|
179
|
+
onCheckedChange={() => setOneankerSubscribeAgree(!oneankerSubscribeAgree)}
|
|
180
|
+
required
|
|
181
|
+
className="border-[#1d1d1f]"
|
|
182
|
+
/>
|
|
183
|
+
<label
|
|
184
|
+
className="ml-2 text-left text-[14px] font-semibold text-[#777] [&_a]:underline"
|
|
185
|
+
dangerouslySetInnerHTML={{
|
|
186
|
+
__html: copy.oneankerSubscribeAgree,
|
|
187
|
+
}}
|
|
188
|
+
/>
|
|
189
|
+
</div>
|
|
190
|
+
)}
|
|
179
191
|
<div className="flex w-full">
|
|
180
192
|
<Checkbox checked={policy} onCheckedChange={() => setPolicy(!policy)} required className="border-[#1d1d1f]" />
|
|
181
193
|
<label
|
|
@@ -194,7 +206,7 @@ export function CreditsSubscribeModal({ copy, onSuccess, ...props }: CreditsSubs
|
|
|
194
206
|
>
|
|
195
207
|
<Text html={successMessage || errorMessage || ''} />
|
|
196
208
|
</div>
|
|
197
|
-
</
|
|
209
|
+
</form>
|
|
198
210
|
</CreditsModalContainer>
|
|
199
211
|
)
|
|
200
212
|
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LiveChatWidget Storybook Stories
|
|
3
|
-
* 展示 LiveChat 组件的各种使用场景和配置
|
|
4
|
-
*/
|
|
5
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
6
|
-
import { LiveChatWidget } from '../components/LiveChatWidget';
|
|
7
|
-
import '../styles/livechat.css';
|
|
8
|
-
declare const meta: Meta<typeof LiveChatWidget>;
|
|
9
|
-
export default meta;
|
|
10
|
-
type Story = StoryObj<typeof LiveChatWidget>;
|
|
11
|
-
/**
|
|
12
|
-
* 默认配置 - 展示所有功能
|
|
13
|
-
*/
|
|
14
|
-
export declare const Default: Story;
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
"use strict";var n=Object.defineProperty;var c=Object.getOwnPropertyDescriptor;var d=Object.getOwnPropertyNames;var p=Object.prototype.hasOwnProperty;var g=(t,e)=>{for(var a in e)n(t,a,{get:e[a],enumerable:!0})},m=(t,e,a,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of d(e))!p.call(t,r)&&r!==a&&n(t,r,{get:()=>e[r],enumerable:!(i=c(e,r))||i.enumerable});return t};var u=t=>m(n({},"__esModule",{value:!0}),t);var f={};g(f,{Default:()=>h,default:()=>y});module.exports=u(f);var o=require("react/jsx-runtime"),s=require("../components/LiveChatWidget"),C=require("../styles/livechat.css");const v={title:"Campaign/LiveChatWidget",component:s.LiveChatWidget,parameters:{layout:"fullscreen",docs:{story:{inline:!1,iframeHeight:500},description:{component:`
|
|
2
|
-
# LiveChat \u804A\u5929\u7EC4\u4EF6
|
|
3
|
-
|
|
4
|
-
\u53EF\u590D\u7528\u7684\u6C14\u6CE1\u5F39\u7A97\u804A\u5929\u7EC4\u4EF6\uFF0C\u652F\u6301 SSE \u6D41\u5F0F\u6D88\u606F\u3001\u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\u548C\u591A\u79CD\u6D88\u606F\u7C7B\u578B\u3002
|
|
5
|
-
|
|
6
|
-
## \u529F\u80FD\u7279\u6027
|
|
7
|
-
|
|
8
|
-
- \u{1F388} **\u6C14\u6CE1\u5F39\u7A97**: \u53EF\u81EA\u5B9A\u4E49\u4F4D\u7F6E\u7684\u60AC\u6D6E\u6C14\u6CE1\u6309\u94AE
|
|
9
|
-
- \u{1F4AC} **\u6D41\u5F0F\u6D88\u606F**: \u57FA\u4E8E SSE \u7684\u5B9E\u65F6\u6D41\u5F0F\u54CD\u5E94
|
|
10
|
-
- \u{1F4E6} **\u591A\u79CD\u6D88\u606F\u7C7B\u578B**: \u6587\u672C\u3001\u5546\u54C1\u5361\u7247\u3001\u5546\u54C1\u5217\u8868\u3001\u653F\u7B56\u3001\u5FEB\u6377\u56DE\u590D\u7B49
|
|
11
|
-
- \u{1F3A8} **\u53EF\u5B9A\u5236**: \u652F\u6301\u81EA\u5B9A\u4E49\u54C1\u724C\u989C\u8272\u3001Logo\u3001\u6E32\u67D3\u5668
|
|
12
|
-
- \u{1F4F1} **\u54CD\u5E94\u5F0F**: \u79FB\u52A8\u7AEF\u5168\u5C4F\uFF0C\u684C\u9762\u7AEF\u56FA\u5B9A\u5C3A\u5BF8
|
|
13
|
-
- \u{1F4BE} **\u4F1A\u8BDD\u7BA1\u7406**: \u81EA\u52A8\u7BA1\u7406 userId \u548C sessionId
|
|
14
|
-
- \u{1F512} **\u5B89\u5168\u9632\u62A4**: \u5185\u7F6E XSS \u9632\u62A4\u548C\u8F93\u5165\u9A8C\u8BC1
|
|
15
|
-
|
|
16
|
-
## \u57FA\u7840\u7528\u6CD5
|
|
17
|
-
|
|
18
|
-
\`\`\`tsx
|
|
19
|
-
import { LiveChatWidget } from '@anker-in/campaign-ui'
|
|
20
|
-
import '@anker-in/campaign-ui/livechat.css'
|
|
21
|
-
|
|
22
|
-
function App() {
|
|
23
|
-
return (
|
|
24
|
-
<LiveChatWidget
|
|
25
|
-
apiBaseUrl="https://beta-api-livechat.anker.com"
|
|
26
|
-
site="www.eufy.com"
|
|
27
|
-
channel_code="web_homepage"
|
|
28
|
-
welcomeMessage="\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B"
|
|
29
|
-
/>
|
|
30
|
-
)
|
|
31
|
-
}
|
|
32
|
-
\`\`\`
|
|
33
|
-
|
|
34
|
-
## \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668
|
|
35
|
-
|
|
36
|
-
\`\`\`tsx
|
|
37
|
-
const customRenderers = {
|
|
38
|
-
video: {
|
|
39
|
-
render: (content) => (
|
|
40
|
-
<video src={content.url} controls className="w-full rounded" />
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
<LiveChatWidget
|
|
46
|
-
apiBaseUrl="..."
|
|
47
|
-
site="..."
|
|
48
|
-
customRenderers={customRenderers}
|
|
49
|
-
/>
|
|
50
|
-
\`\`\`
|
|
51
|
-
`}}},tags:["autodocs"],argTypes:{apiBaseUrl:{control:"text",description:"API \u57FA\u7840 URL"},headers:{control:"object",description:"\u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\uFF0C\u5C06\u5728\u6240\u6709 API \u8BF7\u6C42\u4E2D\u6DFB\u52A0",table:{defaultValue:{summary:"undefined"}}},recaptchaSitekey:{control:"text",description:"Google reCAPTCHA v3 site key\uFF0C\u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u81EA\u52A8\u542F\u7528 reCAPTCHA v3 \u9A8C\u8BC1",table:{defaultValue:{summary:"undefined"}}},recaptchaAction:{control:"text",description:"reCAPTCHA action \u540D\u79F0\uFF0C\u7528\u4E8E\u533A\u5206\u4E0D\u540C\u7684\u9A8C\u8BC1\u573A\u666F",table:{defaultValue:{summary:'"activity"'}}},site:{control:"text",description:"Shopify \u5E97\u94FA URL"},channelCode:{control:"text",description:"\u6E20\u9053\u7F16\u7801\uFF0C\u7528\u4E8E\u6807\u8BC6\u6765\u6E90\u6E20\u9053",table:{defaultValue:{summary:"undefined"}}},welcomeMessage:{control:"text",description:"\u6B22\u8FCE\u6D88\u606F",table:{defaultValue:{summary:"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F"}}},logoUrl:{control:"text",description:"Logo URL"},position:{control:"object",description:"\u6C14\u6CE1\u6309\u94AE\u4F4D\u7F6E\u5BF9\u8C61",table:{defaultValue:{summary:'{ bottom: "1.5rem", right: "1.5rem" }'}}}},args:{apiBaseUrl:"http://172.16.38.183:3003",site:"www.eufy.com",loginUserId:"test_test",welcomeMessage:"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F"}};var y=v;const h={args:{loginUserId:"test_test1",apiBaseUrl:"https://beta-api-v2-livechat.anker.com",site:"beta.soundcore.com",channelCode:"dtc",title:"eufy AI Assistant",cartId:"gid://shopify/Cart/hWNAAJweS9IkeRKib4LRy9PY?key=ff1f9b24e74b3f11f5574539e7cbbb2f",accessToken:"47b1aa2c0797043f9baba39388029d70",position:{bottom:"24px",right:"30px"},welcomeMessage:"",quickReplies:[{id:"1",label:"Product Info",value:"Tell me about your products",icon:"\u{1F4E6}"},{id:"2",label:"Track Order",value:"I want to track my order",icon:"\u{1F69A}"},{id:"3",label:"Support",value:"I need help with my device",icon:"\u{1F527}"},{id:"4",label:"Recommendations",value:"Recommend products for me",icon:"\u2B50"}],complianceConfig:{title:"Hi! I'm your eufy AI assistant.",content:"AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data.",checkboxText:`By starting to use "Live Chat", you agree to Anker's <a href="https://www.anker.com/pages/privacy-policy" target="_blank" rel="noopener noreferrer" style="text-decoration: underline;">LIVE CHAT PRIVACY NOTICE</a>.`,agreeButtonText:"Agree"},recaptchaAction:"livechat",showNewSessionButton:!0,commonText:{learnMore:"Learn More",total:"Total"},customRenderers:{video:{render:t=>{const e=t;return(0,o.jsxs)("div",{className:"w-full",children:[(0,o.jsx)("video",{src:e.url,controls:!0,className:"w-full rounded-lg",poster:e.poster,children:"Your browser does not support video playback"}),e.title&&(0,o.jsx)("p",{className:"mt-2 text-sm text-gray-600",children:e.title})]})}},image_gallery:{render:t=>{const a=t.images||[];return(0,o.jsx)("div",{className:"grid grid-cols-2 gap-2",children:a.map((i,r)=>(0,o.jsx)("div",{className:"relative aspect-square",children:(0,o.jsx)("img",{src:i.url,alt:i.alt||`Image ${r+1}`,className:"size-full rounded-lg object-cover"})},r))})}}},productCardRender:(t,e)=>{if(!t)return(0,o.jsx)("div",{style:{padding:"16px",border:"1px dashed #ccc",borderRadius:"8px",textAlign:"center"},children:(0,o.jsxs)("p",{children:["Product loading... (handle: ",e,")"]})});const a=t?.featured_image||"",i=t?.title||"",r=t?.description||"",l=t?.average_rating;return(0,o.jsx)("div",{style:{border:"2px solid #4CAF50",borderRadius:"16px",padding:"16px",margin:"12px 0",backgroundColor:"#f0f9ff",boxShadow:"0 4px 12px rgba(76, 175, 80, 0.15)"},children:(0,o.jsxs)("div",{style:{display:"flex",gap:"12px",alignItems:"center"},children:[a&&(0,o.jsx)("img",{src:a,alt:i,style:{width:"60px",height:"60px",borderRadius:"8px",objectFit:"cover"}}),(0,o.jsxs)("div",{style:{flex:1},children:[(0,o.jsx)("h4",{style:{margin:"0 0 4px 0",fontSize:"16px",fontWeight:"bold"},children:i}),r&&(0,o.jsxs)("p",{style:{margin:0,fontSize:"12px",color:"#666",lineHeight:1.4},children:[r.slice(0,80),"..."]}),l&&(0,o.jsxs)("span",{style:{fontSize:"12px",color:"#FFB800"},children:["Rating: ",l.toFixed(1)]})]}),(0,o.jsx)("button",{style:{padding:"8px 16px",backgroundColor:"#4CAF50",color:"white",borderRadius:"8px",border:"none",cursor:"pointer"},onClick:()=>console.log("View product:",e,t),children:"View"})]})})}},render:t=>(0,o.jsx)(s.LiveChatWidget,{...t,onOpen:()=>console.log("[LiveChat] Chat opened"),onClose:()=>console.log("[LiveChat] Chat closed"),onMessageSend:e=>console.log("[LiveChat] Message sent:",e),onError:e=>console.error("[LiveChat] Error:",e),onTextMessage:()=>console.log("[LiveChat] AI text message received"),onProductList:()=>console.log("[LiveChat] Product list received"),onPromotionList:()=>console.log("[LiveChat] Promotion list received"),onAddToCart:e=>{console.log("[LiveChat] Add to cart:",e),alert(`Added "${e.title}" to cart!`)},onCart:(e,a)=>{console.log("[LiveChat] Cart clicked:",{cartId:e,checkoutUrl:a}),alert(`Cart ID: ${e}`)}})};
|
|
52
|
-
//# sourceMappingURL=LiveChatWidget.stories.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/stories/LiveChatWidget.stories.tsx"],
|
|
4
|
-
"sourcesContent": ["/**\n * LiveChatWidget Storybook Stories\n * \u5C55\u793A LiveChat \u7EC4\u4EF6\u7684\u5404\u79CD\u4F7F\u7528\u573A\u666F\u548C\u914D\u7F6E\n */\n\nimport type { Meta, StoryObj } from '@storybook/react'\nimport { LiveChatWidget } from '../components/LiveChatWidget'\nimport type { MessageRenderer, MessageContent } from '../components/LiveChatWidget'\nimport '../styles/livechat.css'\n\nconst meta: Meta<typeof LiveChatWidget> = {\n title: 'Campaign/LiveChatWidget',\n component: LiveChatWidget,\n parameters: {\n layout: 'fullscreen',\n docs: {\n story: {\n inline: false,\n iframeHeight: 500,\n },\n description: {\n component: `\n# LiveChat \u804A\u5929\u7EC4\u4EF6\n\n\u53EF\u590D\u7528\u7684\u6C14\u6CE1\u5F39\u7A97\u804A\u5929\u7EC4\u4EF6\uFF0C\u652F\u6301 SSE \u6D41\u5F0F\u6D88\u606F\u3001\u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\u548C\u591A\u79CD\u6D88\u606F\u7C7B\u578B\u3002\n\n## \u529F\u80FD\u7279\u6027\n\n- \uD83C\uDF88 **\u6C14\u6CE1\u5F39\u7A97**: \u53EF\u81EA\u5B9A\u4E49\u4F4D\u7F6E\u7684\u60AC\u6D6E\u6C14\u6CE1\u6309\u94AE\n- \uD83D\uDCAC **\u6D41\u5F0F\u6D88\u606F**: \u57FA\u4E8E SSE \u7684\u5B9E\u65F6\u6D41\u5F0F\u54CD\u5E94\n- \uD83D\uDCE6 **\u591A\u79CD\u6D88\u606F\u7C7B\u578B**: \u6587\u672C\u3001\u5546\u54C1\u5361\u7247\u3001\u5546\u54C1\u5217\u8868\u3001\u653F\u7B56\u3001\u5FEB\u6377\u56DE\u590D\u7B49\n- \uD83C\uDFA8 **\u53EF\u5B9A\u5236**: \u652F\u6301\u81EA\u5B9A\u4E49\u54C1\u724C\u989C\u8272\u3001Logo\u3001\u6E32\u67D3\u5668\n- \uD83D\uDCF1 **\u54CD\u5E94\u5F0F**: \u79FB\u52A8\u7AEF\u5168\u5C4F\uFF0C\u684C\u9762\u7AEF\u56FA\u5B9A\u5C3A\u5BF8\n- \uD83D\uDCBE **\u4F1A\u8BDD\u7BA1\u7406**: \u81EA\u52A8\u7BA1\u7406 userId \u548C sessionId\n- \uD83D\uDD12 **\u5B89\u5168\u9632\u62A4**: \u5185\u7F6E XSS \u9632\u62A4\u548C\u8F93\u5165\u9A8C\u8BC1\n\n## \u57FA\u7840\u7528\u6CD5\n\n\\`\\`\\`tsx\nimport { LiveChatWidget } from '@anker-in/campaign-ui'\nimport '@anker-in/campaign-ui/livechat.css'\n\nfunction App() {\n return (\n <LiveChatWidget\n apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n site=\"www.eufy.com\"\n channel_code=\"web_homepage\"\n welcomeMessage=\"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\"\n />\n )\n}\n\\`\\`\\`\n\n## \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\n\n\\`\\`\\`tsx\nconst customRenderers = {\n video: {\n render: (content) => (\n <video src={content.url} controls className=\"w-full rounded\" />\n )\n }\n}\n\n<LiveChatWidget\n apiBaseUrl=\"...\"\n site=\"...\"\n customRenderers={customRenderers}\n/>\n\\`\\`\\`\n `,\n },\n },\n },\n tags: ['autodocs'],\n argTypes: {\n apiBaseUrl: {\n control: 'text',\n description: 'API \u57FA\u7840 URL',\n },\n headers: {\n control: 'object',\n description: '\u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\uFF0C\u5C06\u5728\u6240\u6709 API \u8BF7\u6C42\u4E2D\u6DFB\u52A0',\n table: {\n defaultValue: { summary: 'undefined' },\n },\n },\n recaptchaSitekey: {\n control: 'text',\n description: 'Google reCAPTCHA v3 site key\uFF0C\u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u81EA\u52A8\u542F\u7528 reCAPTCHA v3 \u9A8C\u8BC1',\n table: {\n defaultValue: { summary: 'undefined' },\n },\n },\n recaptchaAction: {\n control: 'text',\n description: 'reCAPTCHA action \u540D\u79F0\uFF0C\u7528\u4E8E\u533A\u5206\u4E0D\u540C\u7684\u9A8C\u8BC1\u573A\u666F',\n table: {\n defaultValue: { summary: '\"activity\"' },\n },\n },\n site: {\n control: 'text',\n description: 'Shopify \u5E97\u94FA URL',\n },\n channelCode: {\n control: 'text',\n description: '\u6E20\u9053\u7F16\u7801\uFF0C\u7528\u4E8E\u6807\u8BC6\u6765\u6E90\u6E20\u9053',\n table: {\n defaultValue: { summary: 'undefined' },\n },\n },\n welcomeMessage: {\n control: 'text',\n description: '\u6B22\u8FCE\u6D88\u606F',\n table: {\n defaultValue: { summary: '\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F' },\n },\n },\n logoUrl: {\n control: 'text',\n description: 'Logo URL',\n },\n position: {\n control: 'object',\n description: '\u6C14\u6CE1\u6309\u94AE\u4F4D\u7F6E\u5BF9\u8C61',\n table: {\n defaultValue: { summary: '{ bottom: \"1.5rem\", right: \"1.5rem\" }' },\n },\n },\n },\n args: {\n apiBaseUrl: 'http://172.16.38.183:3003',\n site: 'www.eufy.com',\n loginUserId: 'test_test',\n welcomeMessage: '\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F',\n },\n}\n\nexport default meta\ntype Story = StoryObj<typeof LiveChatWidget>\n\n/**\n * \u9ED8\u8BA4\u914D\u7F6E - \u5C55\u793A\u6240\u6709\u529F\u80FD\n */\nexport const Default: Story = {\n args: {\n // \u57FA\u7840\u914D\u7F6E\n loginUserId: 'test_test1',\n apiBaseUrl: 'https://beta-api-v2-livechat.anker.com',\n site: 'beta.soundcore.com',\n channelCode: 'dtc',\n title: 'eufy AI Assistant',\n cartId: 'gid://shopify/Cart/hWNAAJweS9IkeRKib4LRy9PY?key=ff1f9b24e74b3f11f5574539e7cbbb2f',\n accessToken: '47b1aa2c0797043f9baba39388029d70',\n\n // \u81EA\u5B9A\u4E49\u4F4D\u7F6E\n position: { bottom: '24px', right: '30px' },\n\n // \u6B22\u8FCE\u6D88\u606F\n welcomeMessage: '',\n\n // \u5FEB\u6377\u56DE\u590D\n quickReplies: [\n { id: '1', label: 'Product Info', value: 'Tell me about your products', icon: '\uD83D\uDCE6' },\n { id: '2', label: 'Track Order', value: 'I want to track my order', icon: '\uD83D\uDE9A' },\n { id: '3', label: 'Support', value: 'I need help with my device', icon: '\uD83D\uDD27' },\n { id: '4', label: 'Recommendations', value: 'Recommend products for me', icon: '\u2B50' },\n ],\n\n // \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\n complianceConfig: {\n title: \"Hi! I'm your eufy AI assistant.\",\n content:\n 'AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data.',\n checkboxText:\n 'By starting to use \"Live Chat\", you agree to Anker\\'s <a href=\"https://www.anker.com/pages/privacy-policy\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"text-decoration: underline;\">LIVE CHAT PRIVACY NOTICE</a>.',\n agreeButtonText: 'Agree',\n },\n\n // reCAPTCHA \u914D\u7F6E\uFF08\u53D6\u6D88\u6CE8\u91CA\u4EE5\u542F\u7528\uFF09\n // recaptchaSitekey: '6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14',\n recaptchaAction: 'livechat',\n\n // \u663E\u793A\u65B0\u4F1A\u8BDD\u6309\u94AE\n showNewSessionButton: true,\n\n // \u81EA\u5B9A\u4E49\u6587\u6848\n commonText: {\n learnMore: 'Learn More',\n total: 'Total',\n },\n\n // \u81EA\u5B9A\u4E49\u6D88\u606F\u6E32\u67D3\u5668\n customRenderers: {\n video: {\n render: (content: MessageContent) => {\n const videoContent = content as any\n return (\n <div className=\"w-full\">\n <video src={videoContent.url} controls className=\"w-full rounded-lg\" poster={videoContent.poster}>\n Your browser does not support video playback\n </video>\n {videoContent.title && <p className=\"mt-2 text-sm text-gray-600\">{videoContent.title}</p>}\n </div>\n )\n },\n } as MessageRenderer,\n image_gallery: {\n render: (content: MessageContent) => {\n const galleryContent = content as any\n const images = galleryContent.images || []\n return (\n <div className=\"grid grid-cols-2 gap-2\">\n {images.map((image: any, index: number) => (\n <div key={index} className=\"relative aspect-square\">\n <img\n src={image.url}\n alt={image.alt || `Image ${index + 1}`}\n className=\"size-full rounded-lg object-cover\"\n />\n </div>\n ))}\n </div>\n )\n },\n } as MessageRenderer,\n },\n\n // \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\n productCardRender: (product, productHandle) => {\n // product \u53EF\u80FD\u4E3A undefined\uFF0C\u6B64\u65F6\u53EF\u7528 productHandle \u67E5\u8BE2\n if (!product) {\n return (\n <div style={{ padding: '16px', border: '1px dashed #ccc', borderRadius: '8px', textAlign: 'center' }}>\n <p>Product loading... (handle: {productHandle})</p>\n </div>\n )\n }\n\n const imageUrl = product?.featured_image || ''\n const title = product?.title || ''\n const description = product?.description || ''\n const averageRating = product?.average_rating\n\n return (\n <div\n style={{\n border: '2px solid #4CAF50',\n borderRadius: '16px',\n padding: '16px',\n margin: '12px 0',\n backgroundColor: '#f0f9ff',\n boxShadow: '0 4px 12px rgba(76, 175, 80, 0.15)',\n }}\n >\n <div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>\n {imageUrl && (\n <img\n src={imageUrl}\n alt={title}\n style={{ width: '60px', height: '60px', borderRadius: '8px', objectFit: 'cover' }}\n />\n )}\n <div style={{ flex: 1 }}>\n <h4 style={{ margin: '0 0 4px 0', fontSize: '16px', fontWeight: 'bold' }}>{title}</h4>\n {description && (\n <p style={{ margin: 0, fontSize: '12px', color: '#666', lineHeight: 1.4 }}>\n {description.slice(0, 80)}...\n </p>\n )}\n {averageRating && (\n <span style={{ fontSize: '12px', color: '#FFB800' }}>Rating: {averageRating.toFixed(1)}</span>\n )}\n </div>\n <button\n style={{\n padding: '8px 16px',\n backgroundColor: '#4CAF50',\n color: 'white',\n borderRadius: '8px',\n border: 'none',\n cursor: 'pointer',\n }}\n onClick={() => console.log('View product:', productHandle, product)}\n >\n View\n </button>\n </div>\n </div>\n )\n },\n },\n\n render: args => (\n <LiveChatWidget\n {...args}\n // \u6240\u6709\u4E8B\u4EF6\u56DE\u8C03\n onOpen={() => console.log('[LiveChat] Chat opened')}\n onClose={() => console.log('[LiveChat] Chat closed')}\n onMessageSend={(message: string) => console.log('[LiveChat] Message sent:', message)}\n onError={(error: Error) => console.error('[LiveChat] Error:', error)}\n onTextMessage={() => console.log('[LiveChat] AI text message received')}\n onProductList={() => console.log('[LiveChat] Product list received')}\n onPromotionList={() => console.log('[LiveChat] Promotion list received')}\n onAddToCart={(product: any) => {\n console.log('[LiveChat] Add to cart:', product)\n alert(`Added \"${product.title}\" to cart!`)\n }}\n onCart={(cartId: string, checkoutUrl?: string) => {\n console.log('[LiveChat] Cart clicked:', { cartId, checkoutUrl })\n alert(`Cart ID: ${cartId}`)\n }}\n />\n ),\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["LiveChatWidget_stories_exports", "__export", "Default", "LiveChatWidget_stories_default", "__toCommonJS", "import_jsx_runtime", "import_LiveChatWidget", "import_livechat", "meta", "content", "videoContent", "images", "image", "index", "product", "productHandle", "imageUrl", "title", "description", "averageRating", "args", "message", "error", "cartId", "checkoutUrl"]
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
-
import { Chat } from '../components/index';
|
|
3
|
-
import '../styles/chat.css';
|
|
4
|
-
type Story = StoryObj<typeof meta>;
|
|
5
|
-
declare const meta: Meta<typeof Chat>;
|
|
6
|
-
export default meta;
|
|
7
|
-
export declare const Default: Story;
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
"use strict";var i=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var n=Object.getOwnPropertyNames;var h=Object.prototype.hasOwnProperty;var c=(t,o)=>{for(var a in o)i(t,a,{get:o[a],enumerable:!0})},y=(t,o,a,r)=>{if(o&&typeof o=="object"||typeof o=="function")for(let e of n(o))!h.call(t,e)&&e!==a&&i(t,e,{get:()=>o[e],enumerable:!(r=m(o,e))||r.enumerable});return t};var l=t=>y(i({},"__esModule",{value:!0}),t);var C={};c(C,{Default:()=>d,default:()=>u});module.exports=l(C);var p=require("react/jsx-runtime"),s=require("../components/index"),D=require("../styles/chat.css");const f={title:"Campaign/Chat",component:s.Chat,args:{title:"DTC Live Chat",runtimeUrl:"https://beta-api-livechat.anker.com",shopifyDomain:"soundcoreusa.myshopify.com"},tags:["autodocs"],decorators:[t=>(0,p.jsx)("div",{style:{height:"700px"},children:(0,p.jsx)(t,{})})]};var u=f;const d={args:{title:"DTC Live Chat",runtimeUrl:"https://beta-api-livechat.anker.com",shopifyDomain:"soundcoreusa.myshopify.com",userId:"arno7",showResponseButton:"follow",lang:{popupTip:"Hi ! Welcome to soundcore Innovations live chat\uFF01",popupTipTimeout:[3e3,6e3]}}};
|
|
2
|
-
//# sourceMappingURL=chat.stories.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/stories/chat.stories.tsx"],
|
|
4
|
-
"sourcesContent": ["import React from 'react'\nimport type { Meta, StoryObj } from '@storybook/react'\nimport { Chat } from '../components/index'\nimport '../styles/chat.css'\n\ntype Story = StoryObj<typeof meta>\n\n// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export\nconst meta: Meta<typeof Chat> = {\n // \u540C\u65F6\u5B9A\u4E49\u4E86\u9875\u9762\u5DE6\u4FA7\u83DC\u5355\n title: 'Campaign/Chat',\n component: Chat,\n args: {\n title: 'DTC Live Chat',\n runtimeUrl: 'https://beta-api-livechat.anker.com',\n shopifyDomain: 'soundcoreusa.myshopify.com',\n },\n // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs\n tags: ['autodocs'],\n // More on argTypes: https://storybook.js.org/docs/api/argtypes\n decorators: [\n Story => (\n <div style={{ height: '700px' }}>\n <Story />\n </div>\n ),\n ],\n} satisfies Meta<typeof Chat>\n\nexport default meta\n\nexport const Default: Story = {\n args: {\n title: 'DTC Live Chat',\n runtimeUrl: 'https://beta-api-livechat.anker.com',\n shopifyDomain: 'soundcoreusa.myshopify.com',\n userId: 'arno7',\n showResponseButton: 'follow',\n lang: { popupTip: 'Hi ! Welcome to soundcore Innovations live chat\uFF01', popupTipTimeout: [3000, 6000] },\n },\n}\n"],
|
|
5
|
-
"mappings": "yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,aAAAE,EAAA,YAAAC,IAAA,eAAAC,EAAAJ,GAuBQ,IAAAK,EAAA,6BArBRC,EAAqB,+BACrBC,EAAO,8BAKP,MAAMC,EAA0B,CAE9B,MAAO,gBACP,UAAW,OACX,KAAM,CACJ,MAAO,gBACP,WAAY,sCACZ,cAAe,4BACjB,EAEA,KAAM,CAAC,UAAU,EAEjB,WAAY,CACVC,MACE,OAAC,OAAI,MAAO,CAAE,OAAQ,OAAQ,EAC5B,mBAACA,EAAA,EAAM,EACT,CAEJ,CACF,EAEA,IAAON,EAAQK,EAER,MAAMN,EAAiB,CAC5B,KAAM,CACJ,MAAO,gBACP,WAAY,sCACZ,cAAe,6BACf,OAAQ,QACR,mBAAoB,SACpB,KAAM,CAAE,SAAU,wDAAoD,gBAAiB,CAAC,IAAM,GAAI,CAAE,CACtG,CACF",
|
|
6
|
-
"names": ["chat_stories_exports", "__export", "Default", "chat_stories_default", "__toCommonJS", "import_jsx_runtime", "import_components", "import_chat", "meta", "Story"]
|
|
7
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LiveChatWidget Storybook Stories
|
|
3
|
-
* 展示 LiveChat 组件的各种使用场景和配置
|
|
4
|
-
*/
|
|
5
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
6
|
-
import { LiveChatWidget } from '../components/LiveChatWidget';
|
|
7
|
-
import '../styles/livechat.css';
|
|
8
|
-
declare const meta: Meta<typeof LiveChatWidget>;
|
|
9
|
-
export default meta;
|
|
10
|
-
type Story = StoryObj<typeof LiveChatWidget>;
|
|
11
|
-
/**
|
|
12
|
-
* 默认配置 - 展示所有功能
|
|
13
|
-
*/
|
|
14
|
-
export declare const Default: Story;
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import{jsx as o,jsxs as r}from"react/jsx-runtime";import{LiveChatWidget as l}from"../components/LiveChatWidget";import"../styles/livechat.css";const c={title:"Campaign/LiveChatWidget",component:l,parameters:{layout:"fullscreen",docs:{story:{inline:!1,iframeHeight:500},description:{component:`
|
|
2
|
-
# LiveChat \u804A\u5929\u7EC4\u4EF6
|
|
3
|
-
|
|
4
|
-
\u53EF\u590D\u7528\u7684\u6C14\u6CE1\u5F39\u7A97\u804A\u5929\u7EC4\u4EF6\uFF0C\u652F\u6301 SSE \u6D41\u5F0F\u6D88\u606F\u3001\u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\u548C\u591A\u79CD\u6D88\u606F\u7C7B\u578B\u3002
|
|
5
|
-
|
|
6
|
-
## \u529F\u80FD\u7279\u6027
|
|
7
|
-
|
|
8
|
-
- \u{1F388} **\u6C14\u6CE1\u5F39\u7A97**: \u53EF\u81EA\u5B9A\u4E49\u4F4D\u7F6E\u7684\u60AC\u6D6E\u6C14\u6CE1\u6309\u94AE
|
|
9
|
-
- \u{1F4AC} **\u6D41\u5F0F\u6D88\u606F**: \u57FA\u4E8E SSE \u7684\u5B9E\u65F6\u6D41\u5F0F\u54CD\u5E94
|
|
10
|
-
- \u{1F4E6} **\u591A\u79CD\u6D88\u606F\u7C7B\u578B**: \u6587\u672C\u3001\u5546\u54C1\u5361\u7247\u3001\u5546\u54C1\u5217\u8868\u3001\u653F\u7B56\u3001\u5FEB\u6377\u56DE\u590D\u7B49
|
|
11
|
-
- \u{1F3A8} **\u53EF\u5B9A\u5236**: \u652F\u6301\u81EA\u5B9A\u4E49\u54C1\u724C\u989C\u8272\u3001Logo\u3001\u6E32\u67D3\u5668
|
|
12
|
-
- \u{1F4F1} **\u54CD\u5E94\u5F0F**: \u79FB\u52A8\u7AEF\u5168\u5C4F\uFF0C\u684C\u9762\u7AEF\u56FA\u5B9A\u5C3A\u5BF8
|
|
13
|
-
- \u{1F4BE} **\u4F1A\u8BDD\u7BA1\u7406**: \u81EA\u52A8\u7BA1\u7406 userId \u548C sessionId
|
|
14
|
-
- \u{1F512} **\u5B89\u5168\u9632\u62A4**: \u5185\u7F6E XSS \u9632\u62A4\u548C\u8F93\u5165\u9A8C\u8BC1
|
|
15
|
-
|
|
16
|
-
## \u57FA\u7840\u7528\u6CD5
|
|
17
|
-
|
|
18
|
-
\`\`\`tsx
|
|
19
|
-
import { LiveChatWidget } from '@anker-in/campaign-ui'
|
|
20
|
-
import '@anker-in/campaign-ui/livechat.css'
|
|
21
|
-
|
|
22
|
-
function App() {
|
|
23
|
-
return (
|
|
24
|
-
<LiveChatWidget
|
|
25
|
-
apiBaseUrl="https://beta-api-livechat.anker.com"
|
|
26
|
-
site="www.eufy.com"
|
|
27
|
-
channel_code="web_homepage"
|
|
28
|
-
welcomeMessage="\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B"
|
|
29
|
-
/>
|
|
30
|
-
)
|
|
31
|
-
}
|
|
32
|
-
\`\`\`
|
|
33
|
-
|
|
34
|
-
## \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668
|
|
35
|
-
|
|
36
|
-
\`\`\`tsx
|
|
37
|
-
const customRenderers = {
|
|
38
|
-
video: {
|
|
39
|
-
render: (content) => (
|
|
40
|
-
<video src={content.url} controls className="w-full rounded" />
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
<LiveChatWidget
|
|
46
|
-
apiBaseUrl="..."
|
|
47
|
-
site="..."
|
|
48
|
-
customRenderers={customRenderers}
|
|
49
|
-
/>
|
|
50
|
-
\`\`\`
|
|
51
|
-
`}}},tags:["autodocs"],argTypes:{apiBaseUrl:{control:"text",description:"API \u57FA\u7840 URL"},headers:{control:"object",description:"\u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\uFF0C\u5C06\u5728\u6240\u6709 API \u8BF7\u6C42\u4E2D\u6DFB\u52A0",table:{defaultValue:{summary:"undefined"}}},recaptchaSitekey:{control:"text",description:"Google reCAPTCHA v3 site key\uFF0C\u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u81EA\u52A8\u542F\u7528 reCAPTCHA v3 \u9A8C\u8BC1",table:{defaultValue:{summary:"undefined"}}},recaptchaAction:{control:"text",description:"reCAPTCHA action \u540D\u79F0\uFF0C\u7528\u4E8E\u533A\u5206\u4E0D\u540C\u7684\u9A8C\u8BC1\u573A\u666F",table:{defaultValue:{summary:'"activity"'}}},site:{control:"text",description:"Shopify \u5E97\u94FA URL"},channelCode:{control:"text",description:"\u6E20\u9053\u7F16\u7801\uFF0C\u7528\u4E8E\u6807\u8BC6\u6765\u6E90\u6E20\u9053",table:{defaultValue:{summary:"undefined"}}},welcomeMessage:{control:"text",description:"\u6B22\u8FCE\u6D88\u606F",table:{defaultValue:{summary:"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F"}}},logoUrl:{control:"text",description:"Logo URL"},position:{control:"object",description:"\u6C14\u6CE1\u6309\u94AE\u4F4D\u7F6E\u5BF9\u8C61",table:{defaultValue:{summary:'{ bottom: "1.5rem", right: "1.5rem" }'}}}},args:{apiBaseUrl:"http://172.16.38.183:3003",site:"www.eufy.com",loginUserId:"test_test",welcomeMessage:"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F"}};var g=c;const m={args:{loginUserId:"test_test1",apiBaseUrl:"https://beta-api-v2-livechat.anker.com",site:"beta.soundcore.com",channelCode:"dtc",title:"eufy AI Assistant",cartId:"gid://shopify/Cart/hWNAAJweS9IkeRKib4LRy9PY?key=ff1f9b24e74b3f11f5574539e7cbbb2f",accessToken:"47b1aa2c0797043f9baba39388029d70",position:{bottom:"24px",right:"30px"},welcomeMessage:"",quickReplies:[{id:"1",label:"Product Info",value:"Tell me about your products",icon:"\u{1F4E6}"},{id:"2",label:"Track Order",value:"I want to track my order",icon:"\u{1F69A}"},{id:"3",label:"Support",value:"I need help with my device",icon:"\u{1F527}"},{id:"4",label:"Recommendations",value:"Recommend products for me",icon:"\u2B50"}],complianceConfig:{title:"Hi! I'm your eufy AI assistant.",content:"AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data.",checkboxText:`By starting to use "Live Chat", you agree to Anker's <a href="https://www.anker.com/pages/privacy-policy" target="_blank" rel="noopener noreferrer" style="text-decoration: underline;">LIVE CHAT PRIVACY NOTICE</a>.`,agreeButtonText:"Agree"},recaptchaAction:"livechat",showNewSessionButton:!0,commonText:{learnMore:"Learn More",total:"Total"},customRenderers:{video:{render:t=>{const e=t;return r("div",{className:"w-full",children:[o("video",{src:e.url,controls:!0,className:"w-full rounded-lg",poster:e.poster,children:"Your browser does not support video playback"}),e.title&&o("p",{className:"mt-2 text-sm text-gray-600",children:e.title})]})}},image_gallery:{render:t=>{const a=t.images||[];return o("div",{className:"grid grid-cols-2 gap-2",children:a.map((i,n)=>o("div",{className:"relative aspect-square",children:o("img",{src:i.url,alt:i.alt||`Image ${n+1}`,className:"size-full rounded-lg object-cover"})},n))})}}},productCardRender:(t,e)=>{if(!t)return o("div",{style:{padding:"16px",border:"1px dashed #ccc",borderRadius:"8px",textAlign:"center"},children:r("p",{children:["Product loading... (handle: ",e,")"]})});const a=t?.featured_image||"",i=t?.title||"",n=t?.description||"",s=t?.average_rating;return o("div",{style:{border:"2px solid #4CAF50",borderRadius:"16px",padding:"16px",margin:"12px 0",backgroundColor:"#f0f9ff",boxShadow:"0 4px 12px rgba(76, 175, 80, 0.15)"},children:r("div",{style:{display:"flex",gap:"12px",alignItems:"center"},children:[a&&o("img",{src:a,alt:i,style:{width:"60px",height:"60px",borderRadius:"8px",objectFit:"cover"}}),r("div",{style:{flex:1},children:[o("h4",{style:{margin:"0 0 4px 0",fontSize:"16px",fontWeight:"bold"},children:i}),n&&r("p",{style:{margin:0,fontSize:"12px",color:"#666",lineHeight:1.4},children:[n.slice(0,80),"..."]}),s&&r("span",{style:{fontSize:"12px",color:"#FFB800"},children:["Rating: ",s.toFixed(1)]})]}),o("button",{style:{padding:"8px 16px",backgroundColor:"#4CAF50",color:"white",borderRadius:"8px",border:"none",cursor:"pointer"},onClick:()=>console.log("View product:",e,t),children:"View"})]})})}},render:t=>o(l,{...t,onOpen:()=>console.log("[LiveChat] Chat opened"),onClose:()=>console.log("[LiveChat] Chat closed"),onMessageSend:e=>console.log("[LiveChat] Message sent:",e),onError:e=>console.error("[LiveChat] Error:",e),onTextMessage:()=>console.log("[LiveChat] AI text message received"),onProductList:()=>console.log("[LiveChat] Product list received"),onPromotionList:()=>console.log("[LiveChat] Promotion list received"),onAddToCart:e=>{console.log("[LiveChat] Add to cart:",e),alert(`Added "${e.title}" to cart!`)},onCart:(e,a)=>{console.log("[LiveChat] Cart clicked:",{cartId:e,checkoutUrl:a}),alert(`Cart ID: ${e}`)}})};export{m as Default,g as default};
|
|
52
|
-
//# sourceMappingURL=LiveChatWidget.stories.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/stories/LiveChatWidget.stories.tsx"],
|
|
4
|
-
"sourcesContent": ["/**\n * LiveChatWidget Storybook Stories\n * \u5C55\u793A LiveChat \u7EC4\u4EF6\u7684\u5404\u79CD\u4F7F\u7528\u573A\u666F\u548C\u914D\u7F6E\n */\n\nimport type { Meta, StoryObj } from '@storybook/react'\nimport { LiveChatWidget } from '../components/LiveChatWidget'\nimport type { MessageRenderer, MessageContent } from '../components/LiveChatWidget'\nimport '../styles/livechat.css'\n\nconst meta: Meta<typeof LiveChatWidget> = {\n title: 'Campaign/LiveChatWidget',\n component: LiveChatWidget,\n parameters: {\n layout: 'fullscreen',\n docs: {\n story: {\n inline: false,\n iframeHeight: 500,\n },\n description: {\n component: `\n# LiveChat \u804A\u5929\u7EC4\u4EF6\n\n\u53EF\u590D\u7528\u7684\u6C14\u6CE1\u5F39\u7A97\u804A\u5929\u7EC4\u4EF6\uFF0C\u652F\u6301 SSE \u6D41\u5F0F\u6D88\u606F\u3001\u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\u548C\u591A\u79CD\u6D88\u606F\u7C7B\u578B\u3002\n\n## \u529F\u80FD\u7279\u6027\n\n- \uD83C\uDF88 **\u6C14\u6CE1\u5F39\u7A97**: \u53EF\u81EA\u5B9A\u4E49\u4F4D\u7F6E\u7684\u60AC\u6D6E\u6C14\u6CE1\u6309\u94AE\n- \uD83D\uDCAC **\u6D41\u5F0F\u6D88\u606F**: \u57FA\u4E8E SSE \u7684\u5B9E\u65F6\u6D41\u5F0F\u54CD\u5E94\n- \uD83D\uDCE6 **\u591A\u79CD\u6D88\u606F\u7C7B\u578B**: \u6587\u672C\u3001\u5546\u54C1\u5361\u7247\u3001\u5546\u54C1\u5217\u8868\u3001\u653F\u7B56\u3001\u5FEB\u6377\u56DE\u590D\u7B49\n- \uD83C\uDFA8 **\u53EF\u5B9A\u5236**: \u652F\u6301\u81EA\u5B9A\u4E49\u54C1\u724C\u989C\u8272\u3001Logo\u3001\u6E32\u67D3\u5668\n- \uD83D\uDCF1 **\u54CD\u5E94\u5F0F**: \u79FB\u52A8\u7AEF\u5168\u5C4F\uFF0C\u684C\u9762\u7AEF\u56FA\u5B9A\u5C3A\u5BF8\n- \uD83D\uDCBE **\u4F1A\u8BDD\u7BA1\u7406**: \u81EA\u52A8\u7BA1\u7406 userId \u548C sessionId\n- \uD83D\uDD12 **\u5B89\u5168\u9632\u62A4**: \u5185\u7F6E XSS \u9632\u62A4\u548C\u8F93\u5165\u9A8C\u8BC1\n\n## \u57FA\u7840\u7528\u6CD5\n\n\\`\\`\\`tsx\nimport { LiveChatWidget } from '@anker-in/campaign-ui'\nimport '@anker-in/campaign-ui/livechat.css'\n\nfunction App() {\n return (\n <LiveChatWidget\n apiBaseUrl=\"https://beta-api-livechat.anker.com\"\n site=\"www.eufy.com\"\n channel_code=\"web_homepage\"\n welcomeMessage=\"\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\"\n />\n )\n}\n\\`\\`\\`\n\n## \u81EA\u5B9A\u4E49\u6E32\u67D3\u5668\n\n\\`\\`\\`tsx\nconst customRenderers = {\n video: {\n render: (content) => (\n <video src={content.url} controls className=\"w-full rounded\" />\n )\n }\n}\n\n<LiveChatWidget\n apiBaseUrl=\"...\"\n site=\"...\"\n customRenderers={customRenderers}\n/>\n\\`\\`\\`\n `,\n },\n },\n },\n tags: ['autodocs'],\n argTypes: {\n apiBaseUrl: {\n control: 'text',\n description: 'API \u57FA\u7840 URL',\n },\n headers: {\n control: 'object',\n description: '\u81EA\u5B9A\u4E49\u8BF7\u6C42\u5934\uFF0C\u5C06\u5728\u6240\u6709 API \u8BF7\u6C42\u4E2D\u6DFB\u52A0',\n table: {\n defaultValue: { summary: 'undefined' },\n },\n },\n recaptchaSitekey: {\n control: 'text',\n description: 'Google reCAPTCHA v3 site key\uFF0C\u63D0\u4F9B\u6B64\u53C2\u6570\u5C06\u81EA\u52A8\u542F\u7528 reCAPTCHA v3 \u9A8C\u8BC1',\n table: {\n defaultValue: { summary: 'undefined' },\n },\n },\n recaptchaAction: {\n control: 'text',\n description: 'reCAPTCHA action \u540D\u79F0\uFF0C\u7528\u4E8E\u533A\u5206\u4E0D\u540C\u7684\u9A8C\u8BC1\u573A\u666F',\n table: {\n defaultValue: { summary: '\"activity\"' },\n },\n },\n site: {\n control: 'text',\n description: 'Shopify \u5E97\u94FA URL',\n },\n channelCode: {\n control: 'text',\n description: '\u6E20\u9053\u7F16\u7801\uFF0C\u7528\u4E8E\u6807\u8BC6\u6765\u6E90\u6E20\u9053',\n table: {\n defaultValue: { summary: 'undefined' },\n },\n },\n welcomeMessage: {\n control: 'text',\n description: '\u6B22\u8FCE\u6D88\u606F',\n table: {\n defaultValue: { summary: '\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F' },\n },\n },\n logoUrl: {\n control: 'text',\n description: 'Logo URL',\n },\n position: {\n control: 'object',\n description: '\u6C14\u6CE1\u6309\u94AE\u4F4D\u7F6E\u5BF9\u8C61',\n table: {\n defaultValue: { summary: '{ bottom: \"1.5rem\", right: \"1.5rem\" }' },\n },\n },\n },\n args: {\n apiBaseUrl: 'http://172.16.38.183:3003',\n site: 'www.eufy.com',\n loginUserId: 'test_test',\n welcomeMessage: '\u4F60\u597D\uFF01\u6211\u662F AI \u52A9\u624B\uFF0C\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u52A9\u4F60\u7684\u5417\uFF1F',\n },\n}\n\nexport default meta\ntype Story = StoryObj<typeof LiveChatWidget>\n\n/**\n * \u9ED8\u8BA4\u914D\u7F6E - \u5C55\u793A\u6240\u6709\u529F\u80FD\n */\nexport const Default: Story = {\n args: {\n // \u57FA\u7840\u914D\u7F6E\n loginUserId: 'test_test1',\n apiBaseUrl: 'https://beta-api-v2-livechat.anker.com',\n site: 'beta.soundcore.com',\n channelCode: 'dtc',\n title: 'eufy AI Assistant',\n cartId: 'gid://shopify/Cart/hWNAAJweS9IkeRKib4LRy9PY?key=ff1f9b24e74b3f11f5574539e7cbbb2f',\n accessToken: '47b1aa2c0797043f9baba39388029d70',\n\n // \u81EA\u5B9A\u4E49\u4F4D\u7F6E\n position: { bottom: '24px', right: '30px' },\n\n // \u6B22\u8FCE\u6D88\u606F\n welcomeMessage: '',\n\n // \u5FEB\u6377\u56DE\u590D\n quickReplies: [\n { id: '1', label: 'Product Info', value: 'Tell me about your products', icon: '\uD83D\uDCE6' },\n { id: '2', label: 'Track Order', value: 'I want to track my order', icon: '\uD83D\uDE9A' },\n { id: '3', label: 'Support', value: 'I need help with my device', icon: '\uD83D\uDD27' },\n { id: '4', label: 'Recommendations', value: 'Recommend products for me', icon: '\u2B50' },\n ],\n\n // \u6CD5\u89C4\u534F\u8BAE\u5F39\u7A97\n complianceConfig: {\n title: \"Hi! I'm your eufy AI assistant.\",\n content:\n 'AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data.',\n checkboxText:\n 'By starting to use \"Live Chat\", you agree to Anker\\'s <a href=\"https://www.anker.com/pages/privacy-policy\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"text-decoration: underline;\">LIVE CHAT PRIVACY NOTICE</a>.',\n agreeButtonText: 'Agree',\n },\n\n // reCAPTCHA \u914D\u7F6E\uFF08\u53D6\u6D88\u6CE8\u91CA\u4EE5\u542F\u7528\uFF09\n // recaptchaSitekey: '6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14',\n recaptchaAction: 'livechat',\n\n // \u663E\u793A\u65B0\u4F1A\u8BDD\u6309\u94AE\n showNewSessionButton: true,\n\n // \u81EA\u5B9A\u4E49\u6587\u6848\n commonText: {\n learnMore: 'Learn More',\n total: 'Total',\n },\n\n // \u81EA\u5B9A\u4E49\u6D88\u606F\u6E32\u67D3\u5668\n customRenderers: {\n video: {\n render: (content: MessageContent) => {\n const videoContent = content as any\n return (\n <div className=\"w-full\">\n <video src={videoContent.url} controls className=\"w-full rounded-lg\" poster={videoContent.poster}>\n Your browser does not support video playback\n </video>\n {videoContent.title && <p className=\"mt-2 text-sm text-gray-600\">{videoContent.title}</p>}\n </div>\n )\n },\n } as MessageRenderer,\n image_gallery: {\n render: (content: MessageContent) => {\n const galleryContent = content as any\n const images = galleryContent.images || []\n return (\n <div className=\"grid grid-cols-2 gap-2\">\n {images.map((image: any, index: number) => (\n <div key={index} className=\"relative aspect-square\">\n <img\n src={image.url}\n alt={image.alt || `Image ${index + 1}`}\n className=\"size-full rounded-lg object-cover\"\n />\n </div>\n ))}\n </div>\n )\n },\n } as MessageRenderer,\n },\n\n // \u81EA\u5B9A\u4E49\u4EA7\u54C1\u5361\u7247\u6E32\u67D3\n productCardRender: (product, productHandle) => {\n // product \u53EF\u80FD\u4E3A undefined\uFF0C\u6B64\u65F6\u53EF\u7528 productHandle \u67E5\u8BE2\n if (!product) {\n return (\n <div style={{ padding: '16px', border: '1px dashed #ccc', borderRadius: '8px', textAlign: 'center' }}>\n <p>Product loading... (handle: {productHandle})</p>\n </div>\n )\n }\n\n const imageUrl = product?.featured_image || ''\n const title = product?.title || ''\n const description = product?.description || ''\n const averageRating = product?.average_rating\n\n return (\n <div\n style={{\n border: '2px solid #4CAF50',\n borderRadius: '16px',\n padding: '16px',\n margin: '12px 0',\n backgroundColor: '#f0f9ff',\n boxShadow: '0 4px 12px rgba(76, 175, 80, 0.15)',\n }}\n >\n <div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>\n {imageUrl && (\n <img\n src={imageUrl}\n alt={title}\n style={{ width: '60px', height: '60px', borderRadius: '8px', objectFit: 'cover' }}\n />\n )}\n <div style={{ flex: 1 }}>\n <h4 style={{ margin: '0 0 4px 0', fontSize: '16px', fontWeight: 'bold' }}>{title}</h4>\n {description && (\n <p style={{ margin: 0, fontSize: '12px', color: '#666', lineHeight: 1.4 }}>\n {description.slice(0, 80)}...\n </p>\n )}\n {averageRating && (\n <span style={{ fontSize: '12px', color: '#FFB800' }}>Rating: {averageRating.toFixed(1)}</span>\n )}\n </div>\n <button\n style={{\n padding: '8px 16px',\n backgroundColor: '#4CAF50',\n color: 'white',\n borderRadius: '8px',\n border: 'none',\n cursor: 'pointer',\n }}\n onClick={() => console.log('View product:', productHandle, product)}\n >\n View\n </button>\n </div>\n </div>\n )\n },\n },\n\n render: args => (\n <LiveChatWidget\n {...args}\n // \u6240\u6709\u4E8B\u4EF6\u56DE\u8C03\n onOpen={() => console.log('[LiveChat] Chat opened')}\n onClose={() => console.log('[LiveChat] Chat closed')}\n onMessageSend={(message: string) => console.log('[LiveChat] Message sent:', message)}\n onError={(error: Error) => console.error('[LiveChat] Error:', error)}\n onTextMessage={() => console.log('[LiveChat] AI text message received')}\n onProductList={() => console.log('[LiveChat] Product list received')}\n onPromotionList={() => console.log('[LiveChat] Promotion list received')}\n onAddToCart={(product: any) => {\n console.log('[LiveChat] Add to cart:', product)\n alert(`Added \"${product.title}\" to cart!`)\n }}\n onCart={(cartId: string, checkoutUrl?: string) => {\n console.log('[LiveChat] Cart clicked:', { cartId, checkoutUrl })\n alert(`Cart ID: ${cartId}`)\n }}\n />\n ),\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["jsx", "jsxs", "LiveChatWidget", "meta", "LiveChatWidget_stories_default", "Default", "content", "videoContent", "images", "image", "index", "product", "productHandle", "imageUrl", "title", "description", "averageRating", "args", "message", "error", "cartId", "checkoutUrl"]
|
|
7
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
-
import { Chat } from '../components/index';
|
|
3
|
-
import '../styles/chat.css';
|
|
4
|
-
type Story = StoryObj<typeof meta>;
|
|
5
|
-
declare const meta: Meta<typeof Chat>;
|
|
6
|
-
export default meta;
|
|
7
|
-
export declare const Default: Story;
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{jsx as t}from"react/jsx-runtime";import{Chat as e}from"../components/index";import"../styles/chat.css";const a={title:"Campaign/Chat",component:e,args:{title:"DTC Live Chat",runtimeUrl:"https://beta-api-livechat.anker.com",shopifyDomain:"soundcoreusa.myshopify.com"},tags:["autodocs"],decorators:[o=>t("div",{style:{height:"700px"},children:t(o,{})})]};var m=a;const n={args:{title:"DTC Live Chat",runtimeUrl:"https://beta-api-livechat.anker.com",shopifyDomain:"soundcoreusa.myshopify.com",userId:"arno7",showResponseButton:"follow",lang:{popupTip:"Hi ! Welcome to soundcore Innovations live chat\uFF01",popupTipTimeout:[3e3,6e3]}}};export{n as Default,m as default};
|
|
2
|
-
//# sourceMappingURL=chat.stories.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../src/stories/chat.stories.tsx"],
|
|
4
|
-
"sourcesContent": ["import React from 'react'\nimport type { Meta, StoryObj } from '@storybook/react'\nimport { Chat } from '../components/index'\nimport '../styles/chat.css'\n\ntype Story = StoryObj<typeof meta>\n\n// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export\nconst meta: Meta<typeof Chat> = {\n // \u540C\u65F6\u5B9A\u4E49\u4E86\u9875\u9762\u5DE6\u4FA7\u83DC\u5355\n title: 'Campaign/Chat',\n component: Chat,\n args: {\n title: 'DTC Live Chat',\n runtimeUrl: 'https://beta-api-livechat.anker.com',\n shopifyDomain: 'soundcoreusa.myshopify.com',\n },\n // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs\n tags: ['autodocs'],\n // More on argTypes: https://storybook.js.org/docs/api/argtypes\n decorators: [\n Story => (\n <div style={{ height: '700px' }}>\n <Story />\n </div>\n ),\n ],\n} satisfies Meta<typeof Chat>\n\nexport default meta\n\nexport const Default: Story = {\n args: {\n title: 'DTC Live Chat',\n runtimeUrl: 'https://beta-api-livechat.anker.com',\n shopifyDomain: 'soundcoreusa.myshopify.com',\n userId: 'arno7',\n showResponseButton: 'follow',\n lang: { popupTip: 'Hi ! Welcome to soundcore Innovations live chat\uFF01', popupTipTimeout: [3000, 6000] },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAuBQ,cAAAA,MAAA,oBArBR,OAAS,QAAAC,MAAY,sBACrB,MAAO,qBAKP,MAAMC,EAA0B,CAE9B,MAAO,gBACP,UAAWD,EACX,KAAM,CACJ,MAAO,gBACP,WAAY,sCACZ,cAAe,4BACjB,EAEA,KAAM,CAAC,UAAU,EAEjB,WAAY,CACVE,GACEH,EAAC,OAAI,MAAO,CAAE,OAAQ,OAAQ,EAC5B,SAAAA,EAACG,EAAA,EAAM,EACT,CAEJ,CACF,EAEA,IAAOC,EAAQF,EAER,MAAMG,EAAiB,CAC5B,KAAM,CACJ,MAAO,gBACP,WAAY,sCACZ,cAAe,6BACf,OAAQ,QACR,mBAAoB,SACpB,KAAM,CAAE,SAAU,wDAAoD,gBAAiB,CAAC,IAAM,GAAI,CAAE,CACtG,CACF",
|
|
6
|
-
"names": ["jsx", "Chat", "meta", "Story", "chat_stories_default", "Default"]
|
|
7
|
-
}
|
|
@@ -1,317 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LiveChatWidget Storybook Stories
|
|
3
|
-
* 展示 LiveChat 组件的各种使用场景和配置
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { Meta, StoryObj } from '@storybook/react'
|
|
7
|
-
import { LiveChatWidget } from '../components/LiveChatWidget'
|
|
8
|
-
import type { MessageRenderer, MessageContent } from '../components/LiveChatWidget'
|
|
9
|
-
import '../styles/livechat.css'
|
|
10
|
-
|
|
11
|
-
const meta: Meta<typeof LiveChatWidget> = {
|
|
12
|
-
title: 'Campaign/LiveChatWidget',
|
|
13
|
-
component: LiveChatWidget,
|
|
14
|
-
parameters: {
|
|
15
|
-
layout: 'fullscreen',
|
|
16
|
-
docs: {
|
|
17
|
-
story: {
|
|
18
|
-
inline: false,
|
|
19
|
-
iframeHeight: 500,
|
|
20
|
-
},
|
|
21
|
-
description: {
|
|
22
|
-
component: `
|
|
23
|
-
# LiveChat 聊天组件
|
|
24
|
-
|
|
25
|
-
可复用的气泡弹窗聊天组件,支持 SSE 流式消息、自定义渲染器和多种消息类型。
|
|
26
|
-
|
|
27
|
-
## 功能特性
|
|
28
|
-
|
|
29
|
-
- 🎈 **气泡弹窗**: 可自定义位置的悬浮气泡按钮
|
|
30
|
-
- 💬 **流式消息**: 基于 SSE 的实时流式响应
|
|
31
|
-
- 📦 **多种消息类型**: 文本、商品卡片、商品列表、政策、快捷回复等
|
|
32
|
-
- 🎨 **可定制**: 支持自定义品牌颜色、Logo、渲染器
|
|
33
|
-
- 📱 **响应式**: 移动端全屏,桌面端固定尺寸
|
|
34
|
-
- 💾 **会话管理**: 自动管理 userId 和 sessionId
|
|
35
|
-
- 🔒 **安全防护**: 内置 XSS 防护和输入验证
|
|
36
|
-
|
|
37
|
-
## 基础用法
|
|
38
|
-
|
|
39
|
-
\`\`\`tsx
|
|
40
|
-
import { LiveChatWidget } from '@anker-in/campaign-ui'
|
|
41
|
-
import '@anker-in/campaign-ui/livechat.css'
|
|
42
|
-
|
|
43
|
-
function App() {
|
|
44
|
-
return (
|
|
45
|
-
<LiveChatWidget
|
|
46
|
-
apiBaseUrl="https://beta-api-livechat.anker.com"
|
|
47
|
-
site="www.eufy.com"
|
|
48
|
-
channel_code="web_homepage"
|
|
49
|
-
welcomeMessage="你好!我是 AI 助手"
|
|
50
|
-
/>
|
|
51
|
-
)
|
|
52
|
-
}
|
|
53
|
-
\`\`\`
|
|
54
|
-
|
|
55
|
-
## 自定义渲染器
|
|
56
|
-
|
|
57
|
-
\`\`\`tsx
|
|
58
|
-
const customRenderers = {
|
|
59
|
-
video: {
|
|
60
|
-
render: (content) => (
|
|
61
|
-
<video src={content.url} controls className="w-full rounded" />
|
|
62
|
-
)
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
<LiveChatWidget
|
|
67
|
-
apiBaseUrl="..."
|
|
68
|
-
site="..."
|
|
69
|
-
customRenderers={customRenderers}
|
|
70
|
-
/>
|
|
71
|
-
\`\`\`
|
|
72
|
-
`,
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
tags: ['autodocs'],
|
|
77
|
-
argTypes: {
|
|
78
|
-
apiBaseUrl: {
|
|
79
|
-
control: 'text',
|
|
80
|
-
description: 'API 基础 URL',
|
|
81
|
-
},
|
|
82
|
-
headers: {
|
|
83
|
-
control: 'object',
|
|
84
|
-
description: '自定义请求头,将在所有 API 请求中添加',
|
|
85
|
-
table: {
|
|
86
|
-
defaultValue: { summary: 'undefined' },
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
recaptchaSitekey: {
|
|
90
|
-
control: 'text',
|
|
91
|
-
description: 'Google reCAPTCHA v3 site key,提供此参数将自动启用 reCAPTCHA v3 验证',
|
|
92
|
-
table: {
|
|
93
|
-
defaultValue: { summary: 'undefined' },
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
recaptchaAction: {
|
|
97
|
-
control: 'text',
|
|
98
|
-
description: 'reCAPTCHA action 名称,用于区分不同的验证场景',
|
|
99
|
-
table: {
|
|
100
|
-
defaultValue: { summary: '"activity"' },
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
site: {
|
|
104
|
-
control: 'text',
|
|
105
|
-
description: 'Shopify 店铺 URL',
|
|
106
|
-
},
|
|
107
|
-
channelCode: {
|
|
108
|
-
control: 'text',
|
|
109
|
-
description: '渠道编码,用于标识来源渠道',
|
|
110
|
-
table: {
|
|
111
|
-
defaultValue: { summary: 'undefined' },
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
welcomeMessage: {
|
|
115
|
-
control: 'text',
|
|
116
|
-
description: '欢迎消息',
|
|
117
|
-
table: {
|
|
118
|
-
defaultValue: { summary: '你好!我是 AI 助手,有什么可以帮助你的吗?' },
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
logoUrl: {
|
|
122
|
-
control: 'text',
|
|
123
|
-
description: 'Logo URL',
|
|
124
|
-
},
|
|
125
|
-
position: {
|
|
126
|
-
control: 'object',
|
|
127
|
-
description: '气泡按钮位置对象',
|
|
128
|
-
table: {
|
|
129
|
-
defaultValue: { summary: '{ bottom: "1.5rem", right: "1.5rem" }' },
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
},
|
|
133
|
-
args: {
|
|
134
|
-
apiBaseUrl: 'http://172.16.38.183:3003',
|
|
135
|
-
site: 'www.eufy.com',
|
|
136
|
-
loginUserId: 'test_test',
|
|
137
|
-
welcomeMessage: '你好!我是 AI 助手,有什么可以帮助你的吗?',
|
|
138
|
-
},
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export default meta
|
|
142
|
-
type Story = StoryObj<typeof LiveChatWidget>
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* 默认配置 - 展示所有功能
|
|
146
|
-
*/
|
|
147
|
-
export const Default: Story = {
|
|
148
|
-
args: {
|
|
149
|
-
// 基础配置
|
|
150
|
-
loginUserId: 'test_test1',
|
|
151
|
-
apiBaseUrl: 'https://beta-api-v2-livechat.anker.com',
|
|
152
|
-
site: 'beta.soundcore.com',
|
|
153
|
-
channelCode: 'dtc',
|
|
154
|
-
title: 'eufy AI Assistant',
|
|
155
|
-
cartId: 'gid://shopify/Cart/hWNAAJweS9IkeRKib4LRy9PY?key=ff1f9b24e74b3f11f5574539e7cbbb2f',
|
|
156
|
-
accessToken: '47b1aa2c0797043f9baba39388029d70',
|
|
157
|
-
|
|
158
|
-
// 自定义位置
|
|
159
|
-
position: { bottom: '24px', right: '30px' },
|
|
160
|
-
|
|
161
|
-
// 欢迎消息
|
|
162
|
-
welcomeMessage: '',
|
|
163
|
-
|
|
164
|
-
// 快捷回复
|
|
165
|
-
quickReplies: [
|
|
166
|
-
{ id: '1', label: 'Product Info', value: 'Tell me about your products', icon: '📦' },
|
|
167
|
-
{ id: '2', label: 'Track Order', value: 'I want to track my order', icon: '🚚' },
|
|
168
|
-
{ id: '3', label: 'Support', value: 'I need help with my device', icon: '🔧' },
|
|
169
|
-
{ id: '4', label: 'Recommendations', value: 'Recommend products for me', icon: '⭐' },
|
|
170
|
-
],
|
|
171
|
-
|
|
172
|
-
// 法规协议弹窗
|
|
173
|
-
complianceConfig: {
|
|
174
|
-
title: "Hi! I'm your eufy AI assistant.",
|
|
175
|
-
content:
|
|
176
|
-
'AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data.',
|
|
177
|
-
checkboxText:
|
|
178
|
-
'By starting to use "Live Chat", you agree to Anker\'s <a href="https://www.anker.com/pages/privacy-policy" target="_blank" rel="noopener noreferrer" style="text-decoration: underline;">LIVE CHAT PRIVACY NOTICE</a>.',
|
|
179
|
-
agreeButtonText: 'Agree',
|
|
180
|
-
},
|
|
181
|
-
|
|
182
|
-
// reCAPTCHA 配置(取消注释以启用)
|
|
183
|
-
// recaptchaSitekey: '6LfS4J4pAAAAACX1e_WrxutmxxzCK7FU4WzVqL14',
|
|
184
|
-
recaptchaAction: 'livechat',
|
|
185
|
-
|
|
186
|
-
// 显示新会话按钮
|
|
187
|
-
showNewSessionButton: true,
|
|
188
|
-
|
|
189
|
-
// 自定义文案
|
|
190
|
-
commonText: {
|
|
191
|
-
learnMore: 'Learn More',
|
|
192
|
-
total: 'Total',
|
|
193
|
-
},
|
|
194
|
-
|
|
195
|
-
// 自定义消息渲染器
|
|
196
|
-
customRenderers: {
|
|
197
|
-
video: {
|
|
198
|
-
render: (content: MessageContent) => {
|
|
199
|
-
const videoContent = content as any
|
|
200
|
-
return (
|
|
201
|
-
<div className="w-full">
|
|
202
|
-
<video src={videoContent.url} controls className="w-full rounded-lg" poster={videoContent.poster}>
|
|
203
|
-
Your browser does not support video playback
|
|
204
|
-
</video>
|
|
205
|
-
{videoContent.title && <p className="mt-2 text-sm text-gray-600">{videoContent.title}</p>}
|
|
206
|
-
</div>
|
|
207
|
-
)
|
|
208
|
-
},
|
|
209
|
-
} as MessageRenderer,
|
|
210
|
-
image_gallery: {
|
|
211
|
-
render: (content: MessageContent) => {
|
|
212
|
-
const galleryContent = content as any
|
|
213
|
-
const images = galleryContent.images || []
|
|
214
|
-
return (
|
|
215
|
-
<div className="grid grid-cols-2 gap-2">
|
|
216
|
-
{images.map((image: any, index: number) => (
|
|
217
|
-
<div key={index} className="relative aspect-square">
|
|
218
|
-
<img
|
|
219
|
-
src={image.url}
|
|
220
|
-
alt={image.alt || `Image ${index + 1}`}
|
|
221
|
-
className="size-full rounded-lg object-cover"
|
|
222
|
-
/>
|
|
223
|
-
</div>
|
|
224
|
-
))}
|
|
225
|
-
</div>
|
|
226
|
-
)
|
|
227
|
-
},
|
|
228
|
-
} as MessageRenderer,
|
|
229
|
-
},
|
|
230
|
-
|
|
231
|
-
// 自定义产品卡片渲染
|
|
232
|
-
productCardRender: (product, productHandle) => {
|
|
233
|
-
// product 可能为 undefined,此时可用 productHandle 查询
|
|
234
|
-
if (!product) {
|
|
235
|
-
return (
|
|
236
|
-
<div style={{ padding: '16px', border: '1px dashed #ccc', borderRadius: '8px', textAlign: 'center' }}>
|
|
237
|
-
<p>Product loading... (handle: {productHandle})</p>
|
|
238
|
-
</div>
|
|
239
|
-
)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const imageUrl = product?.featured_image || ''
|
|
243
|
-
const title = product?.title || ''
|
|
244
|
-
const description = product?.description || ''
|
|
245
|
-
const averageRating = product?.average_rating
|
|
246
|
-
|
|
247
|
-
return (
|
|
248
|
-
<div
|
|
249
|
-
style={{
|
|
250
|
-
border: '2px solid #4CAF50',
|
|
251
|
-
borderRadius: '16px',
|
|
252
|
-
padding: '16px',
|
|
253
|
-
margin: '12px 0',
|
|
254
|
-
backgroundColor: '#f0f9ff',
|
|
255
|
-
boxShadow: '0 4px 12px rgba(76, 175, 80, 0.15)',
|
|
256
|
-
}}
|
|
257
|
-
>
|
|
258
|
-
<div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
|
|
259
|
-
{imageUrl && (
|
|
260
|
-
<img
|
|
261
|
-
src={imageUrl}
|
|
262
|
-
alt={title}
|
|
263
|
-
style={{ width: '60px', height: '60px', borderRadius: '8px', objectFit: 'cover' }}
|
|
264
|
-
/>
|
|
265
|
-
)}
|
|
266
|
-
<div style={{ flex: 1 }}>
|
|
267
|
-
<h4 style={{ margin: '0 0 4px 0', fontSize: '16px', fontWeight: 'bold' }}>{title}</h4>
|
|
268
|
-
{description && (
|
|
269
|
-
<p style={{ margin: 0, fontSize: '12px', color: '#666', lineHeight: 1.4 }}>
|
|
270
|
-
{description.slice(0, 80)}...
|
|
271
|
-
</p>
|
|
272
|
-
)}
|
|
273
|
-
{averageRating && (
|
|
274
|
-
<span style={{ fontSize: '12px', color: '#FFB800' }}>Rating: {averageRating.toFixed(1)}</span>
|
|
275
|
-
)}
|
|
276
|
-
</div>
|
|
277
|
-
<button
|
|
278
|
-
style={{
|
|
279
|
-
padding: '8px 16px',
|
|
280
|
-
backgroundColor: '#4CAF50',
|
|
281
|
-
color: 'white',
|
|
282
|
-
borderRadius: '8px',
|
|
283
|
-
border: 'none',
|
|
284
|
-
cursor: 'pointer',
|
|
285
|
-
}}
|
|
286
|
-
onClick={() => console.log('View product:', productHandle, product)}
|
|
287
|
-
>
|
|
288
|
-
View
|
|
289
|
-
</button>
|
|
290
|
-
</div>
|
|
291
|
-
</div>
|
|
292
|
-
)
|
|
293
|
-
},
|
|
294
|
-
},
|
|
295
|
-
|
|
296
|
-
render: args => (
|
|
297
|
-
<LiveChatWidget
|
|
298
|
-
{...args}
|
|
299
|
-
// 所有事件回调
|
|
300
|
-
onOpen={() => console.log('[LiveChat] Chat opened')}
|
|
301
|
-
onClose={() => console.log('[LiveChat] Chat closed')}
|
|
302
|
-
onMessageSend={(message: string) => console.log('[LiveChat] Message sent:', message)}
|
|
303
|
-
onError={(error: Error) => console.error('[LiveChat] Error:', error)}
|
|
304
|
-
onTextMessage={() => console.log('[LiveChat] AI text message received')}
|
|
305
|
-
onProductList={() => console.log('[LiveChat] Product list received')}
|
|
306
|
-
onPromotionList={() => console.log('[LiveChat] Promotion list received')}
|
|
307
|
-
onAddToCart={(product: any) => {
|
|
308
|
-
console.log('[LiveChat] Add to cart:', product)
|
|
309
|
-
alert(`Added "${product.title}" to cart!`)
|
|
310
|
-
}}
|
|
311
|
-
onCart={(cartId: string, checkoutUrl?: string) => {
|
|
312
|
-
console.log('[LiveChat] Cart clicked:', { cartId, checkoutUrl })
|
|
313
|
-
alert(`Cart ID: ${cartId}`)
|
|
314
|
-
}}
|
|
315
|
-
/>
|
|
316
|
-
),
|
|
317
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import type { Meta, StoryObj } from '@storybook/react'
|
|
3
|
-
import { Chat } from '../components/index'
|
|
4
|
-
import '../styles/chat.css'
|
|
5
|
-
|
|
6
|
-
type Story = StoryObj<typeof meta>
|
|
7
|
-
|
|
8
|
-
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
|
|
9
|
-
const meta: Meta<typeof Chat> = {
|
|
10
|
-
// 同时定义了页面左侧菜单
|
|
11
|
-
title: 'Campaign/Chat',
|
|
12
|
-
component: Chat,
|
|
13
|
-
args: {
|
|
14
|
-
title: 'DTC Live Chat',
|
|
15
|
-
runtimeUrl: 'https://beta-api-livechat.anker.com',
|
|
16
|
-
shopifyDomain: 'soundcoreusa.myshopify.com',
|
|
17
|
-
},
|
|
18
|
-
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
|
|
19
|
-
tags: ['autodocs'],
|
|
20
|
-
// More on argTypes: https://storybook.js.org/docs/api/argtypes
|
|
21
|
-
decorators: [
|
|
22
|
-
Story => (
|
|
23
|
-
<div style={{ height: '700px' }}>
|
|
24
|
-
<Story />
|
|
25
|
-
</div>
|
|
26
|
-
),
|
|
27
|
-
],
|
|
28
|
-
} satisfies Meta<typeof Chat>
|
|
29
|
-
|
|
30
|
-
export default meta
|
|
31
|
-
|
|
32
|
-
export const Default: Story = {
|
|
33
|
-
args: {
|
|
34
|
-
title: 'DTC Live Chat',
|
|
35
|
-
runtimeUrl: 'https://beta-api-livechat.anker.com',
|
|
36
|
-
shopifyDomain: 'soundcoreusa.myshopify.com',
|
|
37
|
-
userId: 'arno7',
|
|
38
|
-
showResponseButton: 'follow',
|
|
39
|
-
lang: { popupTip: 'Hi ! Welcome to soundcore Innovations live chat!', popupTipTimeout: [3000, 6000] },
|
|
40
|
-
},
|
|
41
|
-
}
|