diff --git a/src/assets/images/Polls.svg b/src/assets/images/Polls.svg new file mode 100644 index 0000000000..8dbba865e0 --- /dev/null +++ b/src/assets/images/Polls.svg @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/src/assets/images/icons/SideDrawer/WaPolls.tsx b/src/assets/images/icons/SideDrawer/WaPolls.tsx new file mode 100644 index 0000000000..062ca16a0f --- /dev/null +++ b/src/assets/images/icons/SideDrawer/WaPolls.tsx @@ -0,0 +1,9 @@ +const SvgComponent = ({ color }: { color: string }) => ( + + + +); +export default SvgComponent; diff --git a/src/common/HelpData.tsx b/src/common/HelpData.tsx index 4bd0ea9b3f..a40dbe7456 100644 --- a/src/common/HelpData.tsx +++ b/src/common/HelpData.tsx @@ -129,3 +129,8 @@ export const templateStatusInfo: HelpDataProps = { ), link: 'https://docs.gupshup.io/docs/message-template-approvals-statuses', }; + +export const pollsInfo: HelpDataProps = { + heading: 'An overview of all the polls created to date', + link: 'https://glific.github.io/docs/docs/category/flows', +}; diff --git a/src/components/UI/ListIcon/ListIcon.tsx b/src/components/UI/ListIcon/ListIcon.tsx index d991108572..545e387434 100644 --- a/src/components/UI/ListIcon/ListIcon.tsx +++ b/src/components/UI/ListIcon/ListIcon.tsx @@ -29,10 +29,11 @@ import WaCollectionIcon from 'assets/images/icons/SideDrawer/WaGroupCollection'; import WaGroupIcon from 'assets/images/icons/SideDrawer/WhatsAppGroupIcon'; import KnowledgeBaseIcon from 'assets/images/icons/SideDrawer/KnowledgeBaseIcon'; import Assistant from 'assets/images/icons/SideDrawer/Assistant'; +import WaPolls from 'assets/images/icons/SideDrawer/WaPolls'; import styles from './ListIcon.module.css'; import FiberNewIcon from '@mui/icons-material/FiberNew'; import { Badge } from '@mui/material'; -import DiscordIcon from 'assets/images/icons/Discord/DiscordIcon' +import DiscordIcon from 'assets/images/icons/Discord/DiscordIcon'; export interface ListIconProps { icon: string | undefined; count?: number; @@ -75,7 +76,8 @@ export const ListIcon = ({ icon = '', selected = false, count }: ListIconProps) waGroup: WaGroupIcon, knowledgeBase: KnowledgeBaseIcon, assistant: Assistant, - discord:DiscordIcon + discord: DiscordIcon, + waPolls: WaPolls, }; const iconImage = stringsToIcons[icon] && ( diff --git a/src/config/menu.ts b/src/config/menu.ts index 24245f506b..2ee5504066 100644 --- a/src/config/menu.ts +++ b/src/config/menu.ts @@ -51,6 +51,13 @@ const menus = (): Menu[] => [ type: 'sideDrawer', roles: allRoles, }, + { + title: 'WhatsApp Polls', + path: '/group/polls', + icon: 'waPolls', + type: 'sideDrawer', + roles: allRoles, + }, ], }, { @@ -279,22 +286,22 @@ const menus = (): Menu[] => [ roles: staffLevel, }, - // { - // title: "What's new", - // path: '/changelog', - // url: NEW_UI_BLOG, - // icon: 'new', - // type: 'sideDrawer', - // roles: staffLevel, - // }, - { - title: "Discord", - path: '/discord', - url: DISCORD_URL, - icon: 'discord', - type: 'sideDrawer', - roles: staffLevel, - }, + // { + // title: "What's new", + // path: '/changelog', + // url: NEW_UI_BLOG, + // icon: 'new', + // type: 'sideDrawer', + // roles: staffLevel, + // }, + { + title: 'Discord', + path: '/discord', + url: DISCORD_URL, + icon: 'discord', + type: 'sideDrawer', + roles: staffLevel, + }, ]; export const getMenus = (menuType = 'sideDrawer', role = 'Staff') => diff --git a/src/containers/WaGroups/WaPolls/WaPollOptions/WaPollOptions.module.css b/src/containers/WaGroups/WaPolls/WaPollOptions/WaPollOptions.module.css new file mode 100644 index 0000000000..5494979456 --- /dev/null +++ b/src/containers/WaGroups/WaPolls/WaPollOptions/WaPollOptions.module.css @@ -0,0 +1,46 @@ +.Container { + background-color: #f8faf5; + border: 1px solid #cccccc; + border-radius: 4px; + margin: 1rem 0; + padding: 1rem; +} + +.Title { + color: #555555; + font-weight: 500; + line-height: 18px; + font-size: 16px; + margin-bottom: 1rem; +} + +.OptionField { + display: flex; + gap: 1rem; + align-items: center; +} + +.TextField { + width: 100%; + background-color: #ffffff; + border-radius: 12px; +} + +.Options { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.Options button { + width: 40%; + align-self: flex-end; +} + +.RemoveIcon { + cursor: pointer; +} + +.EmojiButton { + margin-right: -1rem; +} diff --git a/src/containers/WaGroups/WaPolls/WaPollOptions/WaPollOptions.tsx b/src/containers/WaGroups/WaPolls/WaPollOptions/WaPollOptions.tsx new file mode 100644 index 0000000000..20aebc3b4c --- /dev/null +++ b/src/containers/WaGroups/WaPolls/WaPollOptions/WaPollOptions.tsx @@ -0,0 +1,146 @@ +import { ClickAwayListener, FormControl, FormHelperText, IconButton, TextField, Typography } from '@mui/material'; +import styles from './WaPollOptions.module.css'; +import { Button } from 'components/UI/Form/Button/Button'; +import CrossIcon from 'assets/images/icons/Cross.svg?react'; +import EmojiPicker from 'components/UI/EmojiPicker/EmojiPicker'; +import { useState } from 'react'; +import EmojiEmotionsOutlinedIcon from '@mui/icons-material/EmojiEmotionsOutlined'; + +interface WaPollOptionsProps { + form: { field: any; errors: any; touched: any; values: any; setFieldValue: any }; + options: string[]; +} + +const emojiStyles = { + position: 'absolute', + bottom: '25px', + right: '-150px', + zIndex: 100, +}; + +export const WaPollOptions = ({ form: { values, setFieldValue, errors, touched } }: WaPollOptionsProps) => { + const handleAddOption = () => { + setFieldValue('options', [...values.options, '']); + }; + + const handleInput = (value: any, ind: any) => { + const newOptions = [...values.options]; + newOptions[ind] = value; + setFieldValue('options', newOptions); + }; + + const handleEmojiAdd = (emoji: any, ind: number) => { + console.log(emoji); + + const newOptions = [...values.options]; + const value = newOptions[ind] + emoji?.native; + newOptions[ind] = value; + setFieldValue('options', newOptions); + }; + + const handleRemoveClick = (index: any) => { + const newOptions = [...values.options]; + setFieldValue( + 'options', + newOptions.filter((_, ind: any) => ind !== index) + ); + }; + + return ( +
+ + Poll Options + +
+ {values.options.map((option: any, ind: number) => ( + + ))} + {values.options.length < 10 && ( + + )} +
+
+ ); +}; + +interface PollOptionProps { + option: string; + ind: number; + options: any; + errors: any; + touched: any; + handleInput: any; + handleRemoveClick: any; + handleEmojiAdd: any; +} + +const PollOption = ({ + option, + ind, + options, + errors, + touched, + handleInput, + handleRemoveClick, + handleEmojiAdd, +}: PollOptionProps) => { + const [showEmojiPicker, setShowEmojiPicker] = useState(false); + const hasError = errors && touched && errors[ind] && touched[ind]; + + return ( + +
+ handleInput(event.target.value, ind)} + slotProps={{ + input: { + endAdornment: ( + setShowEmojiPicker(false)}> + setShowEmojiPicker(!showEmojiPicker)} + > + + + + ), + }, + }} + /> + + {options.length !== 2 && ( + handleRemoveClick(ind)} + /> + )} + {showEmojiPicker && ( + handleEmojiAdd(emoji, ind)} displayStyle={emojiStyles} /> + )} +
+ {hasError ? {errors[ind]} : null} +
+ ); +}; diff --git a/src/containers/WaGroups/WaPolls/WaPolls.module.css b/src/containers/WaGroups/WaPolls/WaPolls.module.css new file mode 100644 index 0000000000..d3ebe260eb --- /dev/null +++ b/src/containers/WaGroups/WaPolls/WaPolls.module.css @@ -0,0 +1,7 @@ +.AllowMultiple { + color: #555555; + font-weight: 400; + line-height: 18px; + font-size: 16px; + padding: 1rem 0; +} diff --git a/src/containers/WaGroups/WaPolls/WaPolls.tsx b/src/containers/WaGroups/WaPolls/WaPolls.tsx new file mode 100644 index 0000000000..7678a44ed1 --- /dev/null +++ b/src/containers/WaGroups/WaPolls/WaPolls.tsx @@ -0,0 +1,101 @@ +import { Typography } from '@mui/material'; +import { Input } from 'components/UI/Form/Input/Input'; +import { FormLayout } from 'containers/Form/FormLayout'; +import { CREATE_COLLECTION, DELETE_COLLECTION, UPDATE_COLLECTION } from 'graphql/mutations/Collection'; +import { GET_COLLECTION } from 'graphql/queries/Collection'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import * as Yup from 'yup'; +import PollsIcon from 'assets/images/Polls.svg?react'; +import { Checkbox } from 'components/UI/Form/Checkbox/Checkbox'; +import styles from './WaPolls.module.css'; +import { WaPollOptions } from './WaPollOptions/WaPollOptions'; +const queries = { + getItemQuery: GET_COLLECTION, + createItemQuery: CREATE_COLLECTION, + updateItemQuery: UPDATE_COLLECTION, + deleteItemQuery: DELETE_COLLECTION, +}; +const pollsIcon = ; + +export const WaPolls = () => { + const [title, setTitle] = useState(''); + const [content, setContent] = useState(''); + const [options, setOptions] = useState(['', '']); + const [allowMultiple, setAllowMultiple] = useState(false); + + const { t } = useTranslation(); + const states = { + title, + content, + options, + allowMultiple, + }; + + const setPayload = (payload: any) => { + console.log(payload); + return payload; + }; + const setStates = (payload: any) => {}; + + const FormSchema = Yup.object().shape({ + title: Yup.string().required(t('Title is required.')).max(50, t('Title is too long.')), + content: Yup.string().required('Content is required.').max(150, 'Content is too long.'), + options: Yup.array().of(Yup.string().required('Required')).min(2, 'At least two options are required'), + }); + + const dialogMessage = "You won't be able to use this poll again."; + + const formFields = [ + { + component: Input, + name: 'title', + type: 'text', + label: t('Title'), + }, + + { + component: Input, + name: 'content', + type: 'text', + label: 'Content', + textArea: true, + rows: 6, + }, + { + component: WaPollOptions, + name: 'options', + }, + { + component: Checkbox, + name: 'allowMultiple', + title: ( + + Allow multiple options + + ), + darkCheckbox: true, + }, + ]; + + return ( + + ); +}; + +export default WaPolls; diff --git a/src/containers/WaGroups/WaPolls/WaPollsList/WaPollsList.module.css b/src/containers/WaGroups/WaPolls/WaPollsList/WaPollsList.module.css new file mode 100644 index 0000000000..be8204cbb0 --- /dev/null +++ b/src/containers/WaGroups/WaPolls/WaPollsList/WaPollsList.module.css @@ -0,0 +1,26 @@ +.Label { + width: 300px; +} + +.Content { + width: 400px; +} + +.Actions { + width: 30%; + min-width: 200px; + text-align: end; +} + +.LabelText { + font-weight: 500; + font-size: 17px; + line-height: 20px; + color: #191c1a; + display: flex; + align-items: center; +} + +.DialogText { + text-align: center; +} diff --git a/src/containers/WaGroups/WaPolls/WaPollsList/WaPollsList.tsx b/src/containers/WaGroups/WaPolls/WaPollsList/WaPollsList.tsx new file mode 100644 index 0000000000..5f09091f70 --- /dev/null +++ b/src/containers/WaGroups/WaPolls/WaPollsList/WaPollsList.tsx @@ -0,0 +1,128 @@ +import { useTranslation } from 'react-i18next'; +import CollectionIcon from 'assets/images/icons/Collection/Dark.svg?react'; +import DeleteIcon from 'assets/images/icons/Delete/Red.svg?react'; +import DuplicateIcon from 'assets/images/icons/Duplicate.svg?react'; +import { FILTER_COLLECTIONS, GET_COLLECTIONS_COUNT } from 'graphql/queries/Collection'; +import { DELETE_COLLECTION } from 'graphql/mutations/Collection'; +const queries = { + countQuery: GET_COLLECTIONS_COUNT, + filterItemsQuery: FILTER_COLLECTIONS, + deleteItemQuery: DELETE_COLLECTION, +}; + +import styles from './WaPollsList.module.css'; +import { List } from 'containers/List/List'; +import { useState } from 'react'; +import { useNavigate } from 'react-router'; +import { pollsInfo } from 'common/HelpData'; +import { DialogBox } from 'components/UI/DialogBox/DialogBox'; + +const getLabel = (label: string) =>
{label}
; + +const getContent = (content: string, id: number) => { + const content1 = + 'Which of our communication channels do you find most effective for staying updated on our activities?'; + const content2 = + 'How frequently do you participate in our activities or events, and what influences your level of involvement? Which day works best for our upcoming community workshops?'; + if (!content) { + content = id % 2 === 0 ? content1 : content2; + } + + return
{content.length < 100 ? content : `${content.slice(0, 100)}...`}
; +}; +export const WaPollsList = () => { + const [deleteItemId, setDeleteItemId] = useState(null); + + const { t } = useTranslation(); + const navigate = useNavigate(); + + const columnNames = [{ name: 'label', label: 'Title' }, { label: 'Content' }, { label: t('Actions') }]; + const title = t('Group polls'); + const collectionIcon = ; + const dialogMessage = t("You won't be able to use this collection again."); + const columnStyles = [styles.Label, styles.Content, styles.Actions]; + + const getColumns = ({ label, content, id }: any) => { + return { + label: getLabel(label), + content: getContent(content, id), + }; + }; + + const columnAttributes = { + columns: getColumns, + columnStyles, + }; + + const handleCopy = (id: any) => { + navigate(`/group/polls/${id}/edit`, { state: 'copy' }); + }; + + const handleDelete = () => { + console.log('deleteItemId', deleteItemId); + }; + + const getRestrictedAction = () => { + const action: any = { edit: false, delete: false }; + return action; + }; + + const additionalAction = () => [ + { + label: t('Copy'), + icon: , + parameter: 'id', + insideMore: false, + dialog: handleCopy, + }, + { + label: t('Delete'), + icon: , + parameter: 'label', + dialog: (id: any) => setDeleteItemId(id), + insideMore: false, + }, + ]; + + const deletedialog = ( + setDeleteItemId(null)} + alignButtons="center" + colorOk={'warning'} + buttonOk={'Delete'} + > +

+ This action is permanent and cannot be undone. Deleting this poll will remove all associated responses and data + from the platform. +

+
+ ); + + return ( + <> + + {deleteItemId && deletedialog} + + ); +}; + +export default WaPollsList; diff --git a/src/i18n/en/en.json b/src/i18n/en/en.json index 99e5e250c1..e2fb3db4fa 100644 --- a/src/i18n/en/en.json +++ b/src/i18n/en/en.json @@ -532,5 +532,6 @@ "First name is required.": "First Name is required.", "Last name is required.": "Last name is required.", "Failed": "Failed", - "Members": "Members" + "Members": "Members", + "Group polls": "Group polls" } diff --git a/src/routes/AuthenticatedRoute/AuthenticatedRoute.tsx b/src/routes/AuthenticatedRoute/AuthenticatedRoute.tsx index d5bf980390..96e3252dc7 100644 --- a/src/routes/AuthenticatedRoute/AuthenticatedRoute.tsx +++ b/src/routes/AuthenticatedRoute/AuthenticatedRoute.tsx @@ -62,6 +62,8 @@ const RoleList = lazy(() => import('containers/Role/RoleList/RoleList')); const Role = lazy(() => import('containers/Role/Role')); const KnowledgeBase = lazy(() => import('containers/KnowledgeBase/KnowledgeBase')); const Assistant = lazy(() => import('containers/Assistants/Assistants')); +const WaPollsCreate = lazy(() => import('containers/WaGroups/WaPolls/WaPolls')); +const WaPollsList = lazy(() => import('containers/WaGroups/WaPolls/WaPollsList/WaPollsList')); const routeStaff = ( @@ -148,6 +150,10 @@ const routeAdmin = ( } /> } /> + } /> + } /> + } /> + } /> );