@aslaluroba/help-center-react 2.0.4 → 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 (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 +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 +366 -136
  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
@@ -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,109 @@ 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,
87
93
  }: 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)
94
+ // ALL HOOKS MUST BE CALLED FIRST - BEFORE ANY EARLY RETURNS
95
+ const [showChat, setShowChat] = useState(false);
96
+ const [isShowList, setIsShowList] = useState<boolean>(!showHelpScreen || false);
97
+ const [expandedOption, setExpandedOption] = useState<Option | null>(null);
98
+ const [endChatConfirmation, setEndChatConfirmation] = useState<boolean>(false);
99
+ const [startNewChatConfirmation, setStartNewChatConfirmation] = useState<boolean>(false);
100
+ const [tempSelectedOption, setTempSelectedOption] = useState<Option | null>(null);
93
101
 
94
- const chatBoxRef = useRef<HTMLDivElement>(null)
102
+ const chatBoxRef = useRef<HTMLDivElement>(null);
103
+ const { t } = useLocalTranslation();
104
+ const messagesRef = useRef<Message[]>([]);
95
105
 
96
- // Scroll to bottom when new messages arrive
97
- useEffect(() => {
106
+ // Memoize the current messages to prevent unnecessary re-renders
107
+ const memoizedMessages = useMemo(() => {
108
+ return messages;
109
+ }, [messages]);
110
+
111
+ // Add this function to show the end chat confirmation modal
112
+ const showEndChatConfirmation = useCallback(() => setEndChatConfirmation(true), []);
113
+
114
+ // Optimize scroll to bottom with debouncing
115
+ const scrollToBottom = useCallback(() => {
98
116
  if (chatBoxRef.current) {
99
- chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight
117
+ requestAnimationFrame(() => {
118
+ if (chatBoxRef.current) {
119
+ chatBoxRef.current.scrollTop = chatBoxRef.current.scrollHeight;
120
+ }
121
+ });
100
122
  }
101
- setCurrentMessages(messages)
102
- }, [messages])
123
+ }, []);
103
124
 
104
- const handleBack = () => {
125
+ const handleBack = useCallback(() => {
105
126
  if (showChat) {
106
- setShowChat(false)
127
+ setShowChat(false);
107
128
  // onEndChat()
108
129
  } else if (selectedOption) {
109
- setSelectedOption(null)
130
+ setSelectedOption(null);
110
131
  } else {
111
- setIsShowList(false)
132
+ setIsShowList(false);
112
133
  }
113
- }
134
+ }, [showChat, selectedOption, setSelectedOption]);
114
135
 
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
136
+ const handleStartChat = useCallback(
137
+ (option: Option) => {
138
+ if (option) {
139
+ if (sessionId) {
140
+ setStartNewChatConfirmation(true);
141
+ setTempSelectedOption(option);
142
+ } else {
143
+ setShowChat(true);
144
+ onStartChat(option);
145
+ setSelectedOption(option);
124
146
  }
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
- }
147
+ }
148
+ },
149
+ [onStartChat, setSelectedOption, sessionId, setStartNewChatConfirmation, setTempSelectedOption]
150
+ );
141
151
 
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
152
+ const handleSendMessage = useCallback(
153
+ (message: string) => {
154
+ if (message.trim()) {
155
+ onSendMessage(message.trim());
152
156
  }
153
- ])
154
- // setTimeout to close the chat after 3 seconds
155
- setTimeout(() => {
156
- onEndChat()
157
- setShowChat(false)
158
- setSelectedOption(null)
159
- }, 3000)
160
- }
157
+ },
158
+ [onSendMessage]
159
+ );
161
160
 
162
- const handleShowActiveChat = () => {
163
- setShowChat(true)
164
- }
161
+ const hideEndChatConfirmation = useCallback(() => {
162
+ setEndChatConfirmation(false);
163
+ }, []);
165
164
 
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
- }
165
+ const handleEndAndStartNewChat = useCallback(async () => {
166
+ if (tempSelectedOption) {
167
+ setStartNewChatConfirmation(false);
168
+ setShowChat(true);
169
+ onEndChat(tempSelectedOption);
170
+ setTempSelectedOption(null);
171
+ }
172
+ }, [onEndChat, setTempSelectedOption, tempSelectedOption, setStartNewChatConfirmation]);
177
173
 
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
- }
174
+ const handleEndChat = useCallback(() => {
175
+ setEndChatConfirmation(false);
176
+ onEndChat();
177
+ setShowChat(false);
178
+ setSelectedOption(null);
179
+ }, [selectedOption, onEndChat, setSelectedOption]);
180
+
181
+ const handleShowActiveChat = useCallback(() => {
182
+ setShowChat(true);
183
+ }, []);
189
184
 
