Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
23 changes: 23 additions & 0 deletions src/assets/images/icons/SideDrawer/WhatsappForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const WhatsAppForm = ({ color }: { color: string }) => {
return (
<svg
className="w-6 h-6 "
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
color={color}
>
<path
stroke={'currentColor'}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M15 4h3a1 1 0 0 1 1 1v15a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h3m0 3h6m-6 5h6m-6 4h6M10 3v4h4V3h-4Z"
/>
</svg>
);
};
export default WhatsAppForm;
5 changes: 5 additions & 0 deletions src/common/HelpData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,8 @@ export const certificatesInfo: HelpDataProps = {
heading: 'An overview of all the certificates created to date',
link: 'https://glific.github.io/docs/docs/Product%20Features/Custom%20Certificates',
};

export const whatsappFormsInfo: HelpDataProps = {
heading: 'An overview of all the whatsapp forms created to date',
link: 'https://glific.github.io/docs/docs/Product%20Features/Custom%20Certificates',
};
2 changes: 2 additions & 0 deletions src/components/UI/ListIcon/ListIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import FiberNewIcon from '@mui/icons-material/FiberNew';
import { Badge } from '@mui/material';
import DiscordIcon from 'assets/images/icons/Discord/DiscordIcon';
import CertificateIcon from 'assets/images/icons/SideDrawer/CertificateIcon';
import WhatsAppForms from 'assets/images/icons/SideDrawer/WhatsappForm';

export interface ListIconProps {
icon: string | undefined;
Expand Down Expand Up @@ -79,6 +80,7 @@ export const ListIcon = ({ icon = '', selected = false, count }: ListIconProps)
discord: DiscordIcon,
waPolls: WaPolls,
certificate: CertificateIcon,
form: WhatsAppForms,
};

