@aslaluroba/help-center-react 2.0.2 → 2.0.5

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 (41) hide show
  1. package/dist/core/api.d.ts +4 -1
  2. package/dist/index.d.ts +3 -2
  3. package/dist/index.esm.js +994 -25294
  4. package/dist/index.esm.js.map +1 -1
  5. package/dist/index.js +995 -25295
  6. package/dist/index.js.map +1 -1
  7. package/dist/lib/config.d.ts +1 -1
  8. package/dist/lib/types.d.ts +4 -0
  9. package/dist/ui/chatbot-popup/chat-window-screen/footer.d.ts +1 -0
  10. package/dist/ui/chatbot-popup/chat-window-screen/index.d.ts +1 -1
  11. package/dist/ui/help-center.d.ts +1 -1
  12. package/dist/ui/help-popup.d.ts +9 -3
  13. package/dist/ui/review-dialog/index.d.ts +8 -0
  14. package/dist/ui/review-dialog/rating.d.ts +12 -0
  15. package/package.json +26 -5
  16. package/src/assets/icons/arrowRight.svg +1 -1
  17. package/src/assets/icons/closeCircle.svg +1 -1
  18. package/src/components/ui/agent-response/agent-response.tsx +36 -34
  19. package/src/components/ui/header.tsx +2 -3
  20. package/src/core/SignalRService.ts +25 -25
  21. package/src/core/api.ts +180 -43
  22. package/src/globals.css +0 -9
  23. package/src/index.ts +3 -2
  24. package/src/lib/config.ts +31 -25
  25. package/src/lib/types.ts +5 -0
  26. package/src/locales/ar.json +18 -1
  27. package/src/locales/en.json +26 -8
  28. package/src/ui/chatbot-popup/chat-window-screen/footer.tsx +31 -34
  29. package/src/ui/chatbot-popup/chat-window-screen/header.tsx +47 -53
  30. package/src/ui/chatbot-popup/chat-window-screen/index.tsx +178 -88
  31. package/src/ui/chatbot-popup/options-list-screen/header.tsx +24 -20
  32. package/src/ui/chatbot-popup/options-list-screen/index.tsx +24 -24
  33. package/src/ui/chatbot-popup/options-list-screen/option-card.tsx +9 -4
  34. package/src/ui/help-center.tsx +367 -141
  35. package/src/ui/help-popup.tsx +239 -165
  36. package/src/ui/review-dialog/index.tsx +106 -0
  37. package/src/ui/review-dialog/rating.tsx +78 -0
  38. package/tsconfig.json +48 -0
  39. package/postcss.config.js +0 -6
  40. package/rollup.config.js +0 -58
  41. package/tailwind.config.js +0 -174
@@ -1,103 +1,193 @@
1
- import AgentResponse from '@/components/ui/agent-response/agent-response'
2
- import { Message } from '@/lib/types'
3
- import ChatWindowFooter from '@/ui/chatbot-popup/chat-window-screen/footer'
4
- import React, { useEffect, useRef, useState } from 'react'
5
- import LoadingGif from './../../../assets/animatedLogo.gif'
6
- import Seperator from './../../../assets/icons/seperator.svg'
7
- import LogoIcon from './../../../assets/logo.svg'
1
+ import AgentResponse from '@/components/ui/agent-response/agent-response';
2
+ import { Message } from '@/lib/types';
3
+ import ChatWindowFooter from '@/ui/chatbot-popup/chat-window-screen/footer';
4
+ import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react';
5
+ import LoadingGif from './../../../assets/animatedLogo.gif';
6
+ import Seperator from './../../../assets/icons/seperator.svg';
7
+ import LogoIcon from './../../../assets/logo.svg';
8
8
 
9
9
  interface ChatWindowProps {
10
- onSendMessage: (message: string) => void
11
- messages: Message[]
12
- assistantStatus: string
13
- needsAgent: boolean
10
+ onSendMessage: (message: string) => void;
11
+ messages: Message[];
12
+ assistantStatus: string;
13
+ needsAgent: boolean;
14
14
  }
15
15
 
16
- export function ChatWindow({ onSendMessage, messages, assistantStatus = 'loading' }: ChatWindowProps) {
17
- const [inputMessage, setInputMessage] = useState('')
18
- const messagesEndRef = useRef<HTMLDivElement>(null)
16
+ // Memoize individual message component to prevent unnecessary re-renders
17
+ const MessageComponent = React.memo(
18
+ ({
19
+ message,
20
+ index,
21
+ messages,
22
+ firstHumanAgentIndex,
23
+ onType,
24
+ }: {
25
+ message: Message;
26
+ index: number;
27
+ messages: Message[];
28
+ firstHumanAgentIndex: number;
29
+ onType: () => void;
30
+ }) => {
31
+ const isFirstInSequence = index === 0 || messages[index - 1].senderType !== message.senderType;
32
+ const isFirstHumanAgentMessage = index === firstHumanAgentIndex && message.senderType === 2;
33
+ const textDirection = message.senderType === 1 ? 'justify-end' : 'justify-start';
19
34
 
20
- const scrollToBottom = () => {
21
- messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
35
+ return (
36
+ <div key={message.id}>
37
+ {isFirstHumanAgentMessage && (
38
+ <div className='babylai-flex babylai-justify-center babylai-items-center babylai-my-4'>
39
+ <Seperator className='babylai-w-full babylai-text-purple-500' />
40
+ </div>
41
+ )}
42
+ <div className={`babylai-mb-4 babylai-flex ${textDirection}`}>
43
+ {isFirstInSequence && message.senderType === 3 && (
44
+ <div className='babylai-flex-shrink-0 babylai-mr-3'>
45
+ <div className='babylai-w-8 babylai-h-8 babylai-rounded-full babylai-bg-purple-500 babylai-flex babylai-items-center babylai-justify-center'>
46
+ <LogoIcon className='babylai-w-4 babylai-h-4 babylai-text-white' />
47
+ </div>
48
+ </div>
49
+ )}
50
+ {!isFirstInSequence && <div className='babylai-flex-shrink-0 babylai-mr-3 babylai-w-8'></div>}
51
+
52
+ <AgentResponse
53
+ messageContent={message.messageContent}
54
+ senderType={message.senderType}
55
+ messageId={message.id}
56
+ onType={onType}
57
+ />
58
+ </div>
59
+ </div>
60
+ );
61
+ }
62
+ );
63
+
64
+ MessageComponent.displayName = 'MessageComponent';
65
+
66
+ // Memoize typing indicator component
67
+ const TypingIndicator = React.memo(({ firstHumanAgentIndex }: { firstHumanAgentIndex: number }) => {
68
+ if (firstHumanAgentIndex !== -1) return null;
69
+
70
+ return (
71
+ <div className='babylai-mb-4 babylai-flex'>
72
+ <div className='babylai-flex-shrink-0 babylai-mr-3'>
73
+ <div className='babylai-w-8 babylai-h-8 babylai-rounded-full babylai-flex babylai-items-center babylai-justify-center'>
74
+ <img src={LoadingGif} alt='Loading' className='babylai-w-8 babylai-h-8' />
75
+ </div>
76
+ </div>
77
+ <div className='babylai-max-w-[80%] babylai-rounded-2xl babylai-p-4 babylai-bg-white'>
78
+ <p className='babylai-text-sm babylai-opacity-70'>...</p>
79
+ </div>
80
+ </div>
81
+ );
82
+ });
83
+
84
+ TypingIndicator.displayName = 'TypingIndicator';
85
+
86
+ export const ChatWindow = React.memo(({ onSendMessage, messages, assistantStatus = 'loading' }: ChatWindowProps) => {
87
+ const [inputMessage, setInputMessage] = useState('');
88
+ const messagesEndRef = useRef<HTMLDivElement>(null);
89
+ const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
90
+ const lastMessageCountRef = useRef(messages.length);
91
+
92
+ // Debounced scroll to bottom function
93
+ const scrollToBottom = useCallback(() => {
94
+ if (scrollTimeoutRef.current) {
95
+ clearTimeout(scrollTimeoutRef.current);
22
96
  }
23
97
 
24
- useEffect(() => {
25
- scrollToBottom()
26
- }, [messages, assistantStatus])
98
+ scrollTimeoutRef.current = setTimeout(() => {
99
+ messagesEndRef.current?.scrollIntoView({
100
+ behavior: 'smooth',
101
+ block: 'end',
102
+ });
103
+ }, 100);
104
+ }, []);
27
105
 
28
- const handleSendMessage = () => {
29
- if (inputMessage.trim()) {
30
- onSendMessage(inputMessage)
31
- setInputMessage('')
32
- }
106
+ // Only scroll when new messages are added or status changes
107
+ useEffect(() => {
108
+ if (messages.length !== lastMessageCountRef.current || assistantStatus === 'typing') {
109
+ lastMessageCountRef.current = messages.length;
110
+ scrollToBottom();
33
111
  }
112
+ }, [messages.length, assistantStatus, scrollToBottom]);
34
113
 
35
- const handleKeyDown = (e: React.KeyboardEvent) => {
36
- if (e.key === 'Enter' && !e.shiftKey) {
37
- e.preventDefault()
38
- onSendMessage(inputMessage)
39
- setInputMessage('')
40
- }
114
+ // Cleanup timeout on unmount
115
+ useEffect(() => {
116
+ return () => {
117
+ if (scrollTimeoutRef.current) {
118
+ clearTimeout(scrollTimeoutRef.current);
119
+ }
120
+ };
121
+ }, []);
122
+
123
+ const handleSendMessage = useCallback(() => {
124
+ if (inputMessage.trim()) {
125
+ onSendMessage(inputMessage);
126
+ setInputMessage('');
41
127
  }
128
+ }, [inputMessage, onSendMessage]);
129
+
130
+ const handleKeyDown = useCallback(
131
+ (e: React.KeyboardEvent) => {
132
+ if (e.key === 'Enter' && !e.shiftKey) {
133
+ e.preventDefault();
134
+ if (inputMessage.trim() && assistantStatus !== 'typing') {
135
+ onSendMessage(inputMessage);
136
+ setInputMessage('');
137
+ }
138
+ }
139
+ },
140
+ [inputMessage, onSendMessage, assistantStatus]
141
+ );
42
142
 
43
- // Find the first human agent message
44
- const firstHumanAgentIndex = messages.findIndex((message) => message.senderType === 2)
143
+ // Memoize the first human agent index calculation
144
+ const firstHumanAgentIndex = useMemo(() => {
145
+ return messages.findIndex((message) => message.senderType === 2);
146
+ }, [messages]);
45
147
 
148
+ // Memoize the message list to prevent unnecessary re-renders
149
+ const messagesList = useMemo(() => {
150
+ return messages.map((message, index) => (
151
+ <MessageComponent
152
+ key={`${message.id}-${index}`}
153
+ message={message}
154
+ index={index}
155
+ messages={messages}
156
+ firstHumanAgentIndex={firstHumanAgentIndex}
157
+ onType={scrollToBottom}
158
+ />
159
+ ));
160
+ }, [messages, firstHumanAgentIndex, scrollToBottom]);
161
+
162
+ // Memoize loading state check
163
+ const isLoading = useMemo(() => {
46
164
  return (
47
- <>
48
- <div className="babylai-flex-1 babylai-overflow-y-auto babylai-p-4 babylai-h-full">
49
- {messages.map((message, index) => {
50
- const isFirstInSequence = index === 0 || messages[index - 1].senderType !== message.senderType
51
- const isFirstHumanAgentMessage = index === firstHumanAgentIndex && message.senderType === 2
52
- const textDirection = message.senderType === 1 ? 'justify-end' : 'justify-start'
53
-
54
- return (
55
- <div key={message.id}>
56
- {isFirstHumanAgentMessage && (
57
- <div className="babylai-flex babylai-justify-center babylai-items-center babylai-my-4">
58
- <Seperator className="babylai-w-full babylai-text-purple-500" />
59
- </div>
60
- )}
61
- <div className={`babylai-mb-4 babylai-flex ${textDirection}`}>
62
- {isFirstInSequence && message.senderType === 3 && (
63
- <div className="babylai-flex-shrink-0 babylai-mr-3">
64
- <div className="babylai-w-8 babylai-h-8 babylai-rounded-full babylai-bg-purple-500 babylai-flex babylai-items-center babylai-justify-center">
65
- <LogoIcon className="babylai-w-4 babylai-h-4 babylai-text-white" />
66
- </div>
67
- </div>
68
- )}
69
- {!isFirstInSequence && <div className="babylai-flex-shrink-0 babylai-mr-3 babylai-w-8"></div>}
70
-
71
- <AgentResponse
72
- messageContent={message.messageContent}
73
- senderType={message.senderType}
74
- messageId={message.id}
75
- onType={scrollToBottom} />
76
- </div>
77
- </div>
78
- )
79
- })}
80
- {assistantStatus === 'typing' && firstHumanAgentIndex === -1 && (
81
- <div className="babylai-mb-4 babylai-flex">
82
- <div className="babylai-flex-shrink-0 babylai-mr-3">
83
- <div className="babylai-w-8 babylai-h-8 babylai-rounded-full babylai-flex babylai-items-center babylai-justify-center">
84
- <img src={LoadingGif} alt="Loading" className="babylai-w-8 babylai-h-8" />
85
- </div>
86
- </div>
87
- <div className="babylai-max-w-[80%] babylai-rounded-2xl babylai-p-4 babylai-bg-white">
88
- <p className="babylai-text-sm babylai-opacity-70">...</p>
89
- </div>
90
- </div>
91
- )}
92
- <div ref={messagesEndRef} />
93
- </div>
165
+ assistantStatus === 'typing' ||
166
+ assistantStatus === 'loading' ||
167
+ assistantStatus === 'error' ||
168
+ inputMessage.trim() === ''
169
+ );
170
+ }, [assistantStatus, inputMessage]);
94
171
 
95
- <ChatWindowFooter
96
- inputMessage={inputMessage}
97
- handleKeyDown={(e) => handleKeyDown(e)}
98
- handleSendMessage={handleSendMessage}
99
- setInputMessage={setInputMessage}
100
- />
101
- </>
102
- )
103
- }
172
+ return (
173
+ <>
174
+ <div className='babylai-flex-1 babylai-overflow-y-auto babylai-p-4 babylai-h-full'>
175
+ {messagesList}
176
+
177
+ {assistantStatus === 'typing' && <TypingIndicator firstHumanAgentIndex={firstHumanAgentIndex} />}
178
+
179
+ <div ref={messagesEndRef} />
180
+ </div>
181
+
182
+ <ChatWindowFooter
183
+ inputMessage={inputMessage}
184
+ handleKeyDown={handleKeyDown}
185
+ handleSendMessage={handleSendMessage}
186
+ setInputMessage={setInputMessage}
187
+ isLoading={isLoading}
188
+ />
189
+ </>
190
+ );
191
+ });
192
+
193
+ ChatWindow.displayName = 'ChatWindow';
@@ -1,38 +1,42 @@
1
- import { Button } from '@/components'
2
- import React from 'react'
3
- import ArrowRight from './../../../assets/icons/arrowRight.svg'
4
- import ThinkingLogo from './../../../assets/thinking-logo.svg'
5
- import { useLocalTranslation } from '../../../useLocalTranslation'
6
- import CloseCircle from '../../../assets/icons/closeCirclePrimary.svg'
1
+ import { Button } from '@/components';
2
+ import React from 'react';
3
+ import ArrowRight from './../../../assets/icons/arrowRight.svg';
4
+ import ThinkingLogo from './../../../assets/thinking-logo.svg';
5
+ import { useLocalTranslation } from '../../../useLocalTranslation';
6
+ import CloseCircle from '../../../assets/icons/closeCirclePrimary.svg';
7
7
 
8
8
  interface OptionsListHeaderProps {
9
- handleBack: () => void
10
- showHelpScreen: boolean
9
+ handleBack: () => void;
10
+ showHelpScreen: boolean;
11
11
  }
12
12
 
13
13
  const OptionsListHeader: React.FC<OptionsListHeaderProps> = ({ handleBack, showHelpScreen }) => {
14
- const { i18n } = useLocalTranslation()
15
- const isRTL = i18n.dir() === 'rtl'
14
+ const { i18n } = useLocalTranslation();
15
+ const isRTL = i18n.dir() === 'rtl';
16
16
 
17
17
  return (
18
18
  <header
19
19
  dir={i18n.dir()}
20
- className={`babylai-flex babylai-w-full babylai-items-center mb-2 ${!showHelpScreen ? 'babylai-justify-end' : 'babylai-justify-between'}`}
20
+ className={`babylai-flex babylai-w-full babylai-items-center mb-2 ${
21
+ !showHelpScreen ? 'babylai-justify-end' : 'babylai-justify-between'
22
+ }`}
21
23
  >
22
24
  <Button
23
- variant="rounded-icon"
24
- size="icon"
25
- className="!babylai-w-12 !babylai-h-12 babylai-bg-black-white-50 babylai-relative babylai-z-10"
25
+ variant='rounded-icon'
26
+ size='icon'
27
+ className='!babylai-w-12 !babylai-h-12 babylai-bg-black-white-50 babylai-relative babylai-z-10'
26
28
  onClick={handleBack}
27
29
  >
28
30
  {showHelpScreen ? (
29
- <ArrowRight className={`${isRTL ? '' : 'babylai-rotate-180'} babylai-text-primary-500`} />
31
+ <ArrowRight
32
+ className={`${isRTL ? '' : 'babylai-rotate-180'} babylai-text-primary-500 babylai-w-full babylai-h-full`}
33
+ />
30
34
  ) : (
31
- <CloseCircle className="!babylai-w-full !babylai-h-full babylai-cursor-pointer" />
35
+ <CloseCircle className='!babylai-w-full !babylai-h-full babylai-cursor-pointer' />
32
36
  )}
33
37
  </Button>
34
- <ThinkingLogo className="babylai-w-40 babylai-h-40 babylai-text-primary-500 babylai-absolute babylai-top-0 babylai-end-1 babylai-overflow-hidden" />
38
+ <ThinkingLogo className='babylai-w-40 babylai-h-40 babylai-text-primary-500 babylai-absolute babylai-top-0 babylai-end-1 babylai-overflow-hidden' />
35
39
  </header>
36
- )
37
- }
38
- export default OptionsListHeader
40
+ );
41
+ };
42
+ export default OptionsListHeader;
@@ -1,17 +1,17 @@
1
- import { Card } from '@/components'
2
- import { HelpScreenData, Option } from '@/lib/types'
3
- import ExpandedOption from '@/ui/chatbot-popup/options-list-screen/expanded-option'
4
- import OptionsListHeader from '@/ui/chatbot-popup/options-list-screen/header'
5
- import OptionCard from '@/ui/chatbot-popup/options-list-screen/option-card'
6
- import React from 'react'
1
+ import { Card } from '@/components';
2
+ import { HelpScreenData, Option } from '@/lib/types';
3
+ import ExpandedOption from '@/ui/chatbot-popup/options-list-screen/expanded-option';
4
+ import OptionsListHeader from '@/ui/chatbot-popup/options-list-screen/header';
5
+ import OptionCard from '@/ui/chatbot-popup/options-list-screen/option-card';
6
+ import React from 'react';
7
7
 
8
8
  interface OptionsListScreenProps {
9
- helpScreen: HelpScreenData | null
10
- expandedOption: Option | null
11
- setExpandedOption: (option: Option | null) => void
12
- handleStartChat: (option: Option) => void
13
- handleBack: () => void
14
- showHelpScreen: boolean
9
+ helpScreen: HelpScreenData | null;
10
+ expandedOption: Option | null;
11
+ setExpandedOption: (option: Option | null) => void;
12
+ handleStartChat: (option: Option) => void;
13
+ handleBack: () => void;
14
+ showHelpScreen: boolean;
15
15
  }
16
16
 
17
17
  const OptionsListScreen: React.FC<OptionsListScreenProps> = ({
@@ -20,29 +20,29 @@ const OptionsListScreen: React.FC<OptionsListScreenProps> = ({
20
20
  setExpandedOption,
21
21
  handleStartChat,
22
22
  handleBack,
23
- showHelpScreen
23
+ showHelpScreen,
24
24
  }) => {
25
25
  const handleToggleExpandOption = (option: Option) => {
26
26
  if (expandedOption?.id === option.id) {
27
- setExpandedOption(null)
27
+ setExpandedOption(null);
28
28
  } else {
29
- setExpandedOption(option)
29
+ setExpandedOption(option);
30
30
  }
31
- }
31
+ };
32
32
 
33
33
  return (
34
- <div className="babylai-px-8 babylai-pb-12 babylai-pt-8 babylai-overflow-y-auto babylai-h-full">
34
+ <div className='babylai-px-8 babylai-pb-12 babylai-pt-8 babylai-overflow-y-auto babylai-h-full'>
35
35
  <OptionsListHeader handleBack={handleBack} showHelpScreen={showHelpScreen} />
36
36
 
37
- <h1 className="babylai-text-4xl babylai-font-bold babylai-text-black-white-800 mb-4">{helpScreen?.title}</h1>
37
+ <h1 className='babylai-text-4xl babylai-font-bold babylai-text-black-white-800 mb-4'>{helpScreen?.title}</h1>
38
38
 
39
39
  {helpScreen && (
40
- <div className="babylai-flex babylai-flex-col babylai-gap-3 babylai-mt-3 babylai-mb-3">
40
+ <div className='babylai-flex babylai-flex-col babylai-gap-3 babylai-mt-3 babylai-mb-3'>
41
41
  {helpScreen?.options?.map((option) => (
42
42
  <Card
43
43
  key={option.id}
44
- variant="rounded"
45
- className="babylai-cursor-pointer babylai-transition-all babylai-hover:babylai-shadow-md !babylai-px-2 !babylai-py-1"
44
+ variant='rounded'
45
+ className='babylai-cursor-pointer babylai-transition-all babylai-hover:babylai-shadow-md !babylai-px-2 !babylai-py-1'
46
46
  onClick={() => handleToggleExpandOption(option)}
47
47
  >
48
48
  <OptionCard title={option.title} />
@@ -53,7 +53,7 @@ const OptionsListScreen: React.FC<OptionsListScreenProps> = ({
53
53
  </div>
54
54
  )}
55
55
  </div>
56
- )
57
- }
56
+ );
57
+ };
58
58
 
59
- export default OptionsListScreen
59
+ export default OptionsListScreen;
@@ -1,17 +1,22 @@
1
1
  import { Button } from '@/components';
2
2
  import React from 'react';
3
3
  import ArrowRight from '../../../assets/icons/arrowRight.svg';
4
+ import { useTranslation } from 'react-i18next'
4
5
 
5
6
  interface OptionCardProps {
6
7
  title: string;
7
8
  }
8
9
 
9
10
  const OptionCard: React.FC<OptionCardProps> = (props) => {
11
+ const { i18n} = useTranslation();
12
+ const isLTR = i18n.dir() === 'ltr';
10
13
  return (
11
- <div className="babylai-flex babylai-items-center babylai-justify-between babylai-p-4">
12
- <p className="babylai-text-base babylai-font-semibold babylai-text-black-white-800">{props.title}</p>
13
- <Button variant="rounded-icon" size="icon" className="!babylai-w-12 !babylai-h-12 babylai-text-primary-500 hover:babylai-bg-primary-100">
14
- <ArrowRight className={` babylai-text-primary-500 babylai-rotate-90 `}/>
14
+ <div className="babylai-flex babylai-items-center babylai-justify-between babylai-p-2">
15
+ <p className="babylai-text-sm babylai-font-medium babylai-text-black-white-800">{props.title}</p>
16
+ <Button variant="rounded-icon" size="icon" className="babylai-text-primary-500 hover:babylai-bg-primary-100">
17
+ <ArrowRight className={`babylai-w-[12px] babylai-h-[12px] ${
18
+ isLTR ? '' : 'babylai-rotate-180'
19
+ } babylai-text-primary-500`}/>
15
20
  </Button>
16
21
  </div>
17
22
  )