@anker-in/campaign-ui 0.2.11-beta.34 → 0.2.11-beta.35

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.
Files changed (31) hide show
  1. package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js +1 -1
  2. package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
  3. package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.d.ts +51 -0
  4. package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js +41 -0
  5. package/dist/cjs/components/LiveChatWidget/components/ComplianceDialog.js.map +7 -0
  6. package/dist/cjs/components/LiveChatWidget/index.d.ts +1 -1
  7. package/dist/cjs/components/LiveChatWidget/index.js +1 -1
  8. package/dist/cjs/components/LiveChatWidget/index.js.map +2 -2
  9. package/dist/cjs/components/LiveChatWidget/types.d.ts +38 -0
  10. package/dist/cjs/components/LiveChatWidget/types.js.map +1 -1
  11. package/dist/cjs/stories/LiveChatWidget.stories.d.ts +14 -0
  12. package/dist/cjs/stories/LiveChatWidget.stories.js +76 -7
  13. package/dist/cjs/stories/LiveChatWidget.stories.js.map +3 -3
  14. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js +1 -1
  15. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
  16. package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.d.ts +51 -0
  17. package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js +41 -0
  18. package/dist/esm/components/LiveChatWidget/components/ComplianceDialog.js.map +7 -0
  19. package/dist/esm/components/LiveChatWidget/index.d.ts +1 -1
  20. package/dist/esm/components/LiveChatWidget/index.js +1 -1
  21. package/dist/esm/components/LiveChatWidget/index.js.map +2 -2
  22. package/dist/esm/components/LiveChatWidget/types.d.ts +38 -0
  23. package/dist/esm/stories/LiveChatWidget.stories.d.ts +14 -0
  24. package/dist/esm/stories/LiveChatWidget.stories.js +76 -7
  25. package/dist/esm/stories/LiveChatWidget.stories.js.map +3 -3
  26. package/package.json +5 -3
  27. package/src/components/LiveChatWidget/LiveChatWidget.tsx +65 -1
  28. package/src/components/LiveChatWidget/components/ComplianceDialog.tsx +229 -0
  29. package/src/components/LiveChatWidget/index.tsx +1 -0
  30. package/src/components/LiveChatWidget/types.ts +48 -0
  31. package/src/stories/LiveChatWidget.stories.tsx +131 -0