190
- const renderContent = () => {
185
+ // Memoize render content function
186
+ const renderContent = useCallback(() => {
191
187
  if (showChat && selectedOption) {
192
188
  return (
193
189
  <>
194
190
  <ChatWindowHeader
195
191
  handleBack={handleBack}
196
- handleEndChat={handleEndChat}
192
+ handleEndChat={showEndChatConfirmation}
197
193
  onClose={onClose}
198
194
  isShowList={isShowList}
199
195
  showChat={showChat}
@@ -201,12 +197,12 @@ md:babylai-bottom-[6rem] md:babylai-right-4 babylai-rounded-none md:babylai-roun
201
197
  />
202
198
  <ChatWindow
203
199
  onSendMessage={handleSendMessage}
204
- messages={currentMessages}
200
+ messages={memoizedMessages}
205
201
  assistantStatus={assistantStatus}
206
202
  needsAgent={needsAgent}
207
203
  />
208
204
  </>
209
- )
205
+ );
210
206
  }
211
207
 
212
208
  if (isShowList) {
@@ -219,46 +215,124 @@ md:babylai-bottom-[6rem] md:babylai-right-4 babylai-rounded-none md:babylai-roun
219
215
  handleBack={showHelpScreen ? handleBack : onClose}
220
216
  showHelpScreen={showHelpScreen}
221
217
  />
222
- )
218
+ );
219
+ }
220
+ return <HomeScreen setIsShowList={setIsShowList} onClose={onClose} />;
221
+ }, [
222
+ showChat,
223
+ selectedOption,
224
+ handleBack,
225
+ showEndChatConfirmation,
226
+ onClose,
227
+ isShowList,
228
+ setIsShowList,
229
+ handleSendMessage,
230
+ memoizedMessages,
231
+ assistantStatus,
232
+ needsAgent,
233
+ helpScreen,
234
+ expandedOption,
235
+ setExpandedOption,
236
+ handleStartChat,
237
+ showHelpScreen,
238
+ ]);
239
+
240
+ // Memoize confirmation modal
241
+ const confirmationModal = useMemo(() => {
242
+ if (!endChatConfirmation) return null;
243
+
244
+ return (
245
+ <ConfirmationModal
246
+ title={t('homeSdk.ConfirmationModal.title')}
247
+ message={t('homeSdk.ConfirmationModal.message')}
248
+ onCancel={hideEndChatConfirmation}
249
+ onConfirm={handleEndChat}
250
+ />
251
+ );
252
+ }, [endChatConfirmation, t, hideEndChatConfirmation, handleEndChat]);
253
+
254
+ // Memoize active chat button
255
+ const activeChatButton = useMemo(() => {
256
+ if (!sessionId || showChat) return null;
257
+
258
+ return (
259
+ <Button
260
+ variant='rounded-icon'
261
+ size='icon'
262
+ 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'
263
+ onClick={handleShowActiveChat}
264
+ >
265
+ <ChatIcon className='babylai-w-5 babylai-h-5 babylai-text-black-white-50' />
266
+ </Button>
267
+ );
268
+ }, [sessionId, showChat, handleShowActiveChat]);
269
+
270
+ // Scroll to bottom when new messages arrive - optimize with ref comparison
271
+ useEffect(() => {
272
+ if (messagesRef.current !== messages) {
273
+ messagesRef.current = messages;
274
+ scrollToBottom();
223
275
  }
224
- return <HomeScreen setIsShowList={setIsShowList} onClose={onClose} />
276
+ }, [messages, scrollToBottom]);
277
+
278
+ // EARLY RETURNS MUST COME AFTER ALL HOOKS
279
+ // Early returns for performance - moved after all hooks
280
+ if (status === 'loading' && !helpScreen) {
281
+ return (
282
+ <div
283
+ 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)]
284
+ babylai-overflow-auto babylai-w-full babylai-bg-black-white-50 md:babylai-mb-4
285
+ 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'
286
+ >
287
+ <ChatBotLoadingScreen isShowList={isShowList} onClose={onClose} />
288
+ </div>
289
+ );
290
+ }
291
+
292
+ if (error) {
293
+ return (
294
+ <div
295
+ 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)]
296
+ babylai-overflow-auto babylai-w-full babylai-bg-black-white-50 md:babylai-mb-4
297
+ 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'
298
+ >
299
+ <ChatBotErrorScreen onClose={onClose} error={error || ''} />
300
+ </div>
301
+ );
225
302
  }
226
303
 
227
304
  return (
228
305
  <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)]
306
+ 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
307
  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"
308
+ 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
309
  >
233
310
  <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>
311
+ className={cn(
312
+ 'babylai-h-full babylai-rounded-none md:babylai-rounded-3xl babylai-flex babylai-flex-col babylai-relative',
313
+ {
314
+ 'babylai-bg-gradient-to-b babylai-from-primary-500 babylai-to-primary-500/60': !isShowList,
315
+ 'babylai-bg-black-white-100': isShowList,
316
+ }
248
317
  )}
318
+ >
319
+ {activeChatButton}
320
+
249
321
  {/* Content */}
250
322
  {renderContent()}
251
323
 
252
324
  {/* End Chat Confirmation Modal */}
253
- {endChatConfirmation && (
325
+ {confirmationModal}
326
+
327
+ {startNewChatConfirmation && (
254
328
  <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}
329
+ title={t('homeSdk.ConfirmationModal.endAndStartNewChatTitle')}
330
+ message={t('homeSdk.ConfirmationModal.endAndStartNewChatMessage')}
331
+ onCancel={() => setStartNewChatConfirmation(false)}
332
+ onConfirm={handleEndAndStartNewChat}
259
333
  />
260
334
  )}
261
335
  </div>
262
336
  </div>
263
- )
337
+ );
264
338
  }
@@ -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