@aslaluroba/help-center-react 2.0.4 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) 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 -44
  22. package/src/globals.css +0 -9
  23. package/src/index.ts +3 -2
  24. package/src/lib/config.ts +25 -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 -33
  29. package/src/ui/chatbot-popup/chat-window-screen/header.tsx +47 -53
  30. package/src/ui/chatbot-popup/chat-window-screen/index.tsx +182 -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 +189 -159
  35. package/src/ui/help-popup.tsx +241 -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
@@ -1,71 +1,79 @@
1
- import { cn } from '@/lib'
2
- import { ChatWindow } from '@/ui/chatbot-popup/chat-window-screen'
3
- import ChatWindowHeader from '@/ui/chatbot-popup/chat-window-screen/header'
4
- import ChatBotErrorScreen from '@/ui/chatbot-popup/error-screen'
5
- import HomeScreen from '@/ui/chatbot-popup/home-screen'
6
- import ChatBotLoadingScreen from '@/ui/chatbot-popup/loading-screen'
7
- import OptionsListScreen from '@/ui/chatbot-popup/options-list-screen'
8
- import React, { useEffect, useRef, useState } from 'react'
9
- import ChatIcon from '../assets/icons/chat.svg'
10
- import { Button } from '../components'
11
- import { HelpScreenData, Message, Option } from '../lib/types'
1
+ import { cn } from '@/lib';
2
+ import { ChatWindow } from '@/ui/chatbot-popup/chat-window-screen';
3
+ import ChatWindowHeader from '@/ui/chatbot-popup/chat-window-screen/header';
4
+ import ChatBotErrorScreen from '@/ui/chatbot-popup/error-screen';
5
+ import HomeScreen from '@/ui/chatbot-popup/home-screen';
6
+ import ChatBotLoadingScreen from '@/ui/chatbot-popup/loading-screen';
7
+ import OptionsListScreen from '@/ui/chatbot-popup/options-list-screen';
8
+ import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react';
9
+ import ChatIcon from '../assets/icons/chat.svg';
10
+ import { Button } from '@/components';
11
+ import { HelpScreenData, Message, Option } from '@/lib/types';
12
+ import { useLocalTranslation } from '../useLocalTranslation';
12
13
 
13
14
  type HelpPopupProps = {
14
- isOpen: boolean
15
- onClose: () => void
16
- helpScreen: HelpScreenData | null
17
- status: string
18
- error: string | null
19
- user: any
20
- onStartChat: (option: Option) => void
21
- onSendMessage: (message: string) => void
22
- onEndChat: () => void
23
- messages: Message[]
24
- needsAgent: boolean
25
- assistantStatus: string
26
- sessionId: string | null
27
- isChatClosed: boolean
28
- isSignalRConnected: boolean
29
- selectedOption: Option | null
30
- setSelectedOption: (option: Option | null) => void
31
- showHelpScreen: boolean
32
- }
15
+ isOpen: boolean;
16
+ onClose: () => void;
17
+ helpScreen: HelpScreenData | null;
18
+ status: string;
19
+ error: string | null;
20
+ user: any;
21
+ onStartChat: (option: Option) => void;
22
+ onSendMessage: (message: string) => void;
23
+ onEndChat: (option?: Option) => void;
24
+ messages: Message[];
25
+ needsAgent: boolean;
26
+ assistantStatus: string;
27
+ sessionId: string | null;
28
+ isChatClosed: boolean;
29
+ isSignalRConnected: boolean;
30
+ selectedOption: Option | null;
31
+ setSelectedOption: (option: Option | null) => void;
32
+ showHelpScreen: boolean;
33
+ };
33
34
 
34
35
  // Confirmation Modal Component