const iconImage = stringsToIcons[icon] && (
Expand Down
8 changes: 8 additions & 0 deletions src/config/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,14 @@ const menus = (): Menu[] => [
type: 'sideDrawer',
roles: managerLevel,
},
{
title: 'WhatsApp Forms',
path: '/whatsapp-forms',
icon: 'form',
type: 'sideDrawer',
roles: managerLevel,
show: !getOrganizationServices('whatsappFormsEnabled'),
},
{
title: 'Triggers',
path: '/trigger',
Expand Down
11 changes: 11 additions & 0 deletions src/containers/Chat/ChatMessages/ChatMessage/ChatMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import styles from './ChatMessage.module.css';
import { setNotification } from 'common/notification';
import { LocationRequestTemplate } from './LocationRequestTemplate/LocationRequestTemplate';
import { PollMessage } from './PollMessage/PollMessage';
import { WhatsAppFormResponse } from './WhatsappFormResponse/WhatsAppFormResponse';

export interface ChatMessageProps {
id: number;
Expand Down Expand Up @@ -60,6 +61,7 @@ export interface ChatMessageProps {
poll?: any;
pollContent?: any;
showIcon?: boolean;
whatsappFormResponse?: any;
}

export const ChatMessage = ({
Expand Down Expand Up @@ -88,6 +90,7 @@ export const ChatMessage = ({
poll,
pollContent,
showIcon = true,
whatsappFormResponse,
}: ChatMessageProps) => {
const [showSaveMessageDialog, setShowSaveMessageDialog] = useState(false);
const Ref = useRef(null);
Expand Down Expand Up @@ -330,6 +333,14 @@ export const ChatMessage = ({
/>
</>
);
} else if (type === 'WHATSAPP_FORM_RESPONSE') {
messageBody = (
<>
{contactName}
<WhatsAppFormResponse rawResponse={whatsappFormResponse?.rawResponse} />
{dateAndSendBy}
</>
);
} else {
messageBody = (
<>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.FormResponseContainer {
display: flex;
align-items: center;
padding: 12px 16px;
border-radius: 12px;
cursor: pointer;
max-width: 280px;
background-color: #0000004c;
}

.Content {
display: flex;
flex-direction: column;
color: #fff !important;
}

.Title {
font-weight: 500;
color: #fff;
margin-bottom: 2px;
}

.Subtitle {
color: #afafaf;
font-size: 12px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Typography } from '@mui/material';

import { DialogBox } from 'components/UI/DialogBox/DialogBox';
import { useEffect, useState } from 'react';
import styles from './WhatsAppFormResponse.module.css';

interface WhatsAppFormResponseProps {
rawResponse: string;
}

export const WhatsAppFormResponse = ({ rawResponse }: WhatsAppFormResponseProps) => {
const [open, setOpen] = useState<boolean>(false);
const [parsedResponse, setParsedResponse] = useState<any>({});

const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);

useEffect(() => {
console.log(rawResponse);
if (rawResponse) {
try {
const response = JSON.parse(rawResponse);
setParsedResponse(response);
} catch (error) {
console.error('Error parsing WhatsApp form response:', error);
setParsedResponse({ error: 'Invalid response format' });
}
}
}, [rawResponse]);

return (
<>
<div className={styles.FormResponseContainer}>
<div onClick={handleOpen} className={styles.Content}>
<Typography variant="body2" className={styles.Title}>
View Response
</Typography>
<Typography variant="caption" className={styles.Subtitle}>
Response recieved
</Typography>
</div>
</div>
{open && (
<DialogBox
open={open}
title="WhatsApp Form Response"
handleCancel={handleClose}
skipCancel
handleOk={handleClose}
buttonOk="Close"
>
{Object.keys(parsedResponse).length > 0 ? (
<>
{Object.entries(parsedResponse).map(([key, value]) => (
<div key={key} style={{ marginBottom: '8px' }}>
<Typography variant="subtitle2" style={{ fontWeight: 'bold' }}>
{key}:
</Typography>
<Typography variant="body2">
{typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value)}
</Typography>
</div>
))}
</>
) : (
<Typography variant="body2">No response data available.</Typography>
)}
</DialogBox>
)}
</>
);
};
76 changes: 76 additions & 0 deletions src/containers/WhatsAppForms/WhatsAppForm.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
.FlowBuilderInfo {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1.5rem;
margin: 2rem;
margin-bottom: 0;
background: linear-gradient(135deg, #f0fdf4 0%, #ecfdf5 100%);
border: 1px solid #bbf7d0;
border-radius: 12px;
box-shadow: 0 1px 3px 0 rgba(34, 197, 94, 0.1);
transition: all 0.2s ease-in-out;
}

.FlowBuilderInfo:hover {
box-shadow: 0 4px 6px -1px rgba(34, 197, 94, 0.15);
transform: translateY(-1px);
}

.FlowBuilderInfo p {
margin-bottom: 0;
}

.InfoContent {
display: flex;
align-items: flex-start;
gap: 1rem;
flex: 1;
}

.IconWrapper {
display: flex;
align-items: center;
justify-content: center;
width: 3rem;
height: 3rem;
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
border-radius: 12px;
flex-shrink: 0;
}

.Icon {
color: white !important;
font-size: 24px !important;
}

.Title {
margin: 0 0 8px 0;
font-size: 1.125rem;
font-weight: 600;
color: #14532d;
line-height: 1.4;
}

.Description {
margin: 0;
font-size: 0.875rem;
color: #16a34a;
line-height: 1.5;
}

.FlowBuilderButton {
padding: 0.75rem 1.5rem !important;
font-weight: 500 !important;
border-radius: 8px !important;
white-space: nowrap;
min-width: 160px;
transition: all 0.2s ease-in-out !important;
color: #fff !important;
}

.FlowBuilderButton:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(34, 197, 94, 0.3) !important;

}
86 changes: 86 additions & 0 deletions src/containers/WhatsAppForms/WhatsAppForm.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { MockedProvider } from '@apollo/client/testing';
import { fireEvent, render, waitFor } from '@testing-library/react';
import * as Notification from 'common/notification';
import { formJson, WHATSAPP_FORM_MOCKS } from 'mocks/WhatsApp';
import { MemoryRouter, Route, Routes } from 'react-router';
import WhatsAppFormList from './WhatsAppFormList/WhatsAppFormList';
import WhatsAppForms from './WhatsAppForms';

describe('<WhatsAppForms />', () => {
const notificationSpy = vi.spyOn(Notification, 'setNotification');
const wrapper = (initialEntry: string = '/whatsapp-forms/add') => (
<MockedProvider mocks={WHATSAPP_FORM_MOCKS}>
<MemoryRouter initialEntries={[initialEntry]}>
<Routes>
<Route path="/whatsapp-forms/add" element={<WhatsAppForms />} />
<Route path="/whatsapp-forms/:id/edit" element={<WhatsAppForms />} />
<Route path="/whatsapp-forms" element={<WhatsAppFormList />} />
</Routes>
</MemoryRouter>
</MockedProvider>
);

test('it should render the WhatsApp Form page initially', async () => {
const { getByText } = render(wrapper());
expect(getByText('Loading...')).toBeInTheDocument();

await waitFor(() => {
expect(getByText('Create WhatsApp Form')).toBeInTheDocument();
});
});

test('it should create WhatsApp Form page with form fields', async () => {
const { getByTestId, getByText, getAllByRole } = render(wrapper());
expect(getByText('Loading...')).toBeInTheDocument();

await waitFor(() => {
expect(getByText('Create WhatsApp Form')).toBeInTheDocument();
});

const inputs = getAllByRole('textbox');

fireEvent.change(inputs[0], { target: { value: 'Test Form' } });
fireEvent.change(inputs[1], { target: { value: 'This is a test form' } });
fireEvent.change(inputs[2], { target: { value: 'sign_up' } });

fireEvent.click(getByTestId('submitActionButton'));

await waitFor(() => {
expect(getByText('Must be valid JSON')).toBeInTheDocument();
});

const autocomplete = getByTestId('AutocompleteInput');

autocomplete.focus();
fireEvent.keyDown(autocomplete, { key: 'ArrowDown' });

fireEvent.click(getByText('Other'), { key: 'Enter' });
fireEvent.change(inputs[2], { target: { value: JSON.stringify(formJson) } });

fireEvent.click(getByTestId('submitActionButton'));

await waitFor(() => {
expect(notificationSpy).toHaveBeenCalledWith('Whatsapp Form created successfully!');
});
});

test('it should edit WhatsApp Form page with form fields', async () => {
const { getByTestId, getByText, getAllByRole } = render(wrapper('/whatsapp-forms/1/edit'));
expect(getByText('Loading...')).toBeInTheDocument();

await waitFor(() => {
expect(getByText('Edit WhatsApp Form')).toBeInTheDocument();
});

const inputs = getAllByRole('textbox');

fireEvent.change(inputs[0], { target: { value: 'Updated Form Name' } });
fireEvent.change(inputs[1], { target: { value: 'This is an updated test form' } });

fireEvent.click(getByTestId('submitActionButton'));

await waitFor(() => {
expect(notificationSpy).toHaveBeenCalled();
});
});
});
Loading
Loading