@@ -0,0 +1,229 @@
1
+ /**
2
+ * 法规协议弹窗组件
3
+ * 在用户首次点击聊天气泡时显示,用户同意后才打开聊天窗口
4
+ */
5
+
6
+ import React, { useState, useCallback } from 'react'
7
+ import * as Dialog from '@radix-ui/react-dialog'
8
+ import type { ComplianceDialogConfig } from '../types'
9
+
10
+ export interface ComplianceDialogProps {
11
+ /**
12
+ * 是否打开弹窗
13
+ */
14
+ open: boolean
15
+
16
+ /**
17
+ * 弹窗配置
18
+ */
19
+ config: ComplianceDialogConfig
20
+
21
+ /**
22
+ * 用户点击同意按钮的回调
23
+ */
24
+ onAgree: () => void
25
+
26
+ /**
27
+ * 用户关闭弹窗的回调(点击遮罩或关闭按钮)
28
+ */
29
+ onClose: () => void
30
+ }
31
+
32
+ /**
33
+ * 法规协议弹窗组件
34
+ *
35
+ * 功能:
36
+ * - 显示法规协议内容
37
+ * - 必须勾选复选框才能点击同意按钮
38
+ * - 支持在文本中嵌入链接
39
+ * - 响应式设计,移动端友好
40
+ *
41
+ * @example
42
+ * ```tsx
43
+ * <ComplianceDialog
44
+ * open={showCompliance}
45
+ * config={{
46
+ * title: "Hi! I'm your eufy AI assistant.",
47
+ * content: "AI-generated responses can be inaccurate...",
48
+ * checkboxText: "By starting to use \"Live Chat\", you agree to Anker's {link}.",
49
+ * linkText: "LIVE CHAT PRIVACY NOTICE",
50
+ * linkUrl: "https://www.anker.com/privacy",
51
+ * agreeButtonText: "Agree"
52
+ * }}
53
+ * onAgree={() => setShowCompliance(false)}
54
+ * onClose={() => setShowCompliance(false)}
55
+ * />
56
+ * ```
57
+ */
58
+ export const ComplianceDialog: React.FC<ComplianceDialogProps> = ({
59
+ open,
60
+ config,
61
+ onAgree,
62
+ onClose,
63
+ }) => {
64
+ const [isChecked, setIsChecked] = useState(false)
65
+
66
+ const handleAgree = useCallback(() => {
67
+ if (isChecked) {
68
+ onAgree()
69
+ // 重置勾选状态,以便下次打开时重新勾选
70
+ setIsChecked(false)
71
+ }
72
+ }, [isChecked, onAgree])
73
+
74
+ const handleOpenChange = useCallback((open: boolean) => {
75
+ if (!open) {
76
+ onClose()
77
+ // 重置勾选状态
78
+ setIsChecked(false)
79
+ }
80
+ }, [onClose])
81
+
82
+ // 处理点击事件,防止点击链接时触发 checkbox
83
+ const handleCheckboxLabelClick = useCallback((e: React.MouseEvent) => {
84
+ // 如果点击的是链接,阻止冒泡
85
+ if ((e.target as HTMLElement).tagName === 'A') {
86
+ e.stopPropagation()
87
+ }
88
+ }, [])
89
+
90
+ return (
91
+ <Dialog.Root open={open} onOpenChange={handleOpenChange}>
92
+ <Dialog.Portal>
93
+ {/* 遮罩层 - 隐藏 */}
94
+ <Dialog.Overlay className="hidden" />
95
+
96
+ {/* 弹窗内容 */}
97
+ <Dialog.Content
98
+ className="
99
+ fixed z-[9998] bg-white overflow-hidden flex flex-col
100
+ animate-in slide-in-from-bottom-4 fade-in duration-300
101
+ max-md:bottom-0 max-md:left-0 max-md:right-0 max-md:w-screen max-md:max-h-[80%]
102
+ max-md:rounded-t-2xl max-md:shadow-[0_-4px_20px_rgba(0,0,0,0.15)]
103
+ md:bottom-4 md:right-4 md:w-[390px] md:max-h-[70vh]
104
+ md:rounded-2xl md:shadow-xl
105
+ 2xl:max-h-[720px]
106
+ "
107
+ >
108
+ {/* Header - 包含关闭按钮(右上角) */}
109
+ <div className="flex items-center justify-end px-4 pt-4 pb-2 flex-shrink-0 max-md:px-6 max-sm:px-5">
110
+ <Dialog.Close asChild>
111
+ <button
112
+ type="button"
113
+ className="
114
+ w-6 h-6
115
+ flex items-center justify-center
116
+ border-0 bg-transparent text-gray-500
117
+ cursor-pointer
118
+ "
119
+ aria-label="关闭"
120
+ >
121
+ <svg
122
+ width="20"
123
+ height="20"
124
+ className="text-[#3C3C3B]"
125
+ viewBox="0 0 20 20"
126
+ fill="none"
127
+ stroke="currentColor"
128
+ strokeWidth="2"
129
+ strokeLinecap="round"
130
+ strokeLinejoin="round"
131
+ >
132
+ <line x1="15" y1="5" x2="5" y2="15" />
133
+ <line x1="5" y1="5" x2="15" y2="15" />
134
+ </svg>
135
+ </button>
136
+ </Dialog.Close>
137
+ </div>
138
+
139
+ {/* Body - 可滚动内容区域 */}
140
+ <div className="flex-1 overflow-y-auto px-4 pb-4 max-md:px-6 max-md:pb-6 max-sm:px-5 max-sm:pb-5">
141
+ {/* 标题 */}
142
+ <Dialog.Title
143
+ className="
144
+ text-[18px] font-bold text-[#080A0F]
145
+ mb-1 leading-[1.4] tracking-tight
146
+ "
147
+ >
148
+ {config.title}
149
+ </Dialog.Title>
150
+
151
+ {/* 内容 */}
152
+ <div
153
+ className="
154
+ text-[16px] font-bold leading-[1.4] text-[#080A0F]
155
+ tracking-tight
156
+ mb-2
157
+ "
158
+ dangerouslySetInnerHTML={{ __html: config.content }}
159
+ />
160
+
161
+ {/* 勾选框 */}
162
+ <label
163
+ className="
164
+ flex items-center gap-2 mb-4
165
+ cursor-pointer select-none
166
+ "
167
+ onClick={handleCheckboxLabelClick}
168
+ >
169
+ <input
170
+ type="checkbox"
171
+ className="absolute opacity-0 w-0 h-0"
172
+ checked={isChecked}
173
+ onChange={e => setIsChecked(e.target.checked)}
174
+ />
175
+ <span
176
+ className={`
177
+ flex-shrink-0 w-4 h-4 mt-0.5
178
+ border-[1px] rounded-[2px] flex items-center justify-center
179
+ transition-all duration-200 ease-in-out
180
+ ${isChecked ? 'bg-[#080A0F] border-[#080A0F] text-white' : 'bg-white border-[#2A2C32]'}
181
+ `}
182
+ >
183
+ {isChecked && (
184
+ <svg
185
+ width="12"
186
+ height="10"
187
+ viewBox="0 0 12 10"
188
+ fill="none"
189
+ stroke="currentColor"
190
+ strokeWidth="2"
191
+ strokeLinecap="round"
192
+ strokeLinejoin="round"
193
+ >
194
+ <polyline points="1.5 5 4.5 8 10.5 2" />
195
+ </svg>
196
+ )}
197
+ </span>
198
+ <span
199
+ className="
200
+ flex-1 text-[14px] leading-[1.4] text-[#080A0F]
201
+ tracking-tight font-bold
202
+ [&_a]:underline
203
+ "
204
+ dangerouslySetInnerHTML={{ __html: config.checkboxText }}
205
+ />
206
+ </label>
207
+
208
+ {/* 同意按钮 */}
209
+ <button
210
+ type="button"
211
+ className={`
212
+ w-full px-5 py-[10px]
213
+ border-0 rounded-[19px]
214
+ text-[14px] leading-[1.2]
215
+ tracking-tighter font-bold
216
+ transition-all duration-200 ease-in-out text-white
217
+ ${isChecked ? 'bg-[#080A0F] cursor-pointer ' : 'bg-[#767880] cursor-not-allowed'}
218
+ `}
219
+ onClick={handleAgree}
220
+ disabled={!isChecked}
221
+ >
222
+ {config.agreeButtonText || 'Agree'}
223
+ </button>
224
+ </div>
225
+ </Dialog.Content>
226
+ </Dialog.Portal>
227
+ </Dialog.Root>
228
+ )
229
+ }
@@ -34,6 +34,7 @@ export type {
34
34
  CartDiscountCode,
35
35
  SSEEvent,
36
36
  ChatStreamRequest,
37
+ ComplianceDialogConfig,
37
38
  } from './types'
38
39
 
39
40
  // 导出 Hook (供高级用户自定义使用)
@@ -702,6 +702,47 @@ export interface CommonText {
702
702
  total?: string
703
703
  }
704
704
 
705
+ // ============================================================================
706
+ // Compliance Dialog Types (法规协议弹窗)
707
+ // ============================================================================
708
+
709
+ /**
710
+ * 法规协议弹窗配置
711
+ */
712
+ export interface ComplianceDialogConfig {
713
+ /**
714
+ * 弹窗标题
715
+ * @example "Hi! I'm your eufy AI assistant."
716
+ */
717
+ title: string
718
+
719
+ /**
720
+ * 弹窗内容文本(支持 HTML)
721
+ * @example "AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data"
722
+ */
723
+ content: string
724
+
725
+ /**
726
+ * 勾选框文本(支持完整 HTML,包括链接)
727
+ * 可以直接包含 <a> 标签等 HTML 元素
728
+ * @example "By starting to use \"Live Chat\", you agree to Anker's <a href=\"https://www.anker.com/privacy\" target=\"_blank\">LIVE CHAT PRIVACY NOTICE</a>."
729
+ */
730
+ checkboxText: string
731
+
732
+ /**
733
+ * 同意按钮文本
734
+ * @default "Agree"
735
+ */
736
+ agreeButtonText?: string
737
+
738
+ /**
739
+ * Cookie 名称,用于记录用户同意状态
740
+ * Cookie 有效期为 365 天
741
+ * @default "livechat_compliance_agreed"
742
+ */
743
+ cookieName?: string
744
+ }
745
+
705
746
  // ============================================================================
706
747
  // Component Props Types (组件 Props)
707
748
  // ============================================================================
@@ -932,6 +973,13 @@ export interface LiveChatWidgetProps {
932
973
  * @default "The above content is provided by AI, for reference."
933
974
  */
934
975
  bottomTips?: string
976
+
977
+ /**
978
+ * 法规协议弹窗配置
979
+ * 提供此参数将在用户首次点击聊天气泡时显示法规协议弹窗
980
+ * 用户同意后才会打开聊天窗口
981
+ */
982
+ complianceConfig?: ComplianceDialogConfig
935
983
  }
936
984
 
937
985
  export interface MessageRenderer {
@@ -574,6 +574,137 @@ export const WithRecaptcha: Story = {
574
574
  },
575
575
  }
576
576
 
577
+ /**
578
+ * 法规协议弹窗
579
+ *
580
+ * 演示如何配置法规协议弹窗,在用户首次点击聊天气泡时显示。
581
+ *
582
+ * 用户必须同意协议后才能打开聊天窗口。
583
+ */
584
+ export const WithComplianceDialog: Story = {
585
+ args: {
586
+ loginUserId: 'test_test',
587
+ apiBaseUrl: 'http://172.16.38.183:3003',
588
+ site: 'beta.eufy.com',
589
+ channelCode: 'dtc',
590
+ title: 'eufy AI Assistant',
591
+
592
+ // 法规协议弹窗配置
593
+ complianceConfig: {
594
+ title: "Hi! I'm your eufy AI assistant.",
595
+ content: "AI-generated responses can be inaccurate. Please verify important info. Do not input sensitive personal data",
596
+ 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>.',
597
+ agreeButtonText: "Agree"
598
+ },
599
+ },
600
+ parameters: {
601
+ docs: {
602
+ description: {
603
+ story: `
604
+ ### 法规协议弹窗功能
605
+
606
+ 使用 \`complianceConfig\` 属性可以在用户首次点击聊天气泡时显示法规协议弹窗。
607
+
608
+ **功能特性:**
609
+
610
+ - ✅ 用户必须勾选复选框才能点击"同意"按钮
611
+ - ✅ 支持在勾选框文本中嵌入链接(使用 \`{link}\` 占位符)
612
+ - ✅ 支持 HTML 格式的内容文本
613
+ - ✅ 同意后自动打开聊天窗口
614
+ - ✅ 关闭弹窗不会打开聊天窗口
615
+ - ✅ 同意后再次点击气泡直接打开聊天(无需重复同意)
616
+ - ✅ 响应式设计,移动端友好
617
+ - ✅ **与聊天窗口位置一致**(右下角或底部固定)
618
+ - ✅ **无遮罩层设计**,不遮挡页面内容
619
+
620
+ **测试步骤:**
621
+
622
+ 1. 点击聊天气泡按钮
623
+ 2. 查看弹出的法规协议弹窗
624
+ 3. 尝试不勾选复选框时点击"Agree"按钮(按钮应该是禁用状态)
625
+ 4. 勾选复选框后点击"Agree"
626
+ 5. 观察弹窗关闭并自动打开聊天窗口
627
+ 6. 关闭聊天窗口后再次点击气泡(应该直接打开聊天,不再显示法规弹窗)
628
+
629
+ **配置项说明:**
630
+
631
+ \`\`\`typescript
632
+ complianceConfig: {
633
+ title: string // 弹窗标题
634
+ content: string // 内容文本(支持 HTML)
635
+ checkboxText: string // 勾选框文本(支持完整 HTML,包括 <a> 标签)
636
+ agreeButtonText?: string // 同意按钮文案(默认 "Agree")
637
+ }
638
+ \`\`\`
639
+
640
+ **示例:**
641
+
642
+ \`\`\`tsx
643
+ checkboxText: 'By starting to use "Live Chat", you agree to <a href="https://example.com/privacy" target="_blank">PRIVACY NOTICE</a>.'
644
+ \`\`\`
645
+
646
+ **样式定制:**
647
+
648
+ 所有样式通过 CSS 类名暴露,可以全局覆盖:
649
+
650
+ \`\`\`css
651
+ .livechat-compliance-content { /* 弹窗主体 */ }
652
+ .livechat-compliance-title { /* 标题 */ }
653
+ .livechat-compliance-text { /* 内容文本 */ }
654
+ .livechat-compliance-checkbox { /* 复选框 */ }
655
+ .livechat-compliance-link { /* 链接 */ }
656
+ .livechat-compliance-button { /* 同意按钮 */ }
657
+ \`\`\`
658
+ `,
659
+ },
660
+ },
661
+ },
662
+ }
663
+
664
+ /**
665
+ * 法规协议弹窗 - 无链接版本
666
+ *
667
+ * 演示不包含链接的简化版法规协议弹窗。
668
+ */
669
+ export const WithComplianceDialogNoLink: Story = {
670
+ args: {
671
+ loginUserId: 'test_test',
672
+ apiBaseUrl: 'http://172.16.38.183:3003',
673
+ site: 'beta.eufy.com',
674
+ channelCode: 'dtc',
675
+ title: 'AI Assistant',
676
+
677
+ // 简化版法规协议(不包含链接)
678
+ complianceConfig: {
679
+ title: "Welcome to AI Chat Support",
680
+ content: "By using this service, you agree to our terms of service and privacy policy. AI responses may contain errors. Please verify important information.",
681
+ checkboxText: "I have read and agree to the terms and conditions",
682
+ agreeButtonText: "Continue"
683
+ },
684
+ },
685
+ parameters: {
686
+ docs: {
687
+ description: {
688
+ story: `
689
+ ### 简化版法规协议弹窗
690
+
691
+ 如果不需要在勾选框中显示链接,可以省略 \`linkText\` 和 \`linkUrl\` 属性。
692
+
693
+ 这个版本适用于:
694
+ - 简单的用户协议确认
695
+ - 免责声明
696
+ - 服务条款同意
697
+
698
+ **与完整版的区别:**
699
+ - 不包含可点击的链接
700
+ - 勾选框文本为纯文本
701
+ - 配置更简单
702
+ `,
703
+ },
704
+ },
705
+ },
706
+ }
707
+
577
708
  /**
578
709
  * 自定义产品卡片渲染
579
710
  *