Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev #72

Merged
merged 4 commits into from
Jul 31, 2024
Merged

Dev #72

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/src/client/app/ChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const chatPage = ({ user }: { user: User }) => {
useEffect(() => {
if (formSubmitMsg && currentChatDetails) {
if (!currentChatDetails.userRespondedWithNextAction) {
setTriggerChatFormSubmitMsg(formSubmitMsg);
setTriggerChatFormSubmitMsg(decodeURIComponent(formSubmitMsg));
}
removeQueryParameters();
}
Expand Down Expand Up @@ -145,6 +145,7 @@ const chatPage = ({ user }: { user: User }) => {
triggerChatFormSubmitMsg={triggerChatFormSubmitMsg}
refetchAllChatDetails={refetchAllChatDetails}
triggerScrollBarMove={triggerScrollBarMove}
setNotificationErrorMessage={setNotificationErrorMessage}
>
<div className='flex h-full flex-col'>
{currentChatDetails ? (
Expand Down
3 changes: 3 additions & 0 deletions app/src/client/app/layout/ChatLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface Props {
triggerChatFormSubmitMsg?: string | null;
refetchAllChatDetails: boolean;
triggerScrollBarMove: boolean;
setNotificationErrorMessage: React.Dispatch<React.SetStateAction<string | null>>;
}

const ChatLayout: FC<Props> = ({
Expand All @@ -25,6 +26,7 @@ const ChatLayout: FC<Props> = ({
triggerChatFormSubmitMsg,
refetchAllChatDetails,
triggerScrollBarMove,
setNotificationErrorMessage,
}) => {
const [sidebarOpen, setSidebarOpen] = useState(false);
const [shouldAutoScroll, setShouldAutoScroll] = useState<boolean>(false);
Expand Down Expand Up @@ -75,6 +77,7 @@ const ChatLayout: FC<Props> = ({
handleFormSubmit={handleFormSubmit}
currentChatDetails={currentChatDetails}
triggerChatFormSubmitMsg={triggerChatFormSubmitMsg}
setNotificationErrorMessage={setNotificationErrorMessage}
/>
</div>

Expand Down
203 changes: 88 additions & 115 deletions app/src/client/components/ChatForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,150 +10,123 @@ interface ChatFormProps {
handleFormSubmit: (userQuery: string, isUserRespondedWithNextAction?: boolean, retrySameChat?: boolean) => void;
currentChatDetails: Chat | null | undefined;
triggerChatFormSubmitMsg?: string | null;
setNotificationErrorMessage: React.Dispatch<React.SetStateAction<string | null>>;
}

export default function ChatForm({ handleFormSubmit, currentChatDetails, triggerChatFormSubmitMsg }: ChatFormProps) {
const [formInputValue, setFormInputValue] = useState('');
const [disableFormSubmit, setDisableFormSubmit] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [toggleTextAreaFocus, setToggleTextAreaFocus] = useState(false);
const isProcessing = useRef(false);
const textAreaRef = React.useRef<HTMLTextAreaElement>();
const isEmptyMessage = formInputValue.trim().length === 0;
export default function ChatForm({
handleFormSubmit,
currentChatDetails,
triggerChatFormSubmitMsg,
setNotificationErrorMessage,
}: ChatFormProps) {
const [message, setMessage] = useState<string>('');
const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
const history = useHistory();
const hasTriggerSubmitted = useRef(false);

const formRef = useCallback(
async (node: any) => {
if (node !== null && triggerChatFormSubmitMsg) {
await handleFormSubmit(triggerChatFormSubmitMsg, true);
}
},
[triggerChatFormSubmitMsg]
);
const isInputDisabled = useCallback(() => {
return (
hasTriggerSubmitted.current || currentChatDetails?.team_status === 'inprogress' || currentChatDetails?.showLoader
);
}, [currentChatDetails]);

const setFocusOnTextArea = () => {
if (textAreaRef.current) {
textAreaRef.current.focus();
}
};
useEffect(() => {
const responseCompleted = !disableFormSubmit || !isSubmitting || !isProcessing.current;
if (toggleTextAreaFocus && responseCompleted) {
setFocusOnTextArea();
if (!isInputDisabled()) {
textAreaRef.current?.focus();
}
}, [disableFormSubmit, isSubmitting, isProcessing.current, toggleTextAreaFocus]);

useSocketListener('streamFromTeamFinished', () => {
setToggleTextAreaFocus(true);
});
}, [isInputDisabled]);

useEffect(() => {
if (currentChatDetails) {
setDisableFormSubmit(currentChatDetails.team_status === 'inprogress');
} else {
setDisableFormSubmit(false);
if (currentChatDetails && currentChatDetails.isChatTerminated) {
return;
}
setFocusOnTextArea();
textAreaRef.current?.focus();
}, [currentChatDetails]);

const submitForm = async () => {
if (isSubmitting || disableFormSubmit || isProcessing.current || isEmptyMessage) return;
useSocketListener('streamFromTeamFinished', () => {
textAreaRef.current?.focus();
hasTriggerSubmitted.current = false;
});

const formRef = useCallback(
async (node: HTMLFormElement | null) => {
if (node !== null && triggerChatFormSubmitMsg && !hasTriggerSubmitted.current) {
hasTriggerSubmitted.current = true;
await handleFormSubmit(triggerChatFormSubmitMsg, true);
}
},
[triggerChatFormSubmitMsg, handleFormSubmit]
);

const msgToSubmit = formInputValue.trim();
const submitMessage = async () => {
if (isInputDisabled() || !message.trim()) return;

setIsSubmitting(true);
setToggleTextAreaFocus(false);
isProcessing.current = true;
hasTriggerSubmitted.current = true;

try {
if (!currentChatDetails) {
const chat: Chat = await createNewChat();
history.push(`/chat/${chat.uuid}?initiateChatMsg=${msgToSubmit}`);
setFormInputValue('');
} else if (
currentChatDetails &&
!currentChatDetails.showLoader &&
currentChatDetails.team_status !== 'inprogress'
) {
setFormInputValue('');
handleFormSubmit(msgToSubmit);
const chat = await createNewChat();
history.push(`/chat/${chat.uuid}?initiateChatMsg=${encodeURIComponent(message.trim())}`);
hasTriggerSubmitted.current = false;
} else {
await handleFormSubmit(message.trim());
}
} catch (err: any) {
console.log('Error: ' + err.message);
window.alert('Error: Something went wrong. Please try again later.');
setMessage('');
} catch (err) {
console.error('Error submitting message:', err);
setNotificationErrorMessage('Oops! Something went wrong while sending your message. Please try again later.');
} finally {
setIsSubmitting(false);
isProcessing.current = false;
hasTriggerSubmitted.current = false;
}
};

const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
await submitForm();
};

const handleButtonClick = async (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
await submitForm();
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
submitMessage();
};

const handleKeyDown = async (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
await submitForm();
submitMessage();
}
};

return (
<div className='mt-2 mb-2'>
<form data-testid='chat-form' onSubmit={handleSubmit} className='' ref={formRef}>
<label htmlFor='search' className='mb-2 text-sm font-medium text-captn-dark-blue sr-only dark:text-white'>
Search
</label>
<div className='relative bottom-0 left-0 right-0 flex items-center justify-between m-1'>
<TextareaAutosize
autoFocus
minRows={1}
maxRows={4}
style={{
lineHeight: 2,
resize: 'none',
}}
id='userQuery'
name='search'
className='block rounded-lg w-full h-12 text-sm text-white bg-primary focus:outline-none focus:ring-0 focus:border-captn-light-blue'
placeholder='Enter your message...'
required
value={formInputValue}
onChange={(e) => setFormInputValue(e.target.value)}
disabled={disableFormSubmit || isSubmitting}
ref={textAreaRef}
onKeyDown={handleKeyDown}
/>
<button
type='button'
disabled={disableFormSubmit || isSubmitting || isEmptyMessage}
onClick={handleButtonClick}
className={`text-primary bg-secondary hover:opacity-90 absolute right-2 font-medium rounded-lg text-sm px-1.5 py-1.5 ${
disableFormSubmit || isSubmitting || isEmptyMessage
? 'cursor-not-allowed bg-white opacity-70 hover:opacity-70'
: 'cursor-pointer'
}`}
>
<span className=''>
<svg width='20' height='20' viewBox='0 0 24 24' fill='none' className='text-primary'>
<path
d='M7 11L12 6L17 11M12 18V7'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
></path>
</svg>
</span>
</button>
</div>
</form>
</div>
<form data-testid='chat-form' onSubmit={handleSubmit} className='mt-2 mb-2' ref={formRef}>
<div className='relative flex items-center m-1'>
<TextareaAutosize
ref={textAreaRef}
value={message}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setMessage(e.target.value)}
onKeyDown={handleKeyDown}
placeholder='Enter your message...'
minRows={1}
maxRows={4}
className='w-full p-2 text-sm text-white bg-primary rounded-lg focus:outline-none focus:ring-0'
style={{ resize: 'none', lineHeight: '1.5' }}
/>
<button
type='submit'
disabled={isInputDisabled() || !message.trim()}
className={`absolute right-2 p-1.5 rounded-lg ${
isInputDisabled() || !message.trim()
? 'bg-gray-300 cursor-not-allowed'
: 'bg-secondary hover:opacity-90 cursor-pointer'
}`}
aria-label='Send message'
>
<svg width='20' height='20' viewBox='0 0 24 24' fill='none' className='text-primary'>
<path
d='M7 11L12 6L17 11M12 18V7'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
/>
</svg>
</button>
</div>
</form>
);
}
12 changes: 9 additions & 3 deletions app/src/client/tests/ChatForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const renderChatForm = (props = {}) => {
handleFormSubmit: vi.fn(),
currentChatDetails: null,
triggerChatFormSubmitMsg: null,
setNotificationErrorMessage: vi.fn(),
};
return renderInContext(<ChatForm {...defaultProps} {...props} />);
};
Expand All @@ -56,7 +57,12 @@ describe('ChatForm', () => {
test('creates a new chat when currentChatDetails is null', async () => {
renderInContext(
<Router history={history}>
<ChatForm handleFormSubmit={vi.fn()} currentChatDetails={null} triggerChatFormSubmitMsg={null} />
<ChatForm
handleFormSubmit={vi.fn()}
currentChatDetails={null}
triggerChatFormSubmitMsg={null}
setNotificationErrorMessage={vi.fn()}
/>
</Router>
);

Expand All @@ -70,7 +76,7 @@ describe('ChatForm', () => {
fireEvent.submit(form);
});

expect(pushSpy).toHaveBeenCalledWith('/chat/new-chat-uuid?initiateChatMsg=New chat message');
expect(pushSpy).toHaveBeenCalledWith('/chat/new-chat-uuid?initiateChatMsg=New%20chat%20message');
expect(mockCreateNewChat).toHaveBeenCalled();
});
});
Expand Down Expand Up @@ -142,7 +148,7 @@ describe('ChatForm', () => {
const input = screen.getByPlaceholderText('Enter your message...');
const submitButton = screen.getByRole('button');

expect(input).toBeDisabled();
expect(input).not.toBeDisabled();
expect(submitButton).toBeDisabled();
});
});
Expand Down