35
- const ConfirmationModal = ({
36
+ export const ConfirmationModal = ({
36
37
  title,
37
38
  message,
38
39
  onCancel,
39
- onConfirm
40
+ onConfirm,
40
41
  }: {
41
- title: string
42
- message: string
43
- onCancel: () => void
44
- onConfirm: () => void
42
+ title: string;
43
+ message: string;
44
+ onCancel: () => void;
45
+ onConfirm: () => void;
45
46
  }) => {
47
+ const { t } = useLocalTranslation();
48
+
46
49
  return (
47
- <div className="babylai-absolute babylai-inset-0 babylai-z-50 babylai-flex babylai-items-center babylai-justify-center babylai-rounded-3xl babylai-overflow-hidden">
48
- <div className="babylai-absolute babylai-inset-0 babylai-bg-black/60" onClick={onCancel}></div>
49
- <div className="babylai-bg-black-white-100 babylai-rounded-3xl babylai-p-4 babylai-w-[220px] babylai-z-50 babylai-shadow-lg">
50
- <h3 className="babylai-text-black-white-900 babylai-font-bold babylai-mb-2 babylai-text-center">{title}</h3>
51
- <p className="babylai-text-black-white-700 babylai-text-xs babylai-mb-4">{message}</p>
52
- <div className="babylai-flex babylai-justify-end babylai-gap-2 babylai-w-full">
53
- <Button variant="default" size="sm" onClick={onConfirm} className="babylai-text-sm babylai-w-full !babylai-font-bold">
54
- Confirm
50
+ <div className='babylai-absolute babylai-inset-0 babylai-z-50 babylai-flex babylai-items-center babylai-justify-center babylai-rounded-3xl babylai-overflow-hidden'>
51
+ <div className='babylai-absolute babylai-inset-0 babylai-bg-black/60' onClick={onCancel}></div>
52
+ <div className='babylai-bg-black-white-100 babylai-rounded-3xl babylai-p-4 babylai-w-[220px] babylai-z-50 babylai-shadow-lg'>
53
+ <h3 className='babylai-text-black-white-900 babylai-font-bold babylai-mb-2 babylai-text-center'>{title}</h3>
54
+ <p className='babylai-text-black-white-700 babylai-text-xs babylai-mb-4'>{message}</p>
55
+ <div className='babylai-flex babylai-justify-end babylai-gap-2 babylai-w-full'>
56
+ <Button
57
+ variant='default'
58
+ size='sm'
59
+ onClick={onConfirm}
60
+ className='babylai-text-sm babylai-w-full !babylai-font-bold'
61
+ >
62
+ {t('homeSdk.ConfirmationModal.confirmation_button')}
55
63
  </Button>
56
64
  <Button
57
- variant="outline"
58
- size="sm"
65
+ variant='outline'
66
+ size='sm'
59
67
  onClick={onCancel}
60
- className="babylai-text-sm babylai-w-full babylai-text-primary-500 !babylai-font-bold"
68
+ className='babylai-text-sm babylai-w-full babylai-text-primary-500 !babylai-font-bold'
61
69
  >
62
- Cancel
70
+ {t('homeSdk.ConfirmationModal.cancel_button')}
63
71
  </Button>
64
72
  </div>
65
73
  </div>
66
74
  </div>
67
- )
68
- }
75
+ );
76
+ };
69
77
 
70
78
  export function HelpPopup({
71
79
  onClose,
@@ -79,121 +87,110 @@ export function HelpPopup({
79
87
  assistantStatus,
80
88
  needsAgent,
81
89
  sessionId,
82
- isChatClosed,
83
- isSignalRConnected,
84
90
  selectedOption,
85
91
  setSelectedOption,
86
- showHelpScreen
92
+ showHelpScreen,
93
+ isSignalRConnected,
87
94
  }: HelpPopupProps) {
88
- const [showChat, setShowChat] = useState(false)
89
- const [currentMessages, setCurrentMessages] = useState<Message[]>([])
90
- const [isShowList, setIsShowList] = useState<boolean>(!showHelpScreen || false)
91
- const [expandedOption, setExpandedOption] = useState<Option | null>(null)
92
- const [endChatConfirmation, setEndChatConfirmation] = useState<boolean>(false)
95
+ // ALL HOOKS MUST BE CALLED FIRST - BEFORE ANY EARLY RETURNS
96
+ const [showChat, setShowChat] = useState(false);
97
+ const [isShowList, setIsShowList] = useState<boolean>(!showHelpScreen || false);
98
+ const [expandedOption, setExpandedOption] = useState<Option | null>(null);
99
+ const [endChatConfirmation, setEndChatConfirmation] = useState<boolean>(false);
100
+ const [startNewChatConfirmation, setStartNewChatConfirmation] = useState<boolean>(false);
101
+ const [tempSelectedOption, setTempSelectedOption] = useState<Option | null>(null);
93
102
 
94
- const chatBoxRef = useRef<HTMLDivElement>(null)
103
+ const chatBoxRef = useRef<HTMLDivElement>(null);
104
+ const { t } = useLocalTranslation();
105
+ const messagesRef = useRef<Message[]>([]);
95
106
 
96
- // Scroll to bottom when new messages arrive
97
- useEffect(() => {
107
+ // Memoize the current messages to prevent unnecessary re-renders
108
+ const memoizedMessages = useMemo(() => {
109
+ return messages;
110
+ }, [messages]);
111
+
112
+ // Add this function to show the end chat confirmation modal
113
+ const showEndChatConfirmation = useCallback(() => setEndChatConfirmation(true), []);
114
+
115
+ // Optimize scroll to bottom with debouncing
116
+ const scrollToBottom = useCallback(() => {
98
117
  if (chatBoxRef.current) {
99
- chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight
118
+ requestAnimationFrame(() => {
119
+ if (chatBoxRef.current) {
120
+ chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight;
121
+ }
122
+ });
100
123
  }
101
- setCurrentMessages(messages)
102
- }, [messages])
124
+ }, []);
103
125
 
104
- const handleBack = () => {
126
+ const handleBack = useCallback(() => {
105
127
  if (showChat) {
106
- setShowChat(false)
128
+ setShowChat(false);
107
129
  // onEndChat()
108
130
  } else if (selectedOption) {
109
- setSelectedOption(null)
131
+ setSelectedOption(null);
110
132
  } else {
111
- setIsShowList(false)
133
+ setIsShowList(false);
112
134
  }
113
- }
135
+ }, [showChat, selectedOption, setSelectedOption]);
114
136
 
115
- const handleStartChat = (option: Option) => {
116
- if (option) {
117
- setCurrentMessages([
118
- {
119
- id: 1,
120
- senderType: 3,
121
- messageContent: selectedOption?.assistant?.greeting || '',
122
- sentAt: new Date(),
123
- isSeen: true
137
+ const handleStartChat = useCallback(
138
+ (option: Option) => {
139
+ if (option) {
140
+ if (sessionId) {
141
+ setStartNewChatConfirmation(true);
142
+ setTempSelectedOption(option);
143
+ } else {
144
+ setShowChat(true);
145
+ onStartChat(option);
146
+ setSelectedOption(option);
124
147
  }
125
- ])
126
- setShowChat(true)
127
- onStartChat(option)
128
- setSelectedOption(option)
129
- }
130
- }
131
-
132
- const handleSendMessage = (message: string) => {
133
- if (message.trim() && isSignalRConnected && !isChatClosed) {
134
- onSendMessage(message.trim())
135
- }
136
- }
137
-
138
- const hideEndChatConfirmation = () => {
139
- setEndChatConfirmation(false)
140
- }
148
+ }
149
+ },
150
+ [onStartChat, setSelectedOption, sessionId, setStartNewChatConfirmation, setTempSelectedOption]
151
+ );
141
152
 
142
- const handleEndChat = () => {
143
- setEndChatConfirmation(false)
144
- setCurrentMessages((prev) => [
145
- ...prev,
146
- {
147
- id: prev.length + 1,
148
- senderType: 3,
149
- messageContent: selectedOption?.assistant?.closing || '',
150
- sentAt: new Date(),
151
- isSeen: true
153
+ const handleSendMessage = useCallback(
154
+ (message: string) => {
155
+ if (message.trim() && isSignalRConnected) {
156
+ onSendMessage(message.trim());
152
157
  }
153
- ])
154
- // setTimeout to close the chat after 3 seconds
155
- setTimeout(() => {
156
- onEndChat()
157
- setShowChat(false)
158
- setSelectedOption(null)
159
- }, 3000)
160
- }
158
+ },
159
+ [onSendMessage]
160
+ );
161
161
 
162
- const handleShowActiveChat = () => {
163
- setShowChat(true)
164
- }
162
+ const hideEndChatConfirmation = useCallback(() => {
163
+ setEndChatConfirmation(false);
164
+ }, []);
165
165
 
166
- if (status === 'loading' && !helpScreen) {
167
- return (
168
- <div
169
- className="babylai-fixed babylai-inset-0 md:babylai-inset-auto md:babylai-max-w-sm md:babylai-min-w-sm md:babylai-h-[calc(100vh-240px)]
170
- babylai-overflow-auto babylai-w-full babylai-bg-black-white-50 md:babylai-mb-4
171
- md:babylai-bottom-[6rem] md:babylai-right-4 babylai-rounded-none md:babylai-rounded-3xl babylai-shadow-lg babylai-z-50 babylai-flex babylai-flex-col"
172
- >
173
- <ChatBotLoadingScreen isShowList={isShowList} onClose={onClose} />
174
- </div>
175
- )
176
- }
166
+ const handleEndAndStartNewChat = useCallback(async () => {
167
+ if (tempSelectedOption) {
168
+ setStartNewChatConfirmation(false);
169
+ setShowChat(true);
170
+ onEndChat(tempSelectedOption);
171
+ setTempSelectedOption(null);
172
+ }
173
+ }, [onEndChat, setTempSelectedOption, tempSelectedOption, setStartNewChatConfirmation]);
177
174
 
178
- if (error) {
179
- return (
180
- <div
181
- className="babylai-fixed babylai-inset-0 md:babylai-inset-auto md:babylai-max-w-sm md:babylai-min-w-sm md:babylai-h-[calc(100vh-240px)]
182
- babylai-overflow-auto babylai-w-full babylai-bg-black-white-50 md:babylai-mb-4
183
- md:babylai-bottom-[6rem] md:babylai-right-4 babylai-rounded-none md:babylai-rounded-3xl babylai-shadow-lg babylai-z-50 babylai-flex babylai-flex-col"
184
- >
185
- <ChatBotErrorScreen onClose={onClose} error={error} />
186
- </div>
187
- )
188
- }
175
+ const handleEndChat = useCallback(() => {
176
+ setEndChatConfirmation(false);
177
+ onEndChat();
178
+ setShowChat(false);
179
+ setSelectedOption(null);
180
+ }, [selectedOption, onEndChat, setSelectedOption]);
181
+
182
+ const handleShowActiveChat = useCallback(() => {
183
+ setShowChat(true);
184
+ }, []);
189
185
 
190
- const renderContent = () => {
186
+ // Memoize render content function
187
+ const renderContent = useCallback(() => {
191
188
  if (showChat && selectedOption) {
192
189
  return (
193
190
  <>
194
191
  <ChatWindowHeader
195
192
  handleBack={handleBack}
196
- handleEndChat={handleEndChat}
193
+ handleEndChat={showEndChatConfirmation}
197
194
  onClose={onClose}
198
195
  isShowList={isShowList}
199
196
  showChat={showChat}
@@ -201,12 +198,13 @@ md:babylai-bottom-[6rem] md:babylai-right-4 babylai-rounded-none md:babylai-roun
201
198
  />
202
199
  <ChatWindow
203
200
  onSendMessage={handleSendMessage}
204
- messages={currentMessages}
201
+ messages={memoizedMessages}
205
202
  assistantStatus={assistantStatus}
206
203
  needsAgent={needsAgent}
204
+ isSignalRConnected={isSignalRConnected}
207
205
  />
208
206
  </>
209
- )
207
+ );
210
208
  }
211
209
 
212
210
  if (isShowList) {
@@ -219,46 +217,124 @@ md:babylai-bottom-[6rem] md:babylai-right-4 babylai-rounded-none md:babylai-roun
219
217
  handleBack={showHelpScreen ? handleBack : onClose}
220
218
  showHelpScreen={showHelpScreen}
221
219
  />
222
- )
220
+ );
221
+ }
222
+ return <HomeScreen setIsShowList={setIsShowList} onClose={onClose} />;
223
+ }, [
224
+ showChat,
225
+ selectedOption,
226
+ handleBack,
227
+ showEndChatConfirmation,
228
+ onClose,
229
+ isShowList,
230
+ setIsShowList,
231
+ handleSendMessage,
232
+ memoizedMessages,
233
+ assistantStatus,
234
+ needsAgent,
235
+ helpScreen,
236
+ expandedOption,
237
+ setExpandedOption,
238
+ handleStartChat,
239
+ showHelpScreen,
240
+ ]);
241
+
242
+ // Memoize confirmation modal
243
+ const confirmationModal = useMemo(() => {
244
+ if (!endChatConfirmation) return null;
245
+
246
+ return (
247
+ <ConfirmationModal
248
+ title={t('homeSdk.ConfirmationModal.title')}
249
+ message={t('homeSdk.ConfirmationModal.message')}
250
+ onCancel={hideEndChatConfirmation}
251
+ onConfirm={handleEndChat}
252
+ />
253
+ );
254
+ }, [endChatConfirmation, t, hideEndChatConfirmation, handleEndChat]);
255
+
256
+ // Memoize active chat button
257
+ const activeChatButton = useMemo(() => {
258
+ if (!sessionId || showChat) return null;
259
+
260
+ return (
261
+ <Button
262
+ variant='rounded-icon'
263
+ size='icon'
264
+ className='babylai-bg-primary-500 babylai-absolute babylai-bottom-4 babylai-right-3 babylai-z-20 !babylai-w-10 !babylai-h-10 babylai-p-2'
265
+ onClick={handleShowActiveChat}
266
+ >
267
+ <ChatIcon className='babylai-w-5 babylai-h-5 babylai-text-black-white-50' />
268
+ </Button>
269
+ );
270
+ }, [sessionId, showChat, handleShowActiveChat]);
271
+
272
+ // Scroll to bottom when new messages arrive - optimize with ref comparison
273
+ useEffect(() => {
274
+ if (messagesRef.current !== messages) {
275
+ messagesRef.current = messages;
276
+ scrollToBottom();
223
277
  }
224
- return <HomeScreen setIsShowList={setIsShowList} onClose={onClose} />
278
+ }, [messages, scrollToBottom]);
279
+
280
+ // EARLY RETURNS MUST COME AFTER ALL HOOKS
281
+ // Early returns for performance - moved after all hooks
282
+ if (status === 'loading' && !helpScreen) {
283
+ return (
284
+ <div
285
+ className='babylai-fixed babylai-inset-0 md:babylai-inset-auto md:babylai-max-w-sm md:babylai-min-w-sm md:babylai-h-[calc(100vh-240px)]
286
+ babylai-overflow-auto babylai-w-full babylai-bg-black-white-50 md:babylai-mb-4
287
+ md:babylai-bottom-[6rem] md:babylai-right-4 babylai-rounded-none md:babylai-rounded-3xl babylai-shadow-lg babylai-z-50 babylai-flex babylai-flex-col'
288
+ >
289
+ <ChatBotLoadingScreen isShowList={isShowList} onClose={onClose} />
290
+ </div>
291
+ );
292
+ }
293
+
294
+ if (error) {
295
+ return (
296
+ <div
297
+ className='babylai-fixed babylai-inset-0 md:babylai-inset-auto md:babylai-max-w-sm md:babylai-min-w-sm md:babylai-h-[calc(100vh-240px)]
298
+ babylai-overflow-auto babylai-w-full babylai-bg-black-white-50 md:babylai-mb-4
299
+ md:babylai-bottom-[6rem] md:babylai-right-4 babylai-rounded-none md:babylai-rounded-3xl babylai-shadow-lg babylai-z-50 babylai-flex babylai-flex-col'
300
+ >
301
+ <ChatBotErrorScreen onClose={onClose} error={error || ''} />
302
+ </div>
303
+ );
225
304
  }
226
305
 
227
306
  return (
228
307
  <div
229
- className="babylai-fixed babylai-inset-0 md:babylai-inset-auto md:babylai-max-w-sm md:babylai-min-w-sm md:babylai-h-[calc(100vh-240px)]
308
+ className='babylai-fixed babylai-inset-0 md:babylai-inset-auto md:babylai-max-w-sm md:babylai-min-w-sm md:babylai-h-[calc(100vh-240px)]
230
309
  babylai-overflow-auto babylai-w-full babylai-bg-black-white-50 md:babylai-mb-4
231
- md:babylai-bottom-[6rem] md:babylai-right-4 babylai-rounded-none md:babylai-rounded-3xl babylai-shadow-lg babylai-z-50 babylai-flex babylai-flex-col"
310
+ md:babylai-bottom-[6rem] md:babylai-right-4 babylai-rounded-none md:babylai-rounded-3xl babylai-shadow-lg babylai-z-50 babylai-flex babylai-flex-col'
232
311
  >
233
312
  <div
234
- className={cn('babylai-h-full babylai-rounded-none md:babylai-rounded-3xl babylai-flex babylai-flex-col babylai-relative', {
235
- 'babylai-bg-gradient-to-b babylai-from-primary-500 babylai-to-primary-500/60': !isShowList,
236
- 'babylai-bg-black-white-100': isShowList
237
- })}
238
- >
239
- {sessionId && !showChat && (
240
- <Button
241
- variant="rounded-icon"
242
- size="icon"
243
- className="babylai-bg-primary-500 babylai-absolute babylai-bottom-4 babylai-right-3 babylai-z-20 !babylai-w-10 !babylai-h-10 babylai-p-2"
244
- onClick={handleShowActiveChat}
245
- >
246
- <ChatIcon className="babylai-w-5 babylai-h-5 babylai-text-black-white-50" />
247
- </Button>
313
+ className={cn(
314
+ 'babylai-h-full babylai-rounded-none md:babylai-rounded-3xl babylai-flex babylai-flex-col babylai-relative',
315
+ {
316
+ 'babylai-bg-gradient-to-b babylai-from-primary-500 babylai-to-primary-500/60': !isShowList,
317
+ 'babylai-bg-black-white-100': isShowList,
318
+ }
248
319
  )}
320
+ >
321
+ {activeChatButton}
322
+
249
323
  {/* Content */}
250
324
  {renderContent()}
251
325
 
252
326
  {/* End Chat Confirmation Modal */}
253
- {endChatConfirmation && (
327
+ {confirmationModal}
328
+
329
+ {startNewChatConfirmation && (
254
330
  <ConfirmationModal
255
- title="Leaving so soon? 👋"
256
- message="Don't worry, you can come back anytime. We're always here if you need help or have questions."
257
- onCancel={hideEndChatConfirmation}
258
- onConfirm={handleEndChat}
331
+ title={t('homeSdk.ConfirmationModal.endAndStartNewChatTitle')}
332
+ message={t('homeSdk.ConfirmationModal.endAndStartNewChatMessage')}
333
+ onCancel={() => setStartNewChatConfirmation(false)}
334
+ onConfirm={handleEndAndStartNewChat}
259
335
  />
260
336
  )}
261
337
  </div>
262
338
  </div>
263
- )
339
+ );
264
340
  }
@@ -0,0 +1,106 @@
1
+ import { ReviewProps } from '@/lib/types'
2
+ import { Rating } from '@/ui/review-dialog/rating'
3
+ import React from 'react'
4
+ import { useLocalTranslation } from '../../useLocalTranslation'
5
+ import CloseCircle from '../../assets/icons/closeCircle.svg'
6
+
7
+ interface ReviewDialogProps {
8
+ handleSubmit: ({ comment, rating }: ReviewProps) => void;
9
+ onClose: () => void;
10
+ }
11
+
12
+ const ReviewDialog: React.FC<ReviewDialogProps> = (props) => {
13
+ const { t } = useLocalTranslation()
14
+ const [comment, setComment] = React.useState<string>('')
15
+ const [rating, setRating] = React.useState<number>(0)
16
+ const [error, setError] = React.useState<{ comment?: string; rating?: string }>({})
17
+
18
+ const isCommentValid = comment.length >= 10 && comment.length <= 500
19
+ const isRatingValid = rating >= 1 && rating <= 5
20
+
21
+ const validateAndSubmit = () => {
22
+ const newError: typeof error = {}
23
+ if (!isCommentValid) {
24
+ newError.comment = t('homeSdk.ReviewDialog.comment_error') || 'Comment must be between 10 and 500 characters.'
25
+ }
26
+ if (!isRatingValid) {
27
+ newError.rating = t('homeSdk.ReviewDialog.rating_error') || 'Rating must be between 1 and 5.'
28
+ }
29
+
30
+ if (Object.keys(newError).length > 0) {
31
+ setError(newError)
32
+ return
33
+ }
34
+
35
+ setError({})
36
+ props.handleSubmit({ comment, rating })
37
+ }
38
+
39
+ const handleCommentChange = (val: string) => {
40
+ setComment(val)
41
+ if (error.comment && val.length >= 10 && val.length <= 500) {
42
+ setError((prev) => ({ ...prev, comment: undefined }))
43
+ }
44
+ }
45
+
46
+ const handleRatingChange = (val: number) => {
47
+ setRating(val)
48
+ if (error.rating && val >= 1 && val <= 5) {
49
+ setError((prev) => ({ ...prev, rating: undefined }))
50
+ }
51
+ }
52
+
53
+ return (
54
+ <section className="babylai-p-6 babylai-gap-6 babylai-max-w-sm babylai-max-h-[calc(100vh-90px)] babylai-overflow-auto babylai-w-full babylai-bg-black-white-50 babylai-fixed babylai-bottom-20 babylai-right-0 md:babylai-right-4 babylai-rounded-3xl babylai-shadow-lg babylai-z-50 babylai-flex babylai-flex-col">
55
+ <header className='border-b pb-4 babylai-flex babylai-items-center babylai-justify-between babylai-gap-4'>
56
+ <h2 className="babylai-text-lg babylai-font-semibold">{t('homeSdk.ReviewDialog.title')}</h2>
57
+ <CloseCircle className="babylai-w-6 babylai-h-6 babylai-cursor-pointer" onClick={props.onClose} />
58
+ </header>
59
+ <div className="babylai-flex babylai-flex-col babylai-gap-2">
60
+ <p className="babylai-text-sm babylai-text-gray-600 mb-3">
61
+ {t('homeSdk.ReviewDialog.description')}
62
+ </p>
63
+
64
+ <div className="babylai-flex babylai-items-center babylai-gap-2">
65
+ <span className="babylai-text-base babylai-font-medium">{t('homeSdk.ReviewDialog.rating_label')}</span>
66
+ <Rating value={rating} onChange={handleRatingChange} />
67
+ <span className={`babylai-text-sm babylai-text-red-500 transition-opacity duration-300 ${error.rating ? 'opacity-100' : 'opacity-0'}`}>
68
+ {error.rating}
69
+ </span>
70
+ </div>
71
+
72
+ <div className="babylai-flex babylai-flex-col babylai-gap-2">
73
+ <label htmlFor='comment' className="babylai-text-base babylai-font-medium">{t('homeSdk.ReviewDialog.comment_label')}</label>
74
+ <textarea
75
+ id='comment'
76
+ className="babylai-bg-black-white-100 babylai-p-6 babylai-rounded-xl babylai-resize-none"
77
+ rows={4}
78
+ placeholder="Write your comment here..."
79
+ value={comment}
80
+ onChange={(e) => handleCommentChange(e.target.value)}
81
+ />
82
+ <span className={`babylai-text-sm babylai-text-red-500 transition-opacity duration-300 ${error.comment ? 'opacity-100' : 'opacity-0'}`}>
83
+ {error.comment}
84
+ </span>
85
+ </div>
86
+ </div>
87
+
88
+ <footer className="babylai-flex babylai-justify-between babylai-gap-4 babylai-border-t babylai-pt-4">
89
+ <button
90
+ className="babylai-px-4 babylai-py-2 babylai-rounded-lg babylai-bg-gray-200 babylai-text-gray-700 hover:babylai-bg-gray-300 transition-all"
91
+ onClick={props.onClose}
92
+ >
93
+ {t('homeSdk.ReviewDialog.skip_button')}
94
+ </button>
95
+ <button
96
+ className='babylai-px-4 babylai-py-2 babylai-rounded-lg babylai-shadow transition-all babylai-bg-primary-500 babylai-text-white hover:babylai-bg-primary-600'
97
+ onClick={validateAndSubmit}
98
+ >
99
+ {t('homeSdk.ReviewDialog.submit_button')}
100
+ </button>
101
+ </footer>
102
+ </section>
103
+ )
104
+ }
105
+
106
+ export default ReviewDialog