From 271027d74018da9f25d272cd0d9da09700452a46 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Mon, 29 Jul 2024 12:04:39 +0530 Subject: [PATCH 001/174] wip code --- template/src/atoms/LinkButton.tsx | 7 +- template/src/components/Navbar.tsx | 19 ++ template/src/components/RTMConfigure.tsx | 16 + .../components/polling/components/Poll.tsx | 35 ++ template/src/components/polling/context.tsx | 280 ++++++++++++++++ .../components/polling/modal/BaseModal.tsx | 143 ++++++++ .../polling/modal/CreatePollModal.tsx | 317 ++++++++++++++++++ .../polling/modal/PollPreviewModal.tsx | 120 +++++++ .../polling/modal/SelectNewPollTypeModal.tsx | 112 +++++++ .../polling/modal/SharePollModal.tsx | 197 +++++++++++ 10 files changed, 1243 insertions(+), 3 deletions(-) create mode 100644 template/src/components/polling/components/Poll.tsx create mode 100644 template/src/components/polling/context.tsx create mode 100644 template/src/components/polling/modal/BaseModal.tsx create mode 100644 template/src/components/polling/modal/CreatePollModal.tsx create mode 100644 template/src/components/polling/modal/PollPreviewModal.tsx create mode 100644 template/src/components/polling/modal/SelectNewPollTypeModal.tsx create mode 100644 template/src/components/polling/modal/SharePollModal.tsx diff --git a/template/src/atoms/LinkButton.tsx b/template/src/atoms/LinkButton.tsx index b202abd22..2728aefd0 100644 --- a/template/src/atoms/LinkButton.tsx +++ b/template/src/atoms/LinkButton.tsx @@ -1,16 +1,17 @@ -import {StyleSheet, Text, TouchableOpacity} from 'react-native'; +import {StyleSheet, Text, TouchableOpacity, TextStyle} from 'react-native'; import React from 'react'; import ThemeConfig from '../theme'; interface LinkButtonProps { onPress: () => void; text: string; + textStyle?: TextStyle; } -const LinkButton = ({onPress, text}: LinkButtonProps) => { +const LinkButton = ({onPress, text, textStyle = {}}: LinkButtonProps) => { return ( - {text} + {text} ); }; diff --git a/template/src/components/Navbar.tsx b/template/src/components/Navbar.tsx index 03a737e39..8687d8a2c 100644 --- a/template/src/components/Navbar.tsx +++ b/template/src/components/Navbar.tsx @@ -76,6 +76,7 @@ import { toolbarItemPeopleText, videoRoomRecordingText, } from '../language/default-labels/videoCallScreenLabels'; +import RTMEngine from '../rtm/RTMEngine'; export const ParticipantsCountView = ({ isMobileView = false, @@ -137,6 +138,7 @@ export const ParticipantsIconButton = (props: ParticipantsIconButtonProps) => { } = useRoomInfo(); const isPendingWaitingRoomApproval = isHost && waitingRoomUids.length > 0; + const {engine} = useContext(ChatContext); const onPress = () => { isPanelActive @@ -192,6 +194,23 @@ export const ParticipantsIconButton = (props: ParticipantsIconButtonProps) => { + {/* + { + console.log('supriya pressed'); + const channelId = RTMEngine.getInstance().channelUid; + engine.addOrUpdateChannelAttributes( + channelId, + [{key: 'poll', value: 'text'}], + {enableNotificationToChannelMembers: true}, + ); + }}> + Dummy Tesyting + + */} {isPendingWaitingRoomApproval || ($config.EVENT_MODE && $config.RAISE_HAND && diff --git a/template/src/components/RTMConfigure.tsx b/template/src/components/RTMConfigure.tsx index f663ff454..4bd1a8cef 100644 --- a/template/src/components/RTMConfigure.tsx +++ b/template/src/components/RTMConfigure.tsx @@ -233,12 +233,23 @@ const RtmConfigure = (props: any) => { } timerValueRef.current = 5; await getMembers(); + const channelAttr = await engine.current.getChannelAttributes( + rtcProps.channel, + ); + const channelAttrByKeys = await engine.current.getChannelAttributesByKeys( + rtcProps.channel, + ['poll'], + ); + console.log('supriya getChannelAttributes', channelAttr); + console.log('supriya getChannelAttributesByKeys', channelAttrByKeys); logger.log(LogSource.AgoraSDK, 'Log', 'RTM getMembers done'); } catch (error) { + console.log('supriya error: ', error); logger.error( LogSource.AgoraSDK, 'Log', 'RTM joinChannel failed..Trying again', + error, ); setTimeout(async () => { timerValueRef.current = timerValueRef.current + timerValueRef.current; @@ -580,6 +591,11 @@ const RtmConfigure = (props: any) => { } } }); + + engine.current.on('channelAttributesUpdated', (data: any) => { + console.log('supriya channel attributes receivwd', data); + }); + await doLoginAndSetupRTM(); }; diff --git a/template/src/components/polling/components/Poll.tsx b/template/src/components/polling/components/Poll.tsx new file mode 100644 index 000000000..c1b15d91b --- /dev/null +++ b/template/src/components/polling/components/Poll.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import {PollProvider, usePoll} from '../context'; +import SelectNewPollTypeModal from '../modal/SelectNewPollTypeModal'; +import CreatePollModal from '../modal/CreatePollModal'; +import PollPreviewModal from '../modal/PollPreviewModal'; +import SharePollModal from '../modal/SharePollModal'; + +function Poll() { + return ( + + + + ); +} + +function PollModals() { + const {state} = usePoll(); + const {nextUserActivity} = state; + + const openSelectNewPollTypeModal = nextUserActivity === 'SELECT_NEW_POLL'; + const openCreatePollModal = nextUserActivity === 'CREATE_POLL'; + const openPreviewModal = nextUserActivity === 'PREVIEW_POLL'; + return ( + <> + {openSelectNewPollTypeModal && ( + + )} + {openCreatePollModal && } + {openPreviewModal && } + {/* */} + + ); +} + +export default Poll; diff --git a/template/src/components/polling/context.tsx b/template/src/components/polling/context.tsx new file mode 100644 index 000000000..f9d36472b --- /dev/null +++ b/template/src/components/polling/context.tsx @@ -0,0 +1,280 @@ +import React, {createContext, Dispatch} from 'react'; +import SelectNewPollTypeModal from './modal/SelectNewPollTypeModal'; +import CreatePollModal from './modal/CreatePollModal'; +import PollPreviewModal from './modal/PollPreviewModal'; +import SharePollModal from './modal/SharePollModal'; + +enum PollKind { + OPEN_ENDED = 'OPEN_ENDED', + MCQ = 'MCQ', + YES_NO = 'YES_NO', +} + +type PollAccess = 'public' | 'private'; +type PollStatus = 'active' | 'finished' | 'later'; + +interface Poll { + type: PollKind; + access: PollAccess; + status: PollStatus; + title: string; + question: string; + options: Array<{ + text: string; + value: string; + votes: []; + }> | null; + multiple: boolean; + share: boolean; + duration: boolean; + timer: number; + createdBy: number; +} + +const initializeNewPoll = (kind: PollKind): Poll => { + if (kind === PollKind.OPEN_ENDED) { + return { + type: PollKind.OPEN_ENDED, + access: 'public', + status: 'later', + title: 'Open Ended Poll', + question: '', + options: null, + multiple: false, + share: false, + duration: false, + timer: -1, + createdBy: -1, + }; + } + if (kind === PollKind.MCQ) { + return { + type: PollKind.MCQ, + access: 'public', + status: 'later', + title: 'Multiple Choice Question', + question: '', + options: [ + { + text: '', + value: '', + votes: [], + }, + { + text: '', + value: '', + votes: [], + }, + { + text: '', + value: '', + votes: [], + }, + ], + multiple: true, + share: false, + duration: false, + timer: -1, + createdBy: -1, + }; + } + if (kind === PollKind.YES_NO) { + return { + type: PollKind.YES_NO, + access: 'public', + status: 'later', + title: 'Yes/No', + question: '', + options: [ + { + text: 'YES', + value: 'yes', + votes: [], + }, + { + text: 'No', + value: 'no', + votes: [], + }, + ], + multiple: false, + share: false, + duration: false, + timer: -1, + createdBy: -1, + }; + } +}; + +// To DO + +// An interface for our actions +// interface RecordingsActions { +// type: keyof typeof RecordingsActionKind; +// payload: T; +// } + +// const initialPollsState: PollState[] = []; + +type PollUserActivity = + | 'SELECT_NEW_POLL' + | 'CREATE_POLL' + | 'PREVIEW_POLL' + | 'SHARE_POLL'; + +interface PollObject { + [key: string]: Poll; +} +interface PollState { + form: Poll; + nextUserActivity: PollUserActivity; + poll: PollObject; +} +enum PollActionKind { + SELECT_NEW_POLL = 'SELECT_NEW_POLL', + EDIT_POLL_FORM_FIELD = 'EDIT_POLL_FORM_FIELD', + EDIT_POLL_FORM_OPTION = 'EDIT_POLL_FORM_OPTION', + ADD_POLL_FORM_OPTION = 'ADD_POLL_FORM_OPTION', + DELETE_POLL_FORM_OPTION = 'DELETE_POLL_FORM_OPTION', + PREVIEW_POLL_FORM = 'PREVIEW_POLL_FORM', + EDIT_POLL_FORM = 'EDIT_POLL_FORM', + SAVE_POLL_FORM = 'SAVE_POLL_FORM', + LAUNCH_POLL_FORM = 'LAUNCH_POLL_FORM', +} +interface PollAction { + type: PollActionKind; + payload: any; +} +function pollReducer(state: PollState, action: PollAction): PollState { + switch (action.type) { + case PollActionKind.SELECT_NEW_POLL: { + return { + ...state, + nextUserActivity: 'CREATE_POLL', + form: initializeNewPoll(action.payload), + }; + } + case PollActionKind.EDIT_POLL_FORM_FIELD: { + return { + ...state, + form: { + ...state.form, + [action.payload.field]: action.payload.value, + }, + }; + } + case PollActionKind.ADD_POLL_FORM_OPTION: { + return { + ...state, + form: { + ...state.form, + options: [ + ...state.form.options, + { + text: '', + value: '', + votes: [], + }, + ], + }, + }; + } + case PollActionKind.EDIT_POLL_FORM_OPTION: { + return { + ...state, + form: { + ...state.form, + options: state.form.options.map((option, i) => { + if (i === action.payload.key) { + const value = action.payload.value + .replace(/\s+/g, '-') + .toLowerCase(); + return { + ...option, + text: action.payload.value, + value, + votes: [], + }; + } + return option; + }), + }, + }; + } + case PollActionKind.DELETE_POLL_FORM_OPTION: { + return { + ...state, + form: { + ...state.form, + options: state.form.options.filter( + (option, i) => i !== action.payload.key, + ), + }, + }; + } + case PollActionKind.PREVIEW_POLL_FORM: { + return { + ...state, + nextUserActivity: 'PREVIEW_POLL', + }; + } + case PollActionKind.EDIT_POLL_FORM: { + return { + ...state, + nextUserActivity: 'CREATE_POLL', + }; + } + case PollActionKind.SAVE_POLL_FORM: { + return { + ...state, + form: { + ...state.form, + status: 'later', + }, + nextUserActivity: null, + }; + } + case PollActionKind.LAUNCH_POLL_FORM: { + return { + ...state, + form: { + ...state.form, + status: 'active', + }, + nextUserActivity: null, + }; + } + default: { + return state; + } + } +} + +interface PollContextValue { + state: PollState; + dispatch: any; +} + +const PollContext = createContext(null); +PollContext.displayName = 'PollContext'; + +function PollProvider({children}: {children: React.ReactNode}) { + const [state, dispatch] = React.useReducer(pollReducer, { + form: null, + nextUserActivity: 'SELECT_NEW_POLL', + poll: {}, + }); + + const value = {state, dispatch}; + return {children}; +} + +function usePoll() { + const context = React.useContext(PollContext); + if (!context) { + throw new Error('usePoll must be used within a PollProvider'); + } + return context; +} + +export {PollProvider, usePoll, PollKind, PollActionKind}; diff --git a/template/src/components/polling/modal/BaseModal.tsx b/template/src/components/polling/modal/BaseModal.tsx new file mode 100644 index 000000000..84c74e1bb --- /dev/null +++ b/template/src/components/polling/modal/BaseModal.tsx @@ -0,0 +1,143 @@ +import {Modal, View, StyleSheet, Text} from 'react-native'; +import React, {ReactNode} from 'react'; +import ThemeConfig from '../../../theme'; +import hexadecimalTransparency from '../../../utils/hexadecimalTransparency'; +import IconButton from '../../../atoms/IconButton'; +import {isMobileUA} from '../../../utils/common'; + +interface TitleProps { + title?: string; + close?: boolean; + children?: ReactNode | ReactNode[]; +} + +function BaseModalTitle({title, close = false, children}: TitleProps) { + return ( + + {title && ( + + {title} + + )} + {children} + {close && ( + + { + //set close + }} + /> + + )} + + ); +} + +interface ContentProps { + children: ReactNode; +} + +function BaseModalContent({children}: ContentProps) { + return {children}; +} + +interface ActionProps { + children: ReactNode; +} +function BaseModalActions({children}: ActionProps) { + return {children}; +} + +type BaseModalProps = { + visible?: boolean; + children: ReactNode; +}; + +const BaseModal = ({children, visible = false}: BaseModalProps) => { + return ( + + + + {children} + + + + ); +}; + +export {BaseModal, BaseModalTitle, BaseModalContent, BaseModalActions}; + +const style = StyleSheet.create({ + baseModalBackDrop: { + flex: 1, + position: 'relative', + justifyContent: 'center', + alignItems: 'center', + padding: 20, + backgroundColor: + $config.HARD_CODED_BLACK_COLOR + hexadecimalTransparency['60%'], + }, + baseModal: { + backgroundColor: $config.CARD_LAYER_1_COLOR, + borderWidth: 1, + borderColor: $config.CARD_LAYER_3_COLOR, + borderRadius: ThemeConfig.BorderRadius.large, + shadowColor: $config.HARD_CODED_BLACK_COLOR, + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 5, + maxWidth: 800, + maxHeight: 800, + overflow: 'scroll', + }, + scrollView: { + flex: 1, + }, + header: { + display: 'flex', + paddingHorizontal: 32, + paddingVertical: 20, + alignItems: 'center', + gap: 20, + minHeight: 72, + justifyContent: 'space-between', + flexDirection: 'row', + borderBottomWidth: 1, + borderColor: $config.CARD_LAYER_3_COLOR, + }, + title: { + color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, + fontSize: ThemeConfig.FontSize.xLarge, + fontFamily: ThemeConfig.FontFamily.sansPro, + lineHeight: 32, + fontWeight: '600', + letterSpacing: -0.48, + }, + content: { + padding: 32, + gap: 20, + display: 'flex', + flexDirection: 'column', + // minWidth: 620, + }, + actions: { + height: 72, + paddingHorizontal: 32, + paddingVertical: 12, + display: 'flex', + gap: 16, + backgroundColor: $config.CARD_LAYER_2_COLOR, + }, +}); diff --git a/template/src/components/polling/modal/CreatePollModal.tsx b/template/src/components/polling/modal/CreatePollModal.tsx new file mode 100644 index 000000000..4a11a7ad8 --- /dev/null +++ b/template/src/components/polling/modal/CreatePollModal.tsx @@ -0,0 +1,317 @@ +import {Text, View, StyleSheet, TextInput} from 'react-native'; +import React, {useState} from 'react'; +import { + BaseModal, + BaseModalTitle, + BaseModalContent, + BaseModalActions, +} from './BaseModal'; +import ThemeConfig from '../../../theme'; +import {PollKind, usePoll} from '../context'; +import LinkButton from '../../../atoms/LinkButton'; +import Checkbox from '../../../atoms/Checkbox'; +import IconButton from '../../../atoms/IconButton'; +import TertiaryButton from '../../../atoms/TertiaryButton'; +import PrimaryButton from '../../../atoms/PrimaryButton'; + +function FormTitle({title}: {title: string}) { + return ( + + {title} + + ); +} +export default function CreatePollModal({visible}) { + const {state, dispatch} = usePoll(); + const {form} = state; + console.log('supriya form', state); + + return ( + + + + {/* Question section */} + + + + + { + if (text.trim() === '') { + return; + } + dispatch({ + type: 'EDIT_POLL_FORM_FIELD', + payload: { + field: 'question', + value: text, + }, + }); + }} + placeholder="Enter poll question here..." + placeholderTextColor={ + $config.FONT_COLOR + ThemeConfig.EmphasisPlus.low + } + /> + + + {/* Options section */} + + + + {form.type === PollKind.MCQ ? ( + <> + {form.options.map((option, index) => ( + + {index + 1} + { + dispatch({ + type: 'EDIT_POLL_FORM_OPTION', + payload: { + value: text, + key: index, + }, + }); + }} + placeholder="Add text here..." + placeholderTextColor={ + $config.FONT_COLOR + ThemeConfig.EmphasisPlus.low + } + /> + + { + dispatch({ + type: 'DELETE_POLL_FORM_OPTION', + payload: { + value: '', + key: index, + }, + }); + }} + /> + + + ))} + + { + dispatch({ + type: 'ADD_POLL_FORM_OPTION', + }); + }} + /> + + + ) : ( + <> + + Yes + + + No + + + )} + + + {/* Sections templete */} + + + + + dispatch({ + type: 'EDIT_POLL_FORM', + payload: { + field: 'multiple', + value: !form.multiple, + }, + }) + } + /> + + + + dispatch({ + type: 'EDIT_POLL_FORM', + payload: { + field: 'share', + value: !form.share, + }, + }) + } + /> + + + + dispatch({ + type: 'EDIT_POLL_FORM', + payload: { + field: 'duration', + value: !form.duration, + }, + }) + } + /> + + + + + + + + { + dispatch({ + type: 'PREVIEW_POLL_FORM', + }); + }} + text="Preview" + /> + + + + ); +} + +export const style = StyleSheet.create({ + createPollBox: { + width: 620, + display: 'flex', + flexDirection: 'column', + gap: 20, + }, + pFormSection: { + gap: 12, + }, + pFormAddOptionLinkSection: { + marginTop: -8, + paddingVertical: 8, + paddingHorizontal: 16, + alignItems: 'flex-start', + }, + pFormTitle: { + color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, + fontSize: ThemeConfig.FontSize.small, + fontFamily: ThemeConfig.FontFamily.sansPro, + lineHeight: 16, + fontWeight: '600', + }, + pFormTextarea: { + color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, + fontSize: ThemeConfig.FontSize.small, + fontFamily: ThemeConfig.FontFamily.sansPro, + lineHeight: 16, + fontWeight: '400', + borderRadius: 8, + borderWidth: 1, + borderColor: $config.INPUT_FIELD_BORDER_COLOR, + backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, + height: 110, + outlineStyle: 'none', + padding: 20, + }, + pFormOptionText: { + color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, + fontSize: ThemeConfig.FontSize.small, + fontFamily: ThemeConfig.FontFamily.sansPro, + lineHeight: 16, + fontWeight: '400', + }, + pFormOptionPrefix: { + color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.low, + paddingRight: 4, + }, + pFormOptionLink: { + fontWeight: '400', + lineHeight: 24, + }, + pFormOptions: { + paddingVertical: 8, + gap: 8, + }, + pFormInput: { + flex: 1, + color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, + fontSize: ThemeConfig.FontSize.small, + fontFamily: ThemeConfig.FontFamily.sansPro, + lineHeight: 16, + fontWeight: '400', + outlineStyle: 'none', + backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, + borderRadius: 9, + paddingVertical: 12, + }, + pFormOptionCard: { + display: 'flex', + paddingHorizontal: 16, + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'center', + alignSelf: 'stretch', + gap: 8, + backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, + borderRadius: 9, + }, + verticalPadding: { + paddingVertical: 12, + }, + pFormCheckboxContainer: { + paddingHorizontal: 16, + paddingVertical: 8, + }, + previewActions: { + flex: 1, + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'flex-end', + }, + btnContainer: { + minWidth: 150, + height: 36, + borderRadius: 4, + }, + btnText: { + color: $config.FONT_COLOR, + fontSize: ThemeConfig.FontSize.small, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontWeight: '600', + textTransform: 'capitalize', + }, +}); diff --git a/template/src/components/polling/modal/PollPreviewModal.tsx b/template/src/components/polling/modal/PollPreviewModal.tsx new file mode 100644 index 000000000..879db4f36 --- /dev/null +++ b/template/src/components/polling/modal/PollPreviewModal.tsx @@ -0,0 +1,120 @@ +import {Text, StyleSheet, View} from 'react-native'; +import React from 'react'; +import { + BaseModal, + BaseModalTitle, + BaseModalContent, + BaseModalActions, +} from './BaseModal'; +import ThemeConfig from '../../../theme'; +import TertiaryButton from '../../../atoms/TertiaryButton'; +import {usePoll} from '../context'; + +export default function PollPreviewModal({visible}) { + const {state, dispatch} = usePoll(); + const {form} = state; + return ( + + + + + {form.duration && ( + {form.timer} + )} + {form.question} + + {form.options.map(option => ( + + {option.text} + + ))} + + + + + + + { + dispatch({ + type: 'EDIT_POLL_FORM', + }); + }} + text="Edit" + /> + + + { + dispatch({ + type: 'SAVE_POLL_FORM', + }); + }} + /> + + + { + dispatch({ + type: 'LAUNCH_POLL_FORM', + }); + }} + /> + + + + + ); +} + +export const style = StyleSheet.create({ + previewContainer: { + width: 550, + }, + previewTimer: { + color: $config.SEMANTIC_WARNING, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontSize: 16, + lineHeight: 20, + paddingBottom: 12, + }, + previewQuestion: { + color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, + fontSize: ThemeConfig.FontSize.medium, + fontFamily: ThemeConfig.FontFamily.sansPro, + lineHeight: 24, + fontWeight: '600', + paddingBottom: 20, + }, + previewOptionSection: { + backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, + borderRadius: 9, + paddingVertical: 8, + display: 'flex', + flexDirection: 'column', + gap: 4, + }, + previewOptionCard: { + display: 'flex', + paddingHorizontal: 16, + paddingVertical: 8, + }, + previewOptionText: { + color: $config.FONT_COLOR, + fontSize: ThemeConfig.FontSize.normal, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontWeight: '400', + lineHeight: 24, + }, + previewActions: { + flex: 1, + display: 'flex', + flexDirection: 'row', + gap: 16, + }, + btnContainer: { + flex: 1, + }, +}); diff --git a/template/src/components/polling/modal/SelectNewPollTypeModal.tsx b/template/src/components/polling/modal/SelectNewPollTypeModal.tsx new file mode 100644 index 000000000..7cc32fed5 --- /dev/null +++ b/template/src/components/polling/modal/SelectNewPollTypeModal.tsx @@ -0,0 +1,112 @@ +import {Text, StyleSheet, View, TouchableOpacity} from 'react-native'; +import React from 'react'; +import {BaseModal, BaseModalTitle, BaseModalContent} from './BaseModal'; +import ThemeConfig from '../../../theme'; +import {PollActionKind, PollKind, usePoll} from '../context'; + +interface newPollType { + key: PollKind; + image: null; + title: string; + description: string; +} + +const newPollTypeConfig: newPollType[] = [ + { + key: PollKind.MCQ, + image: null, + title: 'Multiple Choice', + description: 'Quick stand-alone question with different options', + }, + { + key: PollKind.OPEN_ENDED, + image: null, + title: 'Open Ended', + description: 'Question with a descriptive, open text response', + }, + { + key: PollKind.YES_NO, + image: null, + title: 'Yes / No', + description: 'A simple question with a binary Yes or No response', + }, +]; + +export default function SelectNewPollTypeModal({visible}) { + const {state, dispatch} = usePoll(); + + // const open = state.nextUserActivity === 'SELECT_NEW_POLL'; + + return ( + + + + + {newPollTypeConfig.map((item: newPollType) => ( + { + dispatch({ + type: PollActionKind.SELECT_NEW_POLL, + payload: item.key, + }); + }}> + + + + {item.title} + {item.description} + + + + ))} + + + + ); +} + +export const style = StyleSheet.create({ + section: { + display: 'flex', + flexDirection: 'row', + gap: 20, + }, + card: { + flexDirection: 'column', + gap: 12, + width: 140, + outlineStyle: 'none', + }, + cardImage: { + height: 90, + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + gap: 8, + borderRadius: 8, + borderWidth: 1, + borderColor: $config.CARD_LAYER_3_COLOR, + backgroundColor: $config.CARD_LAYER_4_COLOR, + }, + cardContent: { + display: 'flex', + flexDirection: 'column', + gap: 4, + }, + cardContentTitle: { + color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, + fontSize: ThemeConfig.FontSize.small, + fontFamily: ThemeConfig.FontFamily.sansPro, + lineHeight: 16, + fontWeight: '400', + }, + cardContentDesc: { + color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.low, + fontSize: ThemeConfig.FontSize.tiny, + fontFamily: ThemeConfig.FontFamily.sansPro, + lineHeight: 16, + fontWeight: '400', + }, +}); diff --git a/template/src/components/polling/modal/SharePollModal.tsx b/template/src/components/polling/modal/SharePollModal.tsx new file mode 100644 index 000000000..f962c22b4 --- /dev/null +++ b/template/src/components/polling/modal/SharePollModal.tsx @@ -0,0 +1,197 @@ +import {Text, StyleSheet, View} from 'react-native'; +import React from 'react'; +import {BaseModal, BaseModalTitle, BaseModalContent} from './BaseModal'; +import ThemeConfig from '../../../theme'; +import UserAvatar from '../../../atoms/UserAvatar'; + +export default function SharePollModal() { + return ( + + + + + + + + Elanor Pena + 2:30 pm MCQ + + + + + + How was today's session? + + + + Great + Your Response + + 75% (15) + + + + + + + + + Okay + Your Response + + 75% (15) + + + + + + + + + Could be better + Your Response + + 75% (15) + + + + + + + + + + + ); +} + +export const style = StyleSheet.create({ + shareBox: { + width: 550, + }, + titleCard: { + display: 'flex', + flexDirection: 'row', + gap: 12, + }, + title: { + display: 'flex', + flexDirection: 'column', + gap: 2, + }, + titleAvatar: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + titleAvatarContainer: { + width: 36, + height: 36, + borderRadius: 18, + backgroundColor: $config.VIDEO_AUDIO_TILE_AVATAR_COLOR, + }, + titleAvatarContainerText: { + fontSize: ThemeConfig.FontSize.small, + lineHeight: 16, + fontWeight: '600', + color: $config.VIDEO_AUDIO_TILE_COLOR, + }, + titleText: { + color: $config.FONT_COLOR, + fontSize: ThemeConfig.FontSize.normal, + fontWeight: '700', + lineHeight: 20, + }, + titleSubtext: { + color: $config.FONT_COLOR, + fontSize: ThemeConfig.FontSize.tiny, + fontWeight: '400', + lineHeight: 16, + }, + questionText: { + color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, + fontSize: ThemeConfig.FontSize.medium, + fontFamily: ThemeConfig.FontFamily.sansPro, + lineHeight: 24, + fontWeight: '600', + }, + responseSection: { + backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, + borderRadius: 9, + paddingTop: 8, + paddingHorizontal: 12, + paddingBottom: 32, + display: 'flex', + flexDirection: 'column', + gap: 4, + marginVertical: 20, + }, + responseCard: { + display: 'flex', + flexDirection: 'column', + gap: 4, + }, + responseCardBody: { + display: 'flex', + flexDirection: 'row', + paddingHorizontal: 16, + paddingVertical: 8, + alignItems: 'center', + }, + responseText: { + color: $config.FONT_COLOR, + fontSize: ThemeConfig.FontSize.normal, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontWeight: '400', + lineHeight: 24, + }, + yourResponseText: { + color: $config.SEMANTIC_SUCCESS, + fontSize: ThemeConfig.FontSize.tiny, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontWeight: '600', + lineHeight: 12, + paddingLeft: 16, + }, + pushRight: { + marginLeft: 'auto', + }, + progressBar: { + height: 4, + borderRadius: 8, + backgroundColor: $config.CARD_LAYER_3_COLOR, + width: '100%', + }, + progressBarFill: { + borderRadius: 8, + backgroundColor: $config.PRIMARY_ACTION_BRAND_COLOR, + }, +}); From de2f06d9c27a8bfb0c3de58476b66f328bc4393d Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Mon, 29 Jul 2024 23:58:35 +0530 Subject: [PATCH 002/174] add poll form states and reducer --- .../components/polling/components/Poll.tsx | 45 +-- .../components/polling/context/poll-form.tsx | 322 ++++++++++++++++++ .../polling/modal/CreatePollModal.tsx | 29 +- .../polling/modal/PollPreviewModal.tsx | 10 +- .../polling/modal/SelectNewPollTypeModal.tsx | 11 +- 5 files changed, 368 insertions(+), 49 deletions(-) create mode 100644 template/src/components/polling/context/poll-form.tsx diff --git a/template/src/components/polling/components/Poll.tsx b/template/src/components/polling/components/Poll.tsx index c1b15d91b..60b6f1c3b 100644 --- a/template/src/components/polling/components/Poll.tsx +++ b/template/src/components/polling/components/Poll.tsx @@ -1,35 +1,38 @@ import React from 'react'; -import {PollProvider, usePoll} from '../context'; +import {PollProvider} from '../context'; +import {PollFormProvider, usePollForm} from '../context/poll-form'; import SelectNewPollTypeModal from '../modal/SelectNewPollTypeModal'; import CreatePollModal from '../modal/CreatePollModal'; import PollPreviewModal from '../modal/PollPreviewModal'; -import SharePollModal from '../modal/SharePollModal'; function Poll() { return ( - + + + ); } -function PollModals() { - const {state} = usePoll(); - const {nextUserActivity} = state; +export default Poll; - const openSelectNewPollTypeModal = nextUserActivity === 'SELECT_NEW_POLL'; - const openCreatePollModal = nextUserActivity === 'CREATE_POLL'; - const openPreviewModal = nextUserActivity === 'PREVIEW_POLL'; - return ( - <> - {openSelectNewPollTypeModal && ( - - )} - {openCreatePollModal && } - {openPreviewModal && } - {/* */} - - ); -} +function PollForms() { + const {state} = usePollForm(); + const {currentStep} = state; -export default Poll; + function renderSwitch() { + switch (currentStep) { + case 'SELECT_POLL': + return ; + case 'CREATE_POLL': + return ; + case 'PREVIEW_POLL': + return ; + default: + return <>; + } + } + + return <>{renderSwitch()}; +} diff --git a/template/src/components/polling/context/poll-form.tsx b/template/src/components/polling/context/poll-form.tsx new file mode 100644 index 000000000..51adf331e --- /dev/null +++ b/template/src/components/polling/context/poll-form.tsx @@ -0,0 +1,322 @@ +import React, {Dispatch, createContext, useContext, useReducer} from 'react'; + +enum PollAccess { + PUBLIC = 'PUBLIC', +} + +enum PollStatus { + ACTIVE = 'PUBLIC', + FINISHED = 'FINISHED', + LATER = 'LATER', +} + +enum PollKind { + OPEN_ENDED = 'OPEN_ENDED', + MCQ = 'MCQ', + YES_NO = 'YES_NO', +} + +enum PollActionKind { + START_POLL = 'START_POLL', + SELECT_POLL = 'SELECT_POLL', + UPDATE_FORM_FIELD = 'UPDATE_FORM_FIELD', + UPDATE_FORM_OPTION = 'UPDATE_FORM_OPTION', + ADD_FORM_OPTION = 'ADD_FORM_OPTION', + DELETE_FORM_OPTION = 'DELETE_FORM_OPTION', + PREVIEW_FORM = 'PREVIEW_FORM', + UPDATE_FORM = 'UPDATE_FORM', + SAVE_FORM = 'SAVE_FORM', + LAUNCH_FORM = 'LAUNCH_FORM', +} + +type PollFormAction = + | { + type: PollActionKind.START_POLL; + } + | { + type: PollActionKind.SELECT_POLL; + payload: { + pollType: PollKind; + }; + } + | { + type: PollActionKind.UPDATE_FORM_FIELD; + payload: { + field: string; + value: string | boolean; + }; + } + | { + type: PollActionKind.ADD_FORM_OPTION; + } + | { + type: PollActionKind.UPDATE_FORM_OPTION; + payload: { + index: number; + value: string; + }; + } + | { + type: PollActionKind.DELETE_FORM_OPTION; + payload: { + index: number; + }; + } + | { + type: PollActionKind.PREVIEW_FORM; + } + | { + type: PollActionKind.SAVE_FORM; + } + | { + type: PollActionKind.UPDATE_FORM; + } + | { + type: PollActionKind.LAUNCH_FORM; + }; + +interface Poll { + type: PollKind; + access: PollAccess; + status: PollStatus; + title: string; + question: string; + options: Array<{ + text: string; + value: string; + votes: []; + }> | null; + multiple: boolean; + share: boolean; + duration: boolean; + timer: number; + createdBy: number; +} + +interface PollFormState { + form: Poll; + currentStep: 'START_POLL' | 'SELECT_POLL' | 'CREATE_POLL' | 'PREVIEW_POLL'; +} + +const initPollForm = (kind: PollKind): Poll => { + if (kind === PollKind.OPEN_ENDED) { + return { + type: PollKind.OPEN_ENDED, + access: PollAccess.PUBLIC, + status: PollStatus.LATER, + title: 'Open Ended Poll', + question: '', + options: null, + multiple: false, + share: false, + duration: false, + timer: -1, + createdBy: -1, + }; + } + if (kind === PollKind.MCQ) { + return { + type: PollKind.MCQ, + access: PollAccess.PUBLIC, + status: PollStatus.LATER, + title: 'Multiple Choice Question', + question: '', + options: [ + { + text: '', + value: '', + votes: [], + }, + { + text: '', + value: '', + votes: [], + }, + { + text: '', + value: '', + votes: [], + }, + ], + multiple: true, + share: false, + duration: false, + timer: -1, + createdBy: -1, + }; + } + if (kind === PollKind.YES_NO) { + return { + type: PollKind.YES_NO, + access: PollAccess.PUBLIC, + status: PollStatus.LATER, + title: 'Yes/No', + question: '', + options: [ + { + text: 'YES', + value: 'yes', + votes: [], + }, + { + text: 'No', + value: 'no', + votes: [], + }, + ], + multiple: false, + share: false, + duration: false, + timer: -1, + createdBy: -1, + }; + } +}; + +function pollFormReducer( + state: PollFormState, + action: PollFormAction, +): PollFormState { + switch (action.type) { + case PollActionKind.START_POLL: { + return { + ...state, + form: null, + currentStep: 'SELECT_POLL', + }; + } + case PollActionKind.SELECT_POLL: { + return { + ...state, + currentStep: 'CREATE_POLL', + form: initPollForm(action.payload.pollType), + }; + } + case PollActionKind.UPDATE_FORM_FIELD: { + return { + ...state, + form: { + ...state.form, + [action.payload.field]: action.payload.value, + }, + }; + } + case PollActionKind.ADD_FORM_OPTION: { + return { + ...state, + form: { + ...state.form, + options: [ + ...state.form.options, + { + text: '', + value: '', + votes: [], + }, + ], + }, + }; + } + case PollActionKind.UPDATE_FORM_OPTION: { + return { + ...state, + form: { + ...state.form, + options: state.form.options.map((option, i) => { + if (i === action.payload.index) { + const value = action.payload.value + .replace(/\s+/g, '-') + .toLowerCase(); + return { + ...option, + text: action.payload.value, + value, + votes: [], + }; + } + return option; + }), + }, + }; + } + case PollActionKind.DELETE_FORM_OPTION: { + return { + ...state, + form: { + ...state.form, + options: state.form.options.filter( + (option, i) => i !== action.payload.index, + ), + }, + }; + } + case PollActionKind.PREVIEW_FORM: { + return { + ...state, + currentStep: 'PREVIEW_POLL', + }; + } + case PollActionKind.UPDATE_FORM: { + return { + ...state, + currentStep: 'CREATE_POLL', + }; + } + case PollActionKind.SAVE_FORM: { + return { + ...state, + form: { + ...state.form, + status: PollStatus.LATER, + }, + currentStep: null, + }; + } + case PollActionKind.LAUNCH_FORM: { + return { + ...state, + form: { + ...state.form, + status: PollStatus.ACTIVE, + }, + currentStep: null, + }; + } + default: { + return state; + } + } +} + +interface PollFormContextValue { + state: PollFormState; + dispatch: Dispatch; +} + +const PollFormContext = createContext(null); +PollFormContext.displayName = 'PollFormContext'; + +function PollFormProvider({children}: {children?: React.ReactNode}) { + const [state, dispatch] = useReducer(pollFormReducer, { + form: null, + currentStep: null, + }); + + const value = {state, dispatch}; + + return ( + + {children} + + ); +} + +function usePollForm() { + const context = useContext(PollFormContext); + if (!context) { + throw new Error('usePollForm must be used within PollFormProvider '); + } + return context; +} + +export {PollFormProvider, usePollForm, PollActionKind, PollKind}; diff --git a/template/src/components/polling/modal/CreatePollModal.tsx b/template/src/components/polling/modal/CreatePollModal.tsx index 4a11a7ad8..610ab4bf8 100644 --- a/template/src/components/polling/modal/CreatePollModal.tsx +++ b/template/src/components/polling/modal/CreatePollModal.tsx @@ -1,5 +1,5 @@ import {Text, View, StyleSheet, TextInput} from 'react-native'; -import React, {useState} from 'react'; +import React from 'react'; import { BaseModal, BaseModalTitle, @@ -7,12 +7,11 @@ import { BaseModalActions, } from './BaseModal'; import ThemeConfig from '../../../theme'; -import {PollKind, usePoll} from '../context'; import LinkButton from '../../../atoms/LinkButton'; import Checkbox from '../../../atoms/Checkbox'; import IconButton from '../../../atoms/IconButton'; -import TertiaryButton from '../../../atoms/TertiaryButton'; import PrimaryButton from '../../../atoms/PrimaryButton'; +import {PollActionKind, PollKind, usePollForm} from '../context/poll-form'; function FormTitle({title}: {title: string}) { return ( @@ -22,9 +21,8 @@ function FormTitle({title}: {title: string}) { ); } export default function CreatePollModal({visible}) { - const {state, dispatch} = usePoll(); + const {state, dispatch} = usePollForm(); const {form} = state; - console.log('supriya form', state); return ( @@ -46,7 +44,7 @@ export default function CreatePollModal({visible}) { return; } dispatch({ - type: 'EDIT_POLL_FORM_FIELD', + type: PollActionKind.UPDATE_FORM_FIELD, payload: { field: 'question', value: text, @@ -76,10 +74,10 @@ export default function CreatePollModal({visible}) { value={option.text} onChangeText={text => { dispatch({ - type: 'EDIT_POLL_FORM_OPTION', + type: PollActionKind.UPDATE_FORM_OPTION, payload: { value: text, - key: index, + index: index, }, }); }} @@ -101,10 +99,9 @@ export default function CreatePollModal({visible}) { }} onPress={() => { dispatch({ - type: 'DELETE_POLL_FORM_OPTION', + type: PollActionKind.DELETE_FORM_OPTION, payload: { - value: '', - key: index, + index: index, }, }); }} @@ -118,7 +115,7 @@ export default function CreatePollModal({visible}) { textStyle={style.pFormOptionLink} onPress={() => { dispatch({ - type: 'ADD_POLL_FORM_OPTION', + type: PollActionKind.ADD_FORM_OPTION, }); }} /> @@ -146,7 +143,7 @@ export default function CreatePollModal({visible}) { labelStye={style.pFormOptionText} onChange={() => dispatch({ - type: 'EDIT_POLL_FORM', + type: PollActionKind.UPDATE_FORM_FIELD, payload: { field: 'multiple', value: !form.multiple, @@ -162,7 +159,7 @@ export default function CreatePollModal({visible}) { labelStye={style.pFormOptionText} onChange={() => dispatch({ - type: 'EDIT_POLL_FORM', + type: PollActionKind.UPDATE_FORM_FIELD, payload: { field: 'share', value: !form.share, @@ -178,7 +175,7 @@ export default function CreatePollModal({visible}) { labelStye={style.pFormOptionText} onChange={() => dispatch({ - type: 'EDIT_POLL_FORM', + type: PollActionKind.UPDATE_FORM_FIELD, payload: { field: 'duration', value: !form.duration, @@ -198,7 +195,7 @@ export default function CreatePollModal({visible}) { textStyle={style.btnText} onPress={() => { dispatch({ - type: 'PREVIEW_POLL_FORM', + type: PollActionKind.PREVIEW_FORM, }); }} text="Preview" diff --git a/template/src/components/polling/modal/PollPreviewModal.tsx b/template/src/components/polling/modal/PollPreviewModal.tsx index 879db4f36..e289b0197 100644 --- a/template/src/components/polling/modal/PollPreviewModal.tsx +++ b/template/src/components/polling/modal/PollPreviewModal.tsx @@ -8,10 +8,10 @@ import { } from './BaseModal'; import ThemeConfig from '../../../theme'; import TertiaryButton from '../../../atoms/TertiaryButton'; -import {usePoll} from '../context'; +import {PollActionKind, usePollForm} from '../context/poll-form'; export default function PollPreviewModal({visible}) { - const {state, dispatch} = usePoll(); + const {state, dispatch} = usePollForm(); const {form} = state; return ( @@ -37,7 +37,7 @@ export default function PollPreviewModal({visible}) { { dispatch({ - type: 'EDIT_POLL_FORM', + type: PollActionKind.UPDATE_FORM, }); }} text="Edit" @@ -48,7 +48,7 @@ export default function PollPreviewModal({visible}) { text="Save for later" onPress={() => { dispatch({ - type: 'SAVE_POLL_FORM', + type: PollActionKind.SAVE_FORM, }); }} /> @@ -58,7 +58,7 @@ export default function PollPreviewModal({visible}) { text="Launch Now" onPress={() => { dispatch({ - type: 'LAUNCH_POLL_FORM', + type: PollActionKind.LAUNCH_FORM, }); }} /> diff --git a/template/src/components/polling/modal/SelectNewPollTypeModal.tsx b/template/src/components/polling/modal/SelectNewPollTypeModal.tsx index 7cc32fed5..22fc0451c 100644 --- a/template/src/components/polling/modal/SelectNewPollTypeModal.tsx +++ b/template/src/components/polling/modal/SelectNewPollTypeModal.tsx @@ -2,7 +2,7 @@ import {Text, StyleSheet, View, TouchableOpacity} from 'react-native'; import React from 'react'; import {BaseModal, BaseModalTitle, BaseModalContent} from './BaseModal'; import ThemeConfig from '../../../theme'; -import {PollActionKind, PollKind, usePoll} from '../context'; +import {PollActionKind, PollKind, usePollForm} from '../context/poll-form'; interface newPollType { key: PollKind; @@ -33,10 +33,7 @@ const newPollTypeConfig: newPollType[] = [ ]; export default function SelectNewPollTypeModal({visible}) { - const {state, dispatch} = usePoll(); - - // const open = state.nextUserActivity === 'SELECT_NEW_POLL'; - + const {dispatch} = usePollForm(); return ( @@ -48,8 +45,8 @@ export default function SelectNewPollTypeModal({visible}) { key={item.key} onPress={() => { dispatch({ - type: PollActionKind.SELECT_NEW_POLL, - payload: item.key, + type: PollActionKind.SELECT_POLL, + payload: {pollType: item.key}, }); }}> From 980a88b92d26d926f531f7e1cfb1b08d0ee0e07e Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Tue, 30 Jul 2024 00:06:04 +0530 Subject: [PATCH 003/174] rename files --- .../components/polling/components/Poll.tsx | 2 +- .../polling/context/poll-context.tsx | 31 ++++++++++++ .../{poll-form.tsx => poll-form-context.tsx} | 49 ++++++++++--------- .../polling/modal/CreatePollModal.tsx | 6 ++- .../polling/modal/PollPreviewModal.tsx | 2 +- .../polling/modal/SelectNewPollTypeModal.tsx | 6 ++- 6 files changed, 68 insertions(+), 28 deletions(-) create mode 100644 template/src/components/polling/context/poll-context.tsx rename template/src/components/polling/context/{poll-form.tsx => poll-form-context.tsx} (98%) diff --git a/template/src/components/polling/components/Poll.tsx b/template/src/components/polling/components/Poll.tsx index 60b6f1c3b..1a926d4d1 100644 --- a/template/src/components/polling/components/Poll.tsx +++ b/template/src/components/polling/components/Poll.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {PollProvider} from '../context'; -import {PollFormProvider, usePollForm} from '../context/poll-form'; +import {PollFormProvider, usePollForm} from '../context/poll-form-context'; import SelectNewPollTypeModal from '../modal/SelectNewPollTypeModal'; import CreatePollModal from '../modal/CreatePollModal'; import PollPreviewModal from '../modal/PollPreviewModal'; diff --git a/template/src/components/polling/context/poll-context.tsx b/template/src/components/polling/context/poll-context.tsx new file mode 100644 index 000000000..d76110c75 --- /dev/null +++ b/template/src/components/polling/context/poll-context.tsx @@ -0,0 +1,31 @@ +import React, {createContext, useState} from 'react'; +import {type PollItem} from './poll-form-context'; + +interface Poll { + [key: string]: PollItem; +} + +const PollContext = createContext(null); +PollContext.displayName = 'PollContext'; + +function PollProvider({children}: {children: React.ReactNode}) { + // const [state, dispatch] = React.useReducer(pollReducer, { + // form: null, + // nextUserActivity: 'SELECT_NEW_POLL', + // poll: {}, + // }); + const [polls, setPolls] = useState(null); + + const value = {polls, setPolls}; + return {children}; +} + +function usePoll() { + const context = React.useContext(PollContext); + if (!context) { + throw new Error('usePoll must be used within a PollProvider'); + } + return context; +} + +export {PollProvider, usePoll}; diff --git a/template/src/components/polling/context/poll-form.tsx b/template/src/components/polling/context/poll-form-context.tsx similarity index 98% rename from template/src/components/polling/context/poll-form.tsx rename to template/src/components/polling/context/poll-form-context.tsx index 51adf331e..339ebe433 100644 --- a/template/src/components/polling/context/poll-form.tsx +++ b/template/src/components/polling/context/poll-form-context.tsx @@ -29,6 +29,24 @@ enum PollActionKind { LAUNCH_FORM = 'LAUNCH_FORM', } +interface PollItem { + type: PollKind; + access: PollAccess; + status: PollStatus; + title: string; + question: string; + options: Array<{ + text: string; + value: string; + votes: []; + }> | null; + multiple: boolean; + share: boolean; + duration: boolean; + timer: number; + createdBy: number; +} + type PollFormAction = | { type: PollActionKind.START_POLL; @@ -75,30 +93,7 @@ type PollFormAction = type: PollActionKind.LAUNCH_FORM; }; -interface Poll { - type: PollKind; - access: PollAccess; - status: PollStatus; - title: string; - question: string; - options: Array<{ - text: string; - value: string; - votes: []; - }> | null; - multiple: boolean; - share: boolean; - duration: boolean; - timer: number; - createdBy: number; -} - -interface PollFormState { - form: Poll; - currentStep: 'START_POLL' | 'SELECT_POLL' | 'CREATE_POLL' | 'PREVIEW_POLL'; -} - -const initPollForm = (kind: PollKind): Poll => { +const initPollForm = (kind: PollKind): PollItem => { if (kind === PollKind.OPEN_ENDED) { return { type: PollKind.OPEN_ENDED, @@ -173,6 +168,11 @@ const initPollForm = (kind: PollKind): Poll => { } }; +interface PollFormState { + form: PollItem; + currentStep: 'START_POLL' | 'SELECT_POLL' | 'CREATE_POLL' | 'PREVIEW_POLL'; +} + function pollFormReducer( state: PollFormState, action: PollFormAction, @@ -320,3 +320,4 @@ function usePollForm() { } export {PollFormProvider, usePollForm, PollActionKind, PollKind}; +export type {PollItem}; diff --git a/template/src/components/polling/modal/CreatePollModal.tsx b/template/src/components/polling/modal/CreatePollModal.tsx index 610ab4bf8..c651249cc 100644 --- a/template/src/components/polling/modal/CreatePollModal.tsx +++ b/template/src/components/polling/modal/CreatePollModal.tsx @@ -11,7 +11,11 @@ import LinkButton from '../../../atoms/LinkButton'; import Checkbox from '../../../atoms/Checkbox'; import IconButton from '../../../atoms/IconButton'; import PrimaryButton from '../../../atoms/PrimaryButton'; -import {PollActionKind, PollKind, usePollForm} from '../context/poll-form'; +import { + PollActionKind, + PollKind, + usePollForm, +} from '../context/poll-form-context'; function FormTitle({title}: {title: string}) { return ( diff --git a/template/src/components/polling/modal/PollPreviewModal.tsx b/template/src/components/polling/modal/PollPreviewModal.tsx index e289b0197..dff485043 100644 --- a/template/src/components/polling/modal/PollPreviewModal.tsx +++ b/template/src/components/polling/modal/PollPreviewModal.tsx @@ -8,7 +8,7 @@ import { } from './BaseModal'; import ThemeConfig from '../../../theme'; import TertiaryButton from '../../../atoms/TertiaryButton'; -import {PollActionKind, usePollForm} from '../context/poll-form'; +import {PollActionKind, usePollForm} from '../context/poll-form-context'; export default function PollPreviewModal({visible}) { const {state, dispatch} = usePollForm(); diff --git a/template/src/components/polling/modal/SelectNewPollTypeModal.tsx b/template/src/components/polling/modal/SelectNewPollTypeModal.tsx index 22fc0451c..42ce36549 100644 --- a/template/src/components/polling/modal/SelectNewPollTypeModal.tsx +++ b/template/src/components/polling/modal/SelectNewPollTypeModal.tsx @@ -2,7 +2,11 @@ import {Text, StyleSheet, View, TouchableOpacity} from 'react-native'; import React from 'react'; import {BaseModal, BaseModalTitle, BaseModalContent} from './BaseModal'; import ThemeConfig from '../../../theme'; -import {PollActionKind, PollKind, usePollForm} from '../context/poll-form'; +import { + PollActionKind, + PollKind, + usePollForm, +} from '../context/poll-form-context'; interface newPollType { key: PollKind; From c01b79922b8b1d1e491d07a6aebaee79ecb512de Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Tue, 30 Jul 2024 00:28:38 +0530 Subject: [PATCH 004/174] fix form data --- .../polling/modal/CreatePollModal.tsx | 171 ++++++++++-------- .../polling/modal/PollPreviewModal.tsx | 19 +- 2 files changed, 105 insertions(+), 85 deletions(-) diff --git a/template/src/components/polling/modal/CreatePollModal.tsx b/template/src/components/polling/modal/CreatePollModal.tsx index c651249cc..9f3d19dc1 100644 --- a/template/src/components/polling/modal/CreatePollModal.tsx +++ b/template/src/components/polling/modal/CreatePollModal.tsx @@ -63,98 +63,113 @@ export default function CreatePollModal({visible}) { {/* Options section */} - - - - {form.type === PollKind.MCQ ? ( - <> - {form.options.map((option, index) => ( - - {index + 1} - { - dispatch({ - type: PollActionKind.UPDATE_FORM_OPTION, - payload: { - value: text, - index: index, - }, - }); - }} - placeholder="Add text here..." - placeholderTextColor={ - $config.FONT_COLOR + ThemeConfig.EmphasisPlus.low - } - /> - - { + {form.type === PollKind.MCQ || form.type === PollKind.YES_NO ? ( + + + + {form.type === PollKind.MCQ ? ( + <> + {form.options.map((option, index) => ( + + {index + 1} + { dispatch({ - type: PollActionKind.DELETE_FORM_OPTION, + type: PollActionKind.UPDATE_FORM_OPTION, payload: { + value: text, index: index, }, }); }} + placeholder="Add text here..." + placeholderTextColor={ + $config.FONT_COLOR + ThemeConfig.EmphasisPlus.low + } /> + + { + dispatch({ + type: PollActionKind.DELETE_FORM_OPTION, + payload: { + index: index, + }, + }); + }} + /> + + ))} + + { + dispatch({ + type: PollActionKind.ADD_FORM_OPTION, + }); + }} + /> + + + ) : ( + <> + )} + {form.type === PollKind.YES_NO ? ( + <> + + Yes + + + No - ))} - - { - dispatch({ - type: PollActionKind.ADD_FORM_OPTION, - }); - }} - /> - - - ) : ( - <> - - Yes - - - No - - - )} + + ) : ( + <> + )} + - + ) : ( + <> + )} {/* Sections templete */} - - dispatch({ - type: PollActionKind.UPDATE_FORM_FIELD, - payload: { - field: 'multiple', - value: !form.multiple, - }, - }) - } - /> + {form.type === PollKind.MCQ ? ( + + dispatch({ + type: PollActionKind.UPDATE_FORM_FIELD, + payload: { + field: 'multiple', + value: !form.multiple, + }, + }) + } + /> + ) : ( + <> + )} @@ -22,13 +23,17 @@ export default function PollPreviewModal({visible}) { {form.timer} )} {form.question} - - {form.options.map(option => ( - - {option.text} - - ))} - + {form?.options ? ( + + {form.options.map(option => ( + + {option.text} + + ))} + + ) : ( + <> + )} From 1b6643a18e880a7912a12d2e11c818cc7ec651d0 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Tue, 30 Jul 2024 00:54:55 +0530 Subject: [PATCH 005/174] rename types and improve state update --- .../polling/context/poll-form-context.tsx | 93 +++++++++---------- .../polling/modal/CreatePollModal.tsx | 18 ++-- .../polling/modal/PollPreviewModal.tsx | 14 ++- .../polling/modal/SelectNewPollTypeModal.tsx | 4 +- 4 files changed, 67 insertions(+), 62 deletions(-) diff --git a/template/src/components/polling/context/poll-form-context.tsx b/template/src/components/polling/context/poll-form-context.tsx index 339ebe433..32984cdfb 100644 --- a/template/src/components/polling/context/poll-form-context.tsx +++ b/template/src/components/polling/context/poll-form-context.tsx @@ -16,19 +16,6 @@ enum PollKind { YES_NO = 'YES_NO', } -enum PollActionKind { - START_POLL = 'START_POLL', - SELECT_POLL = 'SELECT_POLL', - UPDATE_FORM_FIELD = 'UPDATE_FORM_FIELD', - UPDATE_FORM_OPTION = 'UPDATE_FORM_OPTION', - ADD_FORM_OPTION = 'ADD_FORM_OPTION', - DELETE_FORM_OPTION = 'DELETE_FORM_OPTION', - PREVIEW_FORM = 'PREVIEW_FORM', - UPDATE_FORM = 'UPDATE_FORM', - SAVE_FORM = 'SAVE_FORM', - LAUNCH_FORM = 'LAUNCH_FORM', -} - interface PollItem { type: PollKind; access: PollAccess; @@ -47,50 +34,62 @@ interface PollItem { createdBy: number; } +enum PollFormActionKind { + START_POLL = 'START_POLL', + SELECT_POLL = 'SELECT_POLL', + UPDATE_FORM_FIELD = 'UPDATE_FORM_FIELD', + UPDATE_FORM_OPTION = 'UPDATE_FORM_OPTION', + ADD_FORM_OPTION = 'ADD_FORM_OPTION', + DELETE_FORM_OPTION = 'DELETE_FORM_OPTION', + PREVIEW_FORM = 'PREVIEW_FORM', + UPDATE_FORM = 'UPDATE_FORM', + SAVE_FORM = 'SAVE_FORM', +} + type PollFormAction = | { - type: PollActionKind.START_POLL; + type: PollFormActionKind.START_POLL; } | { - type: PollActionKind.SELECT_POLL; + type: PollFormActionKind.SELECT_POLL; payload: { pollType: PollKind; }; } | { - type: PollActionKind.UPDATE_FORM_FIELD; + type: PollFormActionKind.UPDATE_FORM_FIELD; payload: { field: string; value: string | boolean; }; } | { - type: PollActionKind.ADD_FORM_OPTION; + type: PollFormActionKind.ADD_FORM_OPTION; } | { - type: PollActionKind.UPDATE_FORM_OPTION; + type: PollFormActionKind.UPDATE_FORM_OPTION; payload: { index: number; value: string; }; } | { - type: PollActionKind.DELETE_FORM_OPTION; + type: PollFormActionKind.DELETE_FORM_OPTION; payload: { index: number; }; } | { - type: PollActionKind.PREVIEW_FORM; - } - | { - type: PollActionKind.SAVE_FORM; + type: PollFormActionKind.PREVIEW_FORM; } | { - type: PollActionKind.UPDATE_FORM; + type: PollFormActionKind.SAVE_FORM; + payload: { + launch: boolean; + }; } | { - type: PollActionKind.LAUNCH_FORM; + type: PollFormActionKind.UPDATE_FORM; }; const initPollForm = (kind: PollKind): PollItem => { @@ -168,6 +167,13 @@ const initPollForm = (kind: PollKind): PollItem => { } }; +const getPollTimer = (isDurationEnabled: boolean) => { + if (isDurationEnabled) { + return 10000; + } + return -1; +}; + interface PollFormState { form: PollItem; currentStep: 'START_POLL' | 'SELECT_POLL' | 'CREATE_POLL' | 'PREVIEW_POLL'; @@ -178,30 +184,33 @@ function pollFormReducer( action: PollFormAction, ): PollFormState { switch (action.type) { - case PollActionKind.START_POLL: { + case PollFormActionKind.START_POLL: { return { ...state, form: null, currentStep: 'SELECT_POLL', }; } - case PollActionKind.SELECT_POLL: { + case PollFormActionKind.SELECT_POLL: { return { ...state, currentStep: 'CREATE_POLL', form: initPollForm(action.payload.pollType), }; } - case PollActionKind.UPDATE_FORM_FIELD: { + case PollFormActionKind.UPDATE_FORM_FIELD: { return { ...state, form: { ...state.form, [action.payload.field]: action.payload.value, + ...(action.payload.field === 'duration' && { + timer: getPollTimer(action.payload.value as boolean), + }), }, }; } - case PollActionKind.ADD_FORM_OPTION: { + case PollFormActionKind.ADD_FORM_OPTION: { return { ...state, form: { @@ -217,7 +226,7 @@ function pollFormReducer( }, }; } - case PollActionKind.UPDATE_FORM_OPTION: { + case PollFormActionKind.UPDATE_FORM_OPTION: { return { ...state, form: { @@ -239,7 +248,7 @@ function pollFormReducer( }, }; } - case PollActionKind.DELETE_FORM_OPTION: { + case PollFormActionKind.DELETE_FORM_OPTION: { return { ...state, form: { @@ -250,34 +259,24 @@ function pollFormReducer( }, }; } - case PollActionKind.PREVIEW_FORM: { + case PollFormActionKind.PREVIEW_FORM: { return { ...state, currentStep: 'PREVIEW_POLL', }; } - case PollActionKind.UPDATE_FORM: { + case PollFormActionKind.UPDATE_FORM: { return { ...state, currentStep: 'CREATE_POLL', }; } - case PollActionKind.SAVE_FORM: { - return { - ...state, - form: { - ...state.form, - status: PollStatus.LATER, - }, - currentStep: null, - }; - } - case PollActionKind.LAUNCH_FORM: { + case PollFormActionKind.SAVE_FORM: { return { ...state, form: { ...state.form, - status: PollStatus.ACTIVE, + status: action.payload.launch ? PollStatus.ACTIVE : PollStatus.LATER, }, currentStep: null, }; @@ -299,7 +298,7 @@ PollFormContext.displayName = 'PollFormContext'; function PollFormProvider({children}: {children?: React.ReactNode}) { const [state, dispatch] = useReducer(pollFormReducer, { form: null, - currentStep: null, + currentStep: 'SELECT_POLL', }); const value = {state, dispatch}; @@ -319,5 +318,5 @@ function usePollForm() { return context; } -export {PollFormProvider, usePollForm, PollActionKind, PollKind}; +export {PollFormProvider, usePollForm, PollFormActionKind, PollKind}; export type {PollItem}; diff --git a/template/src/components/polling/modal/CreatePollModal.tsx b/template/src/components/polling/modal/CreatePollModal.tsx index 9f3d19dc1..73529258d 100644 --- a/template/src/components/polling/modal/CreatePollModal.tsx +++ b/template/src/components/polling/modal/CreatePollModal.tsx @@ -12,7 +12,7 @@ import Checkbox from '../../../atoms/Checkbox'; import IconButton from '../../../atoms/IconButton'; import PrimaryButton from '../../../atoms/PrimaryButton'; import { - PollActionKind, + PollFormActionKind, PollKind, usePollForm, } from '../context/poll-form-context'; @@ -48,7 +48,7 @@ export default function CreatePollModal({visible}) { return; } dispatch({ - type: PollActionKind.UPDATE_FORM_FIELD, + type: PollFormActionKind.UPDATE_FORM_FIELD, payload: { field: 'question', value: text, @@ -79,7 +79,7 @@ export default function CreatePollModal({visible}) { value={option.text} onChangeText={text => { dispatch({ - type: PollActionKind.UPDATE_FORM_OPTION, + type: PollFormActionKind.UPDATE_FORM_OPTION, payload: { value: text, index: index, @@ -104,7 +104,7 @@ export default function CreatePollModal({visible}) { }} onPress={() => { dispatch({ - type: PollActionKind.DELETE_FORM_OPTION, + type: PollFormActionKind.DELETE_FORM_OPTION, payload: { index: index, }, @@ -120,7 +120,7 @@ export default function CreatePollModal({visible}) { textStyle={style.pFormOptionLink} onPress={() => { dispatch({ - type: PollActionKind.ADD_FORM_OPTION, + type: PollFormActionKind.ADD_FORM_OPTION, }); }} /> @@ -159,7 +159,7 @@ export default function CreatePollModal({visible}) { labelStye={style.pFormOptionText} onChange={() => dispatch({ - type: PollActionKind.UPDATE_FORM_FIELD, + type: PollFormActionKind.UPDATE_FORM_FIELD, payload: { field: 'multiple', value: !form.multiple, @@ -178,7 +178,7 @@ export default function CreatePollModal({visible}) { labelStye={style.pFormOptionText} onChange={() => dispatch({ - type: PollActionKind.UPDATE_FORM_FIELD, + type: PollFormActionKind.UPDATE_FORM_FIELD, payload: { field: 'share', value: !form.share, @@ -194,7 +194,7 @@ export default function CreatePollModal({visible}) { labelStye={style.pFormOptionText} onChange={() => dispatch({ - type: PollActionKind.UPDATE_FORM_FIELD, + type: PollFormActionKind.UPDATE_FORM_FIELD, payload: { field: 'duration', value: !form.duration, @@ -214,7 +214,7 @@ export default function CreatePollModal({visible}) { textStyle={style.btnText} onPress={() => { dispatch({ - type: PollActionKind.PREVIEW_FORM, + type: PollFormActionKind.PREVIEW_FORM, }); }} text="Preview" diff --git a/template/src/components/polling/modal/PollPreviewModal.tsx b/template/src/components/polling/modal/PollPreviewModal.tsx index 04ee35eba..967c1d07b 100644 --- a/template/src/components/polling/modal/PollPreviewModal.tsx +++ b/template/src/components/polling/modal/PollPreviewModal.tsx @@ -8,7 +8,7 @@ import { } from './BaseModal'; import ThemeConfig from '../../../theme'; import TertiaryButton from '../../../atoms/TertiaryButton'; -import {PollActionKind, usePollForm} from '../context/poll-form-context'; +import {PollFormActionKind, usePollForm} from '../context/poll-form-context'; export default function PollPreviewModal({visible}) { const {state, dispatch} = usePollForm(); @@ -42,7 +42,7 @@ export default function PollPreviewModal({visible}) { { dispatch({ - type: PollActionKind.UPDATE_FORM, + type: PollFormActionKind.UPDATE_FORM, }); }} text="Edit" @@ -53,7 +53,10 @@ export default function PollPreviewModal({visible}) { text="Save for later" onPress={() => { dispatch({ - type: PollActionKind.SAVE_FORM, + type: PollFormActionKind.SAVE_FORM, + payload: { + launch: false, + }, }); }} /> @@ -63,7 +66,10 @@ export default function PollPreviewModal({visible}) { text="Launch Now" onPress={() => { dispatch({ - type: PollActionKind.LAUNCH_FORM, + type: PollFormActionKind.SAVE_FORM, + payload: { + launch: true, + }, }); }} /> diff --git a/template/src/components/polling/modal/SelectNewPollTypeModal.tsx b/template/src/components/polling/modal/SelectNewPollTypeModal.tsx index 42ce36549..7d72c449d 100644 --- a/template/src/components/polling/modal/SelectNewPollTypeModal.tsx +++ b/template/src/components/polling/modal/SelectNewPollTypeModal.tsx @@ -3,7 +3,7 @@ import React from 'react'; import {BaseModal, BaseModalTitle, BaseModalContent} from './BaseModal'; import ThemeConfig from '../../../theme'; import { - PollActionKind, + PollFormActionKind, PollKind, usePollForm, } from '../context/poll-form-context'; @@ -49,7 +49,7 @@ export default function SelectNewPollTypeModal({visible}) { key={item.key} onPress={() => { dispatch({ - type: PollActionKind.SELECT_POLL, + type: PollFormActionKind.SELECT_POLL, payload: {pollType: item.key}, }); }}> From 802ed3859b45025d1975df5839c1e37527993990 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Tue, 30 Jul 2024 10:59:44 +0530 Subject: [PATCH 006/174] add poll form --- template/src/components/polling/context.tsx | 280 ------------------ .../polling/context/poll-context.tsx | 44 ++- .../polling/context/poll-form-context.tsx | 31 +- .../polling/modal/PollPreviewModal.tsx | 50 +++- 4 files changed, 91 insertions(+), 314 deletions(-) delete mode 100644 template/src/components/polling/context.tsx diff --git a/template/src/components/polling/context.tsx b/template/src/components/polling/context.tsx deleted file mode 100644 index f9d36472b..000000000 --- a/template/src/components/polling/context.tsx +++ /dev/null @@ -1,280 +0,0 @@ -import React, {createContext, Dispatch} from 'react'; -import SelectNewPollTypeModal from './modal/SelectNewPollTypeModal'; -import CreatePollModal from './modal/CreatePollModal'; -import PollPreviewModal from './modal/PollPreviewModal'; -import SharePollModal from './modal/SharePollModal'; - -enum PollKind { - OPEN_ENDED = 'OPEN_ENDED', - MCQ = 'MCQ', - YES_NO = 'YES_NO', -} - -type PollAccess = 'public' | 'private'; -type PollStatus = 'active' | 'finished' | 'later'; - -interface Poll { - type: PollKind; - access: PollAccess; - status: PollStatus; - title: string; - question: string; - options: Array<{ - text: string; - value: string; - votes: []; - }> | null; - multiple: boolean; - share: boolean; - duration: boolean; - timer: number; - createdBy: number; -} - -const initializeNewPoll = (kind: PollKind): Poll => { - if (kind === PollKind.OPEN_ENDED) { - return { - type: PollKind.OPEN_ENDED, - access: 'public', - status: 'later', - title: 'Open Ended Poll', - question: '', - options: null, - multiple: false, - share: false, - duration: false, - timer: -1, - createdBy: -1, - }; - } - if (kind === PollKind.MCQ) { - return { - type: PollKind.MCQ, - access: 'public', - status: 'later', - title: 'Multiple Choice Question', - question: '', - options: [ - { - text: '', - value: '', - votes: [], - }, - { - text: '', - value: '', - votes: [], - }, - { - text: '', - value: '', - votes: [], - }, - ], - multiple: true, - share: false, - duration: false, - timer: -1, - createdBy: -1, - }; - } - if (kind === PollKind.YES_NO) { - return { - type: PollKind.YES_NO, - access: 'public', - status: 'later', - title: 'Yes/No', - question: '', - options: [ - { - text: 'YES', - value: 'yes', - votes: [], - }, - { - text: 'No', - value: 'no', - votes: [], - }, - ], - multiple: false, - share: false, - duration: false, - timer: -1, - createdBy: -1, - }; - } -}; - -// To DO - -// An interface for our actions -// interface RecordingsActions { -// type: keyof typeof RecordingsActionKind; -// payload: T; -// } - -// const initialPollsState: PollState[] = []; - -type PollUserActivity = - | 'SELECT_NEW_POLL' - | 'CREATE_POLL' - | 'PREVIEW_POLL' - | 'SHARE_POLL'; - -interface PollObject { - [key: string]: Poll; -} -interface PollState { - form: Poll; - nextUserActivity: PollUserActivity; - poll: PollObject; -} -enum PollActionKind { - SELECT_NEW_POLL = 'SELECT_NEW_POLL', - EDIT_POLL_FORM_FIELD = 'EDIT_POLL_FORM_FIELD', - EDIT_POLL_FORM_OPTION = 'EDIT_POLL_FORM_OPTION', - ADD_POLL_FORM_OPTION = 'ADD_POLL_FORM_OPTION', - DELETE_POLL_FORM_OPTION = 'DELETE_POLL_FORM_OPTION', - PREVIEW_POLL_FORM = 'PREVIEW_POLL_FORM', - EDIT_POLL_FORM = 'EDIT_POLL_FORM', - SAVE_POLL_FORM = 'SAVE_POLL_FORM', - LAUNCH_POLL_FORM = 'LAUNCH_POLL_FORM', -} -interface PollAction { - type: PollActionKind; - payload: any; -} -function pollReducer(state: PollState, action: PollAction): PollState { - switch (action.type) { - case PollActionKind.SELECT_NEW_POLL: { - return { - ...state, - nextUserActivity: 'CREATE_POLL', - form: initializeNewPoll(action.payload), - }; - } - case PollActionKind.EDIT_POLL_FORM_FIELD: { - return { - ...state, - form: { - ...state.form, - [action.payload.field]: action.payload.value, - }, - }; - } - case PollActionKind.ADD_POLL_FORM_OPTION: { - return { - ...state, - form: { - ...state.form, - options: [ - ...state.form.options, - { - text: '', - value: '', - votes: [], - }, - ], - }, - }; - } - case PollActionKind.EDIT_POLL_FORM_OPTION: { - return { - ...state, - form: { - ...state.form, - options: state.form.options.map((option, i) => { - if (i === action.payload.key) { - const value = action.payload.value - .replace(/\s+/g, '-') - .toLowerCase(); - return { - ...option, - text: action.payload.value, - value, - votes: [], - }; - } - return option; - }), - }, - }; - } - case PollActionKind.DELETE_POLL_FORM_OPTION: { - return { - ...state, - form: { - ...state.form, - options: state.form.options.filter( - (option, i) => i !== action.payload.key, - ), - }, - }; - } - case PollActionKind.PREVIEW_POLL_FORM: { - return { - ...state, - nextUserActivity: 'PREVIEW_POLL', - }; - } - case PollActionKind.EDIT_POLL_FORM: { - return { - ...state, - nextUserActivity: 'CREATE_POLL', - }; - } - case PollActionKind.SAVE_POLL_FORM: { - return { - ...state, - form: { - ...state.form, - status: 'later', - }, - nextUserActivity: null, - }; - } - case PollActionKind.LAUNCH_POLL_FORM: { - return { - ...state, - form: { - ...state.form, - status: 'active', - }, - nextUserActivity: null, - }; - } - default: { - return state; - } - } -} - -interface PollContextValue { - state: PollState; - dispatch: any; -} - -const PollContext = createContext(null); -PollContext.displayName = 'PollContext'; - -function PollProvider({children}: {children: React.ReactNode}) { - const [state, dispatch] = React.useReducer(pollReducer, { - form: null, - nextUserActivity: 'SELECT_NEW_POLL', - poll: {}, - }); - - const value = {state, dispatch}; - return {children}; -} - -function usePoll() { - const context = React.useContext(PollContext); - if (!context) { - throw new Error('usePoll must be used within a PollProvider'); - } - return context; -} - -export {PollProvider, usePoll, PollKind, PollActionKind}; diff --git a/template/src/components/polling/context/poll-context.tsx b/template/src/components/polling/context/poll-context.tsx index d76110c75..62b94f1bf 100644 --- a/template/src/components/polling/context/poll-context.tsx +++ b/template/src/components/polling/context/poll-context.tsx @@ -1,22 +1,44 @@ -import React, {createContext, useState} from 'react'; +import React, {createContext, useReducer, Dispatch} from 'react'; import {type PollItem} from './poll-form-context'; interface Poll { [key: string]: PollItem; } -const PollContext = createContext(null); +enum PollActionKind { + ADD_FINAL_POLL_ITEM = 'ADD_FINAL_POLL_ITEM', +} + +type PollAction = { + type: PollActionKind.ADD_FINAL_POLL_ITEM; + payload: {item: Poll}; +}; + +function pollReducer(state: Poll, action: PollAction): Poll { + switch (action.type) { + case PollActionKind.ADD_FINAL_POLL_ITEM: { + return { + ...state, + ...action.payload.item, + }; + } + default: { + return state; + } + } +} + +interface PollContextValue { + polls: Poll; + dispatch: Dispatch; +} + +const PollContext = createContext(null); PollContext.displayName = 'PollContext'; function PollProvider({children}: {children: React.ReactNode}) { - // const [state, dispatch] = React.useReducer(pollReducer, { - // form: null, - // nextUserActivity: 'SELECT_NEW_POLL', - // poll: {}, - // }); - const [polls, setPolls] = useState(null); - - const value = {polls, setPolls}; + const [polls, dispatch] = useReducer(pollReducer, {}); + const value = {polls, dispatch}; return {children}; } @@ -28,4 +50,4 @@ function usePoll() { return context; } -export {PollProvider, usePoll}; +export {PollProvider, usePoll, PollActionKind}; diff --git a/template/src/components/polling/context/poll-form-context.tsx b/template/src/components/polling/context/poll-form-context.tsx index 32984cdfb..24174d5bb 100644 --- a/template/src/components/polling/context/poll-form-context.tsx +++ b/template/src/components/polling/context/poll-form-context.tsx @@ -44,6 +44,7 @@ enum PollFormActionKind { PREVIEW_FORM = 'PREVIEW_FORM', UPDATE_FORM = 'UPDATE_FORM', SAVE_FORM = 'SAVE_FORM', + POLL_FORM_CLOSE = 'POLL_FORM_CLOSE', } type PollFormAction = @@ -86,10 +87,14 @@ type PollFormAction = type: PollFormActionKind.SAVE_FORM; payload: { launch: boolean; + createdBy: number; }; } | { type: PollFormActionKind.UPDATE_FORM; + } + | { + type: PollFormActionKind.POLL_FORM_CLOSE; }; const initPollForm = (kind: PollKind): PollItem => { @@ -271,13 +276,19 @@ function pollFormReducer( currentStep: 'CREATE_POLL', }; } - case PollFormActionKind.SAVE_FORM: { + // case PollFormActionKind.SAVE_FORM: { + // return { + // ...state, + // form: { + // ...state.form, + // status: action.payload.launch ? PollStatus.ACTIVE : PollStatus.LATER, + // }, + // currentStep: null, + // }; + // } + case PollFormActionKind.POLL_FORM_CLOSE: { return { - ...state, - form: { - ...state.form, - status: action.payload.launch ? PollStatus.ACTIVE : PollStatus.LATER, - }, + form: null, currentStep: null, }; } @@ -318,5 +329,11 @@ function usePollForm() { return context; } -export {PollFormProvider, usePollForm, PollFormActionKind, PollKind}; +export { + PollFormProvider, + usePollForm, + PollFormActionKind, + PollKind, + PollStatus, +}; export type {PollItem}; diff --git a/template/src/components/polling/modal/PollPreviewModal.tsx b/template/src/components/polling/modal/PollPreviewModal.tsx index 967c1d07b..c0d06a09b 100644 --- a/template/src/components/polling/modal/PollPreviewModal.tsx +++ b/template/src/components/polling/modal/PollPreviewModal.tsx @@ -1,5 +1,5 @@ import {Text, StyleSheet, View} from 'react-native'; -import React from 'react'; +import React, {useEffect} from 'react'; import { BaseModal, BaseModalTitle, @@ -8,12 +8,40 @@ import { } from './BaseModal'; import ThemeConfig from '../../../theme'; import TertiaryButton from '../../../atoms/TertiaryButton'; -import {PollFormActionKind, usePollForm} from '../context/poll-form-context'; +import { + PollFormActionKind, + PollStatus, + usePollForm, +} from '../context/poll-form-context'; +import {usePoll} from '../context/poll-context'; +import {PollActionKind} from '../context/poll-context'; +import {useLocalUid} from 'agora-rn-uikit'; export default function PollPreviewModal({visible}) { - const {state, dispatch} = usePollForm(); + const {state, dispatch: pollFormDispatch} = usePollForm(); + const {dispatch: pollDispatch} = usePoll(); + const localUid = useLocalUid(); + const {form} = state; + const onSave = (launch: boolean) => { + pollDispatch({ + type: PollActionKind.ADD_FINAL_POLL_ITEM, + payload: { + item: { + [localUid]: { + ...form, + status: launch ? PollStatus.LATER : PollStatus.ACTIVE, + createdBy: localUid, + }, + }, + }, + }); + pollFormDispatch({ + type: PollFormActionKind.POLL_FORM_CLOSE, + }); + }; + return ( @@ -41,7 +69,7 @@ export default function PollPreviewModal({visible}) { { - dispatch({ + pollFormDispatch({ type: PollFormActionKind.UPDATE_FORM, }); }} @@ -52,12 +80,7 @@ export default function PollPreviewModal({visible}) { { - dispatch({ - type: PollFormActionKind.SAVE_FORM, - payload: { - launch: false, - }, - }); + onSave(false); }} /> @@ -65,12 +88,7 @@ export default function PollPreviewModal({visible}) { { - dispatch({ - type: PollFormActionKind.SAVE_FORM, - payload: { - launch: true, - }, - }); + onSave(true); }} /> From 9b4fb600c7de6724f78df7dda3c59783814488f3 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Tue, 30 Jul 2024 11:08:27 +0530 Subject: [PATCH 007/174] fix imports --- template/src/components/polling/components/Poll.tsx | 2 +- template/src/components/polling/modal/PollPreviewModal.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/template/src/components/polling/components/Poll.tsx b/template/src/components/polling/components/Poll.tsx index 1a926d4d1..a7c16502f 100644 --- a/template/src/components/polling/components/Poll.tsx +++ b/template/src/components/polling/components/Poll.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {PollProvider} from '../context'; +import {PollProvider} from '../context/poll-context'; import {PollFormProvider, usePollForm} from '../context/poll-form-context'; import SelectNewPollTypeModal from '../modal/SelectNewPollTypeModal'; import CreatePollModal from '../modal/CreatePollModal'; diff --git a/template/src/components/polling/modal/PollPreviewModal.tsx b/template/src/components/polling/modal/PollPreviewModal.tsx index c0d06a09b..035176eed 100644 --- a/template/src/components/polling/modal/PollPreviewModal.tsx +++ b/template/src/components/polling/modal/PollPreviewModal.tsx @@ -15,7 +15,7 @@ import { } from '../context/poll-form-context'; import {usePoll} from '../context/poll-context'; import {PollActionKind} from '../context/poll-context'; -import {useLocalUid} from 'agora-rn-uikit'; +import {useLocalUid} from '../../../../agora-rn-uikit'; export default function PollPreviewModal({visible}) { const {state, dispatch: pollFormDispatch} = usePollForm(); From d37cb1b6b254f64b6f71d40556d45d5ce9952add Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Tue, 6 Aug 2024 00:19:27 +0530 Subject: [PATCH 008/174] wip poll event code --- template/bridge/rtm/web/index.ts | 96 ++++++---- template/src/components/RTMConfigure.tsx | 22 ++- .../components/polling/components/Poll.tsx | 35 +--- .../polling/context/poll-context.tsx | 140 ++++++++++++-- .../polling/context/poll-events.tsx | 42 +++++ .../src/components/polling/form-config.ts | 94 ++++++++++ .../polling/modal/CreatePollModal.tsx | 177 ++++++++++-------- .../polling/modal/PollFormModal.tsx | 72 +++++++ .../polling/modal/PollPreviewModal.tsx | 60 ++---- .../polling/modal/SelectNewPollTypeModal.tsx | 24 +-- template/src/rtm-events-api/Events.ts | 40 +++- template/src/rtm-events-api/types.ts | 6 + 12 files changed, 585 insertions(+), 223 deletions(-) create mode 100644 template/src/components/polling/context/poll-events.tsx create mode 100644 template/src/components/polling/form-config.ts create mode 100644 template/src/components/polling/modal/PollFormModal.tsx diff --git a/template/bridge/rtm/web/index.ts b/template/bridge/rtm/web/index.ts index 2470cc327..7d2a5beda 100644 --- a/template/bridge/rtm/web/index.ts +++ b/template/bridge/rtm/web/index.ts @@ -29,7 +29,7 @@ type callbackType = (args?: any) => void; export default class RtmEngine { public appId: string; - public client: any; + public client: RtmClient; public channelMap = new Map([]); public remoteInvititations = new Map([]); public localInvititations = new Map([]); @@ -37,13 +37,13 @@ export default class RtmEngine { ['channelMessageReceived', () => null], ['channelMemberJoined', () => null], ['channelMemberLeft', () => null], - ['ChannelAttributesUpdated', () => null], ]); public clientEventsMap = new Map([ ['connectionStateChanged', () => null], ['messageReceived', () => null], ['remoteInvitationReceived', () => null], ['tokenExpired', () => null], + ['channelAttributesUpdated', () => null], ]); public localInvitationEventsMap = new Map([ ['localInvitationAccepted', () => null], @@ -67,15 +67,15 @@ export default class RtmEngine { if ( event === 'channelMessageReceived' || event === 'channelMemberJoined' || - event === 'channelMemberLeft' || - event === 'ChannelAttributesUpdated' + event === 'channelMemberLeft' ) { this.channelEventsMap.set(event, listener); } else if ( event === 'connectionStateChanged' || event === 'messageReceived' || event === 'remoteInvitationReceived' || - event === 'tokenExpired' + event === 'tokenExpired' || + event === 'channelAttributesUpdated' ) { this.clientEventsMap.set(event, listener); } else if ( @@ -235,16 +235,25 @@ export default class RtmEngine { .get(channelId) .on('AttributesUpdated', (attributes: RtmChannelAttribute) => { /** - * a) The following piece of code is commented for future reference. - * b) To be used in future implementations of channel attributes - * c) Kindly note the below event listener 'ChannelAttributesUpdated' expects type + * a) Kindly note the below event listener 'channelAttributesUpdated' expects type * RtmChannelAttribute[] (array of objects [{key: 'valueOfKey', value: 'valueOfValue}]) * whereas the above listener 'AttributesUpdated' receives attributes in object form * {[valueOfKey]: valueOfValue} of type RtmChannelAttribute - * d) Hence in this bridge the data should be modified to keep in sync with both the + * b) Hence in this bridge the data should be modified to keep in sync with both the * listeners for web and listener for native */ - // this.channelEventsMap.get('ChannelAttributesUpdated')(attributes); //RN expect evt: any + /** + * 1. Loop through object + * 2. Create a object {key: "", value: ""} and push into array + * 3. Return the Array + */ + const channelAttributes = Object.keys(attributes).reduce((acc, key) => { + const {value, lastUpdateTs, lastUpdateUserId} = attributes[key]; + acc.push({key, value, lastUpdateTs, lastUpdateUserId}); + return acc; + }, []); + + this.clientEventsMap.get('channelAttributesUpdated')(channelAttributes); //RN expect evt: any }); return this.channelMap.get(channelId).join(); @@ -331,6 +340,32 @@ export default class RtmEngine { return this.client.clearLocalUserAttributes(); } + async getChannelAttributes(channelId: string) { + let response = {}; + await this.client + .getChannelAttributes(channelId) + .then((attributes: string) => { + response = {attributes}; + }) + .catch((e: any) => { + Promise.reject(e); + }); + return response; + } + + async getChannelAttributesByKeys(channelId: string, attributeKeys: string[]) { + let response = {}; + await this.client + .getChannelAttributesByKeys(channelId, attributeKeys) + .then((attributes: string) => { + response = {attributes}; + }) + .catch((e: any) => { + Promise.reject(e); + }); + return response; + } + async removeLocalUserAttributesByKeys(keys: string[]) { return this.client.deleteLocalUserAttributesByKeys(keys); } @@ -366,7 +401,7 @@ export default class RtmEngine { return this.client.addOrUpdateLocalUserAttributes({...formattedAttributes}); } - addOrUpdateChannelAttributes( + async addOrUpdateChannelAttributes( channelId: string, attributes: RtmChannelAttribute[], option: ChannelAttributeOptions, @@ -376,18 +411,17 @@ export default class RtmEngine { * to be used in future implementations of channel attributes */ - // let formattedAttributes: any = {}; - // attributes.map((attribute) => { - // let key = Object.values(attribute)[0]; - // let value = Object.values(attribute)[1]; - // formattedAttributes[key] = value; - // }); - // return this.client.addOrUpdateChannelAttributes( - // channelId, - // {...formattedAttributes}, - // option, - // ); - return Promise.resolve(); // remove this line when functionality is added to the above function + let formattedAttributes: any = {}; + attributes.map(attribute => { + let key = Object.values(attribute)[0]; + let value = Object.values(attribute)[1]; + formattedAttributes[key] = value; + }); + return this.client.addOrUpdateChannelAttributes( + channelId, + {...formattedAttributes}, + option, + ); } async sendLocalInvitation(invitationProps: any) { @@ -492,20 +526,4 @@ export default class RtmEngine { getSdkVersion(callback: (version: string) => void) { callback(VERSION); } - - addListener( - event: EventType, - listener: RtmClientEvents[EventType], - ): Subscription { - if (event === 'ChannelAttributesUpdated') { - this.channelEventsMap.set(event, listener as callbackType); - } - return { - remove: () => { - console.log( - 'Use destroy method to remove all the event listeners from the RtcEngine instead.', - ); - }, - }; - } } diff --git a/template/src/components/RTMConfigure.tsx b/template/src/components/RTMConfigure.tsx index 4bd1a8cef..38a82fd59 100644 --- a/template/src/components/RTMConfigure.tsx +++ b/template/src/components/RTMConfigure.tsx @@ -236,15 +236,10 @@ const RtmConfigure = (props: any) => { const channelAttr = await engine.current.getChannelAttributes( rtcProps.channel, ); - const channelAttrByKeys = await engine.current.getChannelAttributesByKeys( - rtcProps.channel, - ['poll'], - ); console.log('supriya getChannelAttributes', channelAttr); - console.log('supriya getChannelAttributesByKeys', channelAttrByKeys); logger.log(LogSource.AgoraSDK, 'Log', 'RTM getMembers done'); } catch (error) { - console.log('supriya error: ', error); + console.log('error: ', error); logger.error( LogSource.AgoraSDK, 'Log', @@ -593,7 +588,20 @@ const RtmConfigure = (props: any) => { }); engine.current.on('channelAttributesUpdated', (data: any) => { - console.log('supriya channel attributes receivwd', data); + console.log('supriya 2 channel attributes receivwd', data); + try { + // const {uid, channelId, text, ts} = evt; + // const timestamp = getMessageTime(ts); + // const sender = isAndroid() ? get32BitUid(peerId) : parseInt(peerId); + // eventDispatcher(data, sender, timestamp); + } catch (error) { + logger.error( + LogSource.Events, + 'CUSTOM_EVENTS', + 'error while dispatching through eventDispatcher', + error, + ); + } }); await doLoginAndSetupRTM(); diff --git a/template/src/components/polling/components/Poll.tsx b/template/src/components/polling/components/Poll.tsx index a7c16502f..f1cce43a0 100644 --- a/template/src/components/polling/components/Poll.tsx +++ b/template/src/components/polling/components/Poll.tsx @@ -1,38 +1,17 @@ import React from 'react'; import {PollProvider} from '../context/poll-context'; -import {PollFormProvider, usePollForm} from '../context/poll-form-context'; -import SelectNewPollTypeModal from '../modal/SelectNewPollTypeModal'; -import CreatePollModal from '../modal/CreatePollModal'; -import PollPreviewModal from '../modal/PollPreviewModal'; +import PollFormModal from '../modal/PollFormModal'; +import {PollEventsProvider} from '../context/poll-events'; -function Poll() { +function Poll({children}: {children?: React.ReactNode}) { return ( - - - + + {children} + + ); } export default Poll; - -function PollForms() { - const {state} = usePollForm(); - const {currentStep} = state; - - function renderSwitch() { - switch (currentStep) { - case 'SELECT_POLL': - return ; - case 'CREATE_POLL': - return ; - case 'PREVIEW_POLL': - return ; - default: - return <>; - } - } - - return <>{renderSwitch()}; -} diff --git a/template/src/components/polling/context/poll-context.tsx b/template/src/components/polling/context/poll-context.tsx index 62b94f1bf..e47885e6d 100644 --- a/template/src/components/polling/context/poll-context.tsx +++ b/template/src/components/polling/context/poll-context.tsx @@ -1,25 +1,84 @@ -import React, {createContext, useReducer, Dispatch} from 'react'; -import {type PollItem} from './poll-form-context'; +import React, {createContext, useReducer, Dispatch, useState} from 'react'; +import events, {PersistanceLevel} from '../../../rtm-events-api'; + +enum PollAccess { + PUBLIC = 'PUBLIC', +} + +enum PollStatus { + ACTIVE = 'PUBLIC', + FINISHED = 'FINISHED', + LATER = 'LATER', +} + +enum PollKind { + OPEN_ENDED = 'OPEN_ENDED', + MCQ = 'MCQ', + YES_NO = 'YES_NO', +} + +type PollCurrentStep = + | 'START_POLL' + | 'SELECT_POLL' + | 'CREATE_POLL' + | 'PREVIEW_POLL'; + +interface PollItem { + id: string; + type: PollKind; + access: PollAccess; // remove it as poll are not private or public but the response will be public or private + status: PollStatus; + question: string; + answers: + | [ + { + uid: number; + response: string; + timestamp: number; + }, + ] + | null; + options: Array<{ + text: string; + value: string; + votes: [{uid: number; access: PollAccess; timestamp: number}]; + }> | null; + multiple_response: boolean; + share: boolean; + duration: boolean; + timer: number; + createdBy: number; +} interface Poll { - [key: string]: PollItem; + [key: string]: PollItem[]; } enum PollActionKind { - ADD_FINAL_POLL_ITEM = 'ADD_FINAL_POLL_ITEM', + ADD_POLL_ITEM = 'ADD_POLL_ITEM', + LAUNCH_POLL = 'LAUNCH_POLL', } -type PollAction = { - type: PollActionKind.ADD_FINAL_POLL_ITEM; - payload: {item: Poll}; -}; +type PollAction = + | { + type: PollActionKind.ADD_POLL_ITEM; + payload: {item: PollItem}; + } + | { + type: PollActionKind.LAUNCH_POLL; + payload: {pollID: string}; + }; function pollReducer(state: Poll, action: PollAction): Poll { switch (action.type) { - case PollActionKind.ADD_FINAL_POLL_ITEM: { + case PollActionKind.ADD_POLL_ITEM: { + const userUid = action.payload.item.createdBy; + const pollId = action.payload.item.id; return { ...state, - ...action.payload.item, + [userUid]: { + [pollId]: {...action.payload.item}, + }, }; } default: { @@ -31,14 +90,62 @@ function pollReducer(state: Poll, action: PollAction): Poll { interface PollContextValue { polls: Poll; dispatch: Dispatch; + startPollForm: () => void; + savePollItem: (item: PollItem) => void; + currentStep: PollCurrentStep; + setCurrentStep: (item: PollCurrentStep) => void; } const PollContext = createContext(null); PollContext.displayName = 'PollContext'; function PollProvider({children}: {children: React.ReactNode}) { + const [currentStep, setCurrentStep] = useState(null); const [polls, dispatch] = useReducer(pollReducer, {}); - const value = {polls, dispatch}; + console.log('supriya polls state: ', polls); + + const startPollForm = () => { + setCurrentStep('SELECT_POLL'); + }; + + const savePollItem = (item: PollItem) => { + if (item.status === PollStatus.ACTIVE) { + dispatch({ + type: PollActionKind.ADD_POLL_ITEM, + payload: { + item: {...item}, + }, + }); + sendPoll(item); + } else { + dispatch({ + type: PollActionKind.ADD_POLL_ITEM, + payload: { + item: {...item}, + }, + }); + } + }; + + const sendPoll = async (poll: PollItem) => { + // const rtmEngine: RtmEngine = RTMEngine.getInstance().engine; + // const channelId = RTMEngine.getInstance().channelUid; + // const rtmAttribute = {key: 'poll', value: JSON.stringify({...poll})}; + // await rtmEngine.addOrUpdateChannelAttributes(channelId, [rtmAttribute], { + // enableNotificationToChannelMembers: true, + // }); + events.send('polls', JSON.stringify({...poll}), PersistanceLevel.Channel); + }; + + const value = { + polls, + dispatch, + savePollItem, + currentStep, + setCurrentStep, + startPollForm, + }; + return {children}; } @@ -50,4 +157,13 @@ function usePoll() { return context; } -export {PollProvider, usePoll, PollActionKind}; +export { + PollProvider, + usePoll, + PollActionKind, + PollKind, + PollStatus, + PollAccess, +}; + +export type {PollItem, PollCurrentStep}; diff --git a/template/src/components/polling/context/poll-events.tsx b/template/src/components/polling/context/poll-events.tsx new file mode 100644 index 000000000..71687e728 --- /dev/null +++ b/template/src/components/polling/context/poll-events.tsx @@ -0,0 +1,42 @@ +import React, {createContext, useContext, useEffect} from 'react'; +import events, {PersistanceLevel} from '../../../rtm-events-api'; +import {PollItem} from './poll-context'; + +interface PollEventsContextValue { + launchPollEvent: (poll: PollItem) => void; +} + +const PollEventsContext = createContext(null); +PollEventsContext.displayName = 'PollEventsContext'; + +function PollEventsProvider({children}: {children?: React.ReactNode}) { + useEffect(() => { + events.on('polls', data => { + console.log('supriya poll event received data', data); + }); + }, []); + + const launchPollEvent = async (poll: PollItem) => { + events.send('polls', JSON.stringify({...poll}), PersistanceLevel.Channel); + }; + + const value = { + launchPollEvent, + }; + + return ( + + {children} + + ); +} + +function usePollEvents() { + const context = useContext(PollEventsContext); + if (!context) { + throw new Error('usePollEvents must be used within PollEventsProvider.'); + } + return context; +} + +export {usePollEvents, PollEventsProvider}; diff --git a/template/src/components/polling/form-config.ts b/template/src/components/polling/form-config.ts new file mode 100644 index 000000000..3d296b8a2 --- /dev/null +++ b/template/src/components/polling/form-config.ts @@ -0,0 +1,94 @@ +import {nanoid} from 'nanoid'; +import { + PollKind, + PollItem, + PollAccess, + PollStatus, +} from './context/poll-context'; + +const getDefaultPollTimer = (isDurationEnabled: boolean) => { + if (isDurationEnabled) { + return 10000; + } + return -1; +}; + +const initPollForm = (kind: PollKind): PollItem => { + if (kind === PollKind.OPEN_ENDED) { + return { + id: nanoid(4), + type: PollKind.OPEN_ENDED, + access: PollAccess.PUBLIC, + status: PollStatus.LATER, + question: '', + answers: null, + options: null, + multiple_response: false, + share: false, + duration: false, + timer: -1, + createdBy: -1, + }; + } + if (kind === PollKind.MCQ) { + return { + id: nanoid(4), + type: PollKind.MCQ, + access: PollAccess.PUBLIC, + status: PollStatus.LATER, + question: '', + answers: null, + options: [ + { + text: '', + value: '', + votes: null, + }, + { + text: '', + value: '', + votes: null, + }, + { + text: '', + value: '', + votes: null, + }, + ], + multiple_response: true, + share: false, + duration: false, + timer: -1, + createdBy: -1, + }; + } + if (kind === PollKind.YES_NO) { + return { + id: nanoid(4), + type: PollKind.YES_NO, + access: PollAccess.PUBLIC, + status: PollStatus.LATER, + question: '', + answers: null, + options: [ + { + text: 'YES', + value: 'yes', + votes: null, + }, + { + text: 'No', + value: 'no', + votes: null, + }, + ], + multiple_response: false, + share: false, + duration: false, + timer: -1, + createdBy: -1, + }; + } +}; + +export {getDefaultPollTimer, initPollForm}; diff --git a/template/src/components/polling/modal/CreatePollModal.tsx b/template/src/components/polling/modal/CreatePollModal.tsx index 73529258d..3ed923718 100644 --- a/template/src/components/polling/modal/CreatePollModal.tsx +++ b/template/src/components/polling/modal/CreatePollModal.tsx @@ -1,21 +1,13 @@ import {Text, View, StyleSheet, TextInput} from 'react-native'; import React from 'react'; -import { - BaseModal, - BaseModalTitle, - BaseModalContent, - BaseModalActions, -} from './BaseModal'; +import {BaseModalTitle, BaseModalContent, BaseModalActions} from './BaseModal'; import ThemeConfig from '../../../theme'; import LinkButton from '../../../atoms/LinkButton'; import Checkbox from '../../../atoms/Checkbox'; import IconButton from '../../../atoms/IconButton'; import PrimaryButton from '../../../atoms/PrimaryButton'; -import { - PollFormActionKind, - PollKind, - usePollForm, -} from '../context/poll-form-context'; +import {PollCurrentStep, PollItem, PollKind} from '../context/poll-context'; +import {getDefaultPollTimer} from '../form-config'; function FormTitle({title}: {title: string}) { return ( @@ -24,13 +16,92 @@ function FormTitle({title}: {title: string}) { ); } -export default function CreatePollModal({visible}) { - const {state, dispatch} = usePollForm(); - const {form} = state; +interface Props { + form: PollItem; + setForm: React.Dispatch>; + setCurrentStep: React.Dispatch>; +} + +export default function CreatePollModal({ + form, + setForm, + setCurrentStep, +}: Props) { + const handleInputChange = (field: string, value: string | boolean) => { + setForm({ + ...form, + [field]: value, + }); + }; + + const handleCheckboxChange = (field: string, value: boolean) => { + setForm({ + ...form, + [field]: value, + ...(field === 'duration' && { + timer: getDefaultPollTimer(value as boolean), + }), + }); + }; + + const updateFormOption = ( + action: 'update' | 'delete' | 'add', + value: string, + index: number, + ) => { + if (action === 'add') { + setForm({ + ...form, + options: [ + ...form.options, + { + text: '', + value: '', + votes: null, + }, + ], + }); + } + if (action === 'update') { + setForm({ + ...form, + options: form.options.map((option, i) => { + if (i === index) { + const lowerText = value.replace(/\s+/g, '-').toLowerCase(); + return { + ...option, + text: value, + value: lowerText, + votes: null, + }; + } + return option; + }), + }); + } + if (action === 'delete') { + setForm({ + ...form, + options: form.options.filter((option, i) => i !== index), + }); + } + }; + + const getTitle = (type: PollKind) => { + if (type === PollKind.MCQ) { + return 'Multiple Choice'; + } + if (type === PollKind.OPEN_ENDED) { + return 'Open Ended Poll'; + } + if (type === PollKind.YES_NO) { + return 'Yes/No'; + } + }; return ( - - + <> + {/* Question section */} @@ -44,16 +115,7 @@ export default function CreatePollModal({visible}) { numberOfLines={4} value={form.question} onChangeText={text => { - if (text.trim() === '') { - return; - } - dispatch({ - type: PollFormActionKind.UPDATE_FORM_FIELD, - payload: { - field: 'question', - value: text, - }, - }); + handleInputChange('question', text); }} placeholder="Enter poll question here..." placeholderTextColor={ @@ -78,13 +140,7 @@ export default function CreatePollModal({visible}) { style={style.pFormInput} value={option.text} onChangeText={text => { - dispatch({ - type: PollFormActionKind.UPDATE_FORM_OPTION, - payload: { - value: text, - index: index, - }, - }); + updateFormOption('update', text, index); }} placeholder="Add text here..." placeholderTextColor={ @@ -103,12 +159,7 @@ export default function CreatePollModal({visible}) { tintColor: $config.CARD_LAYER_5_COLOR, }} onPress={() => { - dispatch({ - type: PollFormActionKind.DELETE_FORM_OPTION, - payload: { - index: index, - }, - }); + updateFormOption('delete', option.text, index); }} /> @@ -119,9 +170,7 @@ export default function CreatePollModal({visible}) { text="Add option" textStyle={style.pFormOptionLink} onPress={() => { - dispatch({ - type: PollFormActionKind.ADD_FORM_OPTION, - }); + updateFormOption('add', null, null); }} /> @@ -154,18 +203,12 @@ export default function CreatePollModal({visible}) { {form.type === PollKind.MCQ ? ( - dispatch({ - type: PollFormActionKind.UPDATE_FORM_FIELD, - payload: { - field: 'multiple', - value: !form.multiple, - }, - }) - } + onChange={() => { + handleCheckboxChange('multiple', !form.multiple_response); + }} /> ) : ( <> @@ -176,15 +219,9 @@ export default function CreatePollModal({visible}) { checked={form.share} label={'Share results with the respondants'} labelStye={style.pFormOptionText} - onChange={() => - dispatch({ - type: PollFormActionKind.UPDATE_FORM_FIELD, - payload: { - field: 'share', - value: !form.share, - }, - }) - } + onChange={() => { + handleCheckboxChange('share', !form.share); + }} /> @@ -192,15 +229,9 @@ export default function CreatePollModal({visible}) { checked={form.duration} label={'Set Timer Duration'} labelStye={style.pFormOptionText} - onChange={() => - dispatch({ - type: PollFormActionKind.UPDATE_FORM_FIELD, - payload: { - field: 'duration', - value: !form.duration, - }, - }) - } + onChange={() => { + handleCheckboxChange('duration', !form.duration); + }} /> @@ -213,15 +244,13 @@ export default function CreatePollModal({visible}) { containerStyle={style.btnContainer} textStyle={style.btnText} onPress={() => { - dispatch({ - type: PollFormActionKind.PREVIEW_FORM, - }); + setCurrentStep('PREVIEW_POLL'); }} text="Preview" /> - + ); } diff --git a/template/src/components/polling/modal/PollFormModal.tsx b/template/src/components/polling/modal/PollFormModal.tsx new file mode 100644 index 000000000..f565d613e --- /dev/null +++ b/template/src/components/polling/modal/PollFormModal.tsx @@ -0,0 +1,72 @@ +import React, {useEffect, useState} from 'react'; +import {BaseModal} from './BaseModal'; +import SelectNewPollTypeModal from './SelectNewPollTypeModal'; +import CreatePollModal from './CreatePollModal'; +import PollPreviewModal from './PollPreviewModal'; +import {PollItem, PollKind, PollStatus} from '../context/poll-context'; +import {useLocalUid} from '../../../../agora-rn-uikit'; +import {usePoll} from '../context/poll-context'; +import {initPollForm} from '../form-config'; + +export default function PollFormModal() { + const {savePollItem, currentStep, setCurrentStep} = usePoll(); + const [form, setForm] = useState(null); + console.log('supriya poll form: ', form); + const [type, setType] = useState(null); + const localUid = useLocalUid(); + + useEffect(() => { + if (!type) { + return; + } + setForm(initPollForm(type)); + setCurrentStep('CREATE_POLL'); + }, [type, setCurrentStep]); + + const onSave = (launch?: boolean) => { + if (launch) { + savePollItem({ + ...form, + status: PollStatus.ACTIVE, + createdBy: localUid, + }); + } else { + savePollItem({ + ...form, + status: PollStatus.LATER, + createdBy: localUid, + }); + } + setType(null); + setCurrentStep(null); + }; + + const onEdit = () => { + setCurrentStep('CREATE_POLL'); + }; + + function renderSwitch(step) { + switch (step) { + case 'SELECT_POLL': + return ; + case 'CREATE_POLL': + return ( + + ); + case 'PREVIEW_POLL': + return ; + default: + return <>; + } + } + + return ( + + {renderSwitch(currentStep)} + + ); +} diff --git a/template/src/components/polling/modal/PollPreviewModal.tsx b/template/src/components/polling/modal/PollPreviewModal.tsx index 035176eed..2a109b3d8 100644 --- a/template/src/components/polling/modal/PollPreviewModal.tsx +++ b/template/src/components/polling/modal/PollPreviewModal.tsx @@ -1,49 +1,19 @@ import {Text, StyleSheet, View} from 'react-native'; -import React, {useEffect} from 'react'; -import { - BaseModal, - BaseModalTitle, - BaseModalContent, - BaseModalActions, -} from './BaseModal'; +import React from 'react'; +import {BaseModalTitle, BaseModalContent, BaseModalActions} from './BaseModal'; import ThemeConfig from '../../../theme'; import TertiaryButton from '../../../atoms/TertiaryButton'; -import { - PollFormActionKind, - PollStatus, - usePollForm, -} from '../context/poll-form-context'; -import {usePoll} from '../context/poll-context'; -import {PollActionKind} from '../context/poll-context'; -import {useLocalUid} from '../../../../agora-rn-uikit'; +import {PollItem} from '../context/poll-context'; -export default function PollPreviewModal({visible}) { - const {state, dispatch: pollFormDispatch} = usePollForm(); - const {dispatch: pollDispatch} = usePoll(); - const localUid = useLocalUid(); - - const {form} = state; - - const onSave = (launch: boolean) => { - pollDispatch({ - type: PollActionKind.ADD_FINAL_POLL_ITEM, - payload: { - item: { - [localUid]: { - ...form, - status: launch ? PollStatus.LATER : PollStatus.ACTIVE, - createdBy: localUid, - }, - }, - }, - }); - pollFormDispatch({ - type: PollFormActionKind.POLL_FORM_CLOSE, - }); - }; +interface Props { + form: PollItem; + onEdit: () => void; + onSave: (launch: boolean) => void; +} +export default function PollPreviewModal({form, onEdit, onSave}: Props) { return ( - + <> @@ -53,8 +23,8 @@ export default function PollPreviewModal({visible}) { {form.question} {form?.options ? ( - {form.options.map(option => ( - + {form.options.map((option, index) => ( + {option.text} ))} @@ -69,9 +39,7 @@ export default function PollPreviewModal({visible}) { { - pollFormDispatch({ - type: PollFormActionKind.UPDATE_FORM, - }); + onEdit(); }} text="Edit" /> @@ -94,7 +62,7 @@ export default function PollPreviewModal({visible}) { - + ); } diff --git a/template/src/components/polling/modal/SelectNewPollTypeModal.tsx b/template/src/components/polling/modal/SelectNewPollTypeModal.tsx index 7d72c449d..db4937943 100644 --- a/template/src/components/polling/modal/SelectNewPollTypeModal.tsx +++ b/template/src/components/polling/modal/SelectNewPollTypeModal.tsx @@ -1,12 +1,8 @@ import {Text, StyleSheet, View, TouchableOpacity} from 'react-native'; import React from 'react'; -import {BaseModal, BaseModalTitle, BaseModalContent} from './BaseModal'; +import {BaseModalTitle, BaseModalContent} from './BaseModal'; import ThemeConfig from '../../../theme'; -import { - PollFormActionKind, - PollKind, - usePollForm, -} from '../context/poll-form-context'; +import {PollKind} from '../context/poll-context'; interface newPollType { key: PollKind; @@ -36,10 +32,13 @@ const newPollTypeConfig: newPollType[] = [ }, ]; -export default function SelectNewPollTypeModal({visible}) { - const {dispatch} = usePollForm(); +export default function SelectNewPollTypeModal({ + setType, +}: { + setType: React.Dispatch>; +}) { return ( - + <> @@ -48,10 +47,7 @@ export default function SelectNewPollTypeModal({visible}) { style={{outlineStyle: 'none'}} key={item.key} onPress={() => { - dispatch({ - type: PollFormActionKind.SELECT_POLL, - payload: {pollType: item.key}, - }); + setType(item.key); }}> @@ -64,7 +60,7 @@ export default function SelectNewPollTypeModal({visible}) { ))} - + ); } diff --git a/template/src/rtm-events-api/Events.ts b/template/src/rtm-events-api/Events.ts index 6616e577f..90d0cdfa3 100644 --- a/template/src/rtm-events-api/Events.ts +++ b/template/src/rtm-events-api/Events.ts @@ -19,6 +19,7 @@ import { EventCallback, EventSource, PersistanceLevel, + RTMAttributePayload, } from './types'; import {adjustUID} from '../rtm/utils'; import {LogSource, logger} from '../logger/AppBuilderLogger'; @@ -98,7 +99,10 @@ class Events { * @param {ReceiverUid} toUid uid or uids[] of user * @api private */ - private _send = async (rtmPayload: any, toUid?: ReceiverUid) => { + private _send = async ( + rtmPayload: RTMAttributePayload, + toUid?: ReceiverUid, + ) => { const to = typeof toUid == 'string' ? parseInt(toUid) : toUid; const rtmEngine: RtmEngine = RTMEngine.getInstance().engine; @@ -180,6 +184,32 @@ class Events { } }; + private _sendAsChannelAttribute = async (rtmPayload: RTMAttributePayload) => { + // Case 1: send to channel + logger.debug( + LogSource.Events, + 'CUSTOM_EVENTS', + 'updating channel attributes', + ); + try { + const rtmEngine: RtmEngine = RTMEngine.getInstance().engine; + const channelId = RTMEngine.getInstance().channelUid; + const rtmAttribute = [{key: rtmPayload.evt, value: rtmPayload.value}]; + // Step 1: Call RTM API to update local attributes + await rtmEngine.addOrUpdateChannelAttributes(channelId, rtmAttribute, { + enableNotificationToChannelMembers: true, + }); + } catch (error) { + logger.error( + LogSource.Events, + 'CUSTOM_EVENTS', + 'updating channel attributes error', + error, + ); + throw error; + } + }; + /** * Listen on a new event by eventName and listener. * When the specified event happens, the Events API triggers the callback that you pass. @@ -275,7 +305,7 @@ class Events { source: this.source, }); - const rtmPayload = { + const rtmPayload: RTMAttributePayload = { evt: eventName, value: persistValue, }; @@ -297,7 +327,11 @@ class Events { `sending event -> ${eventName}`, persistValue, ); - await this._send(rtmPayload, receiver); + if (persistLevel === PersistanceLevel.Channel) { + await this._sendAsChannelAttribute(rtmPayload); + } else { + await this._send(rtmPayload, receiver); + } } catch (error) { logger.error( LogSource.Events, diff --git a/template/src/rtm-events-api/types.ts b/template/src/rtm-events-api/types.ts index cfb9addab..53b033c7c 100644 --- a/template/src/rtm-events-api/types.ts +++ b/template/src/rtm-events-api/types.ts @@ -12,6 +12,7 @@ export enum PersistanceLevel { 'None' = 1, 'Sender', 'Session', + 'Channel', } interface EventCallbackPayload { payload: string; @@ -19,4 +20,9 @@ interface EventCallbackPayload { sender: UidType; ts: number; } +export interface RTMAttributePayload { + evt: string; + value: string; +} + export type EventCallback = (args: EventCallbackPayload) => void; From 8181650507c9628ebbc36a962bb4d6167be7720d Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Tue, 6 Aug 2024 11:09:53 +0530 Subject: [PATCH 009/174] add poll event rtm layer --- template/src/components/RTMConfigure.tsx | 48 ++++++++++++------- .../polling/context/poll-events.tsx | 3 ++ 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/template/src/components/RTMConfigure.tsx b/template/src/components/RTMConfigure.tsx index 38a82fd59..fde9e3567 100644 --- a/template/src/components/RTMConfigure.tsx +++ b/template/src/components/RTMConfigure.tsx @@ -11,7 +11,7 @@ */ // @ts-nocheck import React, {useState, useContext, useEffect, useRef} from 'react'; -import RtmEngine from 'agora-react-native-rtm'; +import RtmEngine, {RtmChannelAttribute} from 'agora-react-native-rtm'; import { ContentInterface, DispatchContext, @@ -587,22 +587,36 @@ const RtmConfigure = (props: any) => { } }); - engine.current.on('channelAttributesUpdated', (data: any) => { - console.log('supriya 2 channel attributes receivwd', data); - try { - // const {uid, channelId, text, ts} = evt; - // const timestamp = getMessageTime(ts); - // const sender = isAndroid() ? get32BitUid(peerId) : parseInt(peerId); - // eventDispatcher(data, sender, timestamp); - } catch (error) { - logger.error( - LogSource.Events, - 'CUSTOM_EVENTS', - 'error while dispatching through eventDispatcher', - error, - ); - } - }); + engine.current.on( + 'channelAttributesUpdated', + (attributeList: RtmChannelAttribute[]) => { + console.log('supriya 2 channel attributes received', attributeList); + try { + attributeList.map((attribute: RtmChannelAttribute) => { + const {key, value, lastUpdateTs, lastUpdateUserId} = attribute; + const timestamp = getMessageTime(lastUpdateTs); + const sender = Platform.OS + ? get32BitUid(lastUpdateUserId) + : parseInt(lastUpdateUserId); + eventDispatcher( + { + evt: key, + value, + }, + sender, + timestamp, + ); + }); + } catch (error) { + logger.error( + LogSource.Events, + 'CUSTOM_EVENTS', + 'error while dispatching through eventDispatcher', + error, + ); + } + }, + ); await doLoginAndSetupRTM(); }; diff --git a/template/src/components/polling/context/poll-events.tsx b/template/src/components/polling/context/poll-events.tsx index 71687e728..c97fa2174 100644 --- a/template/src/components/polling/context/poll-events.tsx +++ b/template/src/components/polling/context/poll-events.tsx @@ -14,6 +14,9 @@ function PollEventsProvider({children}: {children?: React.ReactNode}) { events.on('polls', data => { console.log('supriya poll event received data', data); }); + return () => { + events.off('polls'); + }; }, []); const launchPollEvent = async (poll: PollItem) => { From e95a0942ee817f3ff409f96d3538a8eda761a162 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Tue, 6 Aug 2024 14:05:34 +0530 Subject: [PATCH 010/174] fix directory structure --- .../components/polling/components/Poll.tsx | 2 +- .../form/CreatePollFormView.tsx} | 22 ++++++++------- .../form/PollPreviewFormView.tsx} | 14 ++++++---- .../form/SelectNewPollTypeFormView.tsx} | 8 +++--- .../{ => components/form}/form-config.ts | 2 +- .../modals}/PollFormModal.tsx | 24 +++++++++-------- .../modals}/SharePollModal.tsx | 6 ++--- .../polling/context/poll-context.tsx | 12 ++------- .../polling/context/poll-events.tsx | 27 ++++++++++++++----- .../polling/{modal => ui}/BaseModal.tsx | 0 10 files changed, 66 insertions(+), 51 deletions(-) rename template/src/components/polling/{modal/CreatePollModal.tsx => components/form/CreatePollFormView.tsx} (95%) rename template/src/components/polling/{modal/PollPreviewModal.tsx => components/form/PollPreviewFormView.tsx} (89%) rename template/src/components/polling/{modal/SelectNewPollTypeModal.tsx => components/form/SelectNewPollTypeFormView.tsx} (92%) rename template/src/components/polling/{ => components/form}/form-config.ts (97%) rename template/src/components/polling/{modal => components/modals}/PollFormModal.tsx (66%) rename template/src/components/polling/{modal => components/modals}/SharePollModal.tsx (96%) rename template/src/components/polling/{modal => ui}/BaseModal.tsx (100%) diff --git a/template/src/components/polling/components/Poll.tsx b/template/src/components/polling/components/Poll.tsx index f1cce43a0..cd3ffc73a 100644 --- a/template/src/components/polling/components/Poll.tsx +++ b/template/src/components/polling/components/Poll.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {PollProvider} from '../context/poll-context'; -import PollFormModal from '../modal/PollFormModal'; +import PollFormModal from './modals/PollFormModal'; import {PollEventsProvider} from '../context/poll-events'; function Poll({children}: {children?: React.ReactNode}) { diff --git a/template/src/components/polling/modal/CreatePollModal.tsx b/template/src/components/polling/components/form/CreatePollFormView.tsx similarity index 95% rename from template/src/components/polling/modal/CreatePollModal.tsx rename to template/src/components/polling/components/form/CreatePollFormView.tsx index 3ed923718..406e2e6c8 100644 --- a/template/src/components/polling/modal/CreatePollModal.tsx +++ b/template/src/components/polling/components/form/CreatePollFormView.tsx @@ -1,13 +1,17 @@ import {Text, View, StyleSheet, TextInput} from 'react-native'; import React from 'react'; -import {BaseModalTitle, BaseModalContent, BaseModalActions} from './BaseModal'; -import ThemeConfig from '../../../theme'; -import LinkButton from '../../../atoms/LinkButton'; -import Checkbox from '../../../atoms/Checkbox'; -import IconButton from '../../../atoms/IconButton'; -import PrimaryButton from '../../../atoms/PrimaryButton'; -import {PollCurrentStep, PollItem, PollKind} from '../context/poll-context'; -import {getDefaultPollTimer} from '../form-config'; +import { + BaseModalTitle, + BaseModalContent, + BaseModalActions, +} from '../../ui/BaseModal'; +import ThemeConfig from '../../../../theme'; +import LinkButton from '../../../../atoms/LinkButton'; +import Checkbox from '../../../../atoms/Checkbox'; +import IconButton from '../../../../atoms/IconButton'; +import PrimaryButton from '../../../../atoms/PrimaryButton'; +import {PollCurrentStep, PollItem, PollKind} from '../../context/poll-context'; +import {getDefaultPollTimer} from './form-config'; function FormTitle({title}: {title: string}) { return ( @@ -22,7 +26,7 @@ interface Props { setCurrentStep: React.Dispatch>; } -export default function CreatePollModal({ +export default function CreatePollFormView({ form, setForm, setCurrentStep, diff --git a/template/src/components/polling/modal/PollPreviewModal.tsx b/template/src/components/polling/components/form/PollPreviewFormView.tsx similarity index 89% rename from template/src/components/polling/modal/PollPreviewModal.tsx rename to template/src/components/polling/components/form/PollPreviewFormView.tsx index 2a109b3d8..0ebc9ca9b 100644 --- a/template/src/components/polling/modal/PollPreviewModal.tsx +++ b/template/src/components/polling/components/form/PollPreviewFormView.tsx @@ -1,9 +1,13 @@ import {Text, StyleSheet, View} from 'react-native'; import React from 'react'; -import {BaseModalTitle, BaseModalContent, BaseModalActions} from './BaseModal'; -import ThemeConfig from '../../../theme'; -import TertiaryButton from '../../../atoms/TertiaryButton'; -import {PollItem} from '../context/poll-context'; +import { + BaseModalTitle, + BaseModalContent, + BaseModalActions, +} from '../../ui/BaseModal'; +import ThemeConfig from '../../../../theme'; +import TertiaryButton from '../../../../atoms/TertiaryButton'; +import {PollItem} from '../../context/poll-context'; interface Props { form: PollItem; @@ -11,7 +15,7 @@ interface Props { onSave: (launch: boolean) => void; } -export default function PollPreviewModal({form, onEdit, onSave}: Props) { +export default function PollPreviewFormView({form, onEdit, onSave}: Props) { return ( <> diff --git a/template/src/components/polling/modal/SelectNewPollTypeModal.tsx b/template/src/components/polling/components/form/SelectNewPollTypeFormView.tsx similarity index 92% rename from template/src/components/polling/modal/SelectNewPollTypeModal.tsx rename to template/src/components/polling/components/form/SelectNewPollTypeFormView.tsx index db4937943..4403122a3 100644 --- a/template/src/components/polling/modal/SelectNewPollTypeModal.tsx +++ b/template/src/components/polling/components/form/SelectNewPollTypeFormView.tsx @@ -1,8 +1,8 @@ import {Text, StyleSheet, View, TouchableOpacity} from 'react-native'; import React from 'react'; -import {BaseModalTitle, BaseModalContent} from './BaseModal'; -import ThemeConfig from '../../../theme'; -import {PollKind} from '../context/poll-context'; +import {BaseModalTitle, BaseModalContent} from '../../ui/BaseModal'; +import ThemeConfig from '../../../../theme'; +import {PollKind} from '../../context/poll-context'; interface newPollType { key: PollKind; @@ -32,7 +32,7 @@ const newPollTypeConfig: newPollType[] = [ }, ]; -export default function SelectNewPollTypeModal({ +export default function SelectNewPollTypeFormView({ setType, }: { setType: React.Dispatch>; diff --git a/template/src/components/polling/form-config.ts b/template/src/components/polling/components/form/form-config.ts similarity index 97% rename from template/src/components/polling/form-config.ts rename to template/src/components/polling/components/form/form-config.ts index 3d296b8a2..0adf4a50d 100644 --- a/template/src/components/polling/form-config.ts +++ b/template/src/components/polling/components/form/form-config.ts @@ -4,7 +4,7 @@ import { PollItem, PollAccess, PollStatus, -} from './context/poll-context'; +} from '../../context/poll-context'; const getDefaultPollTimer = (isDurationEnabled: boolean) => { if (isDurationEnabled) { diff --git a/template/src/components/polling/modal/PollFormModal.tsx b/template/src/components/polling/components/modals/PollFormModal.tsx similarity index 66% rename from template/src/components/polling/modal/PollFormModal.tsx rename to template/src/components/polling/components/modals/PollFormModal.tsx index f565d613e..801df35a7 100644 --- a/template/src/components/polling/modal/PollFormModal.tsx +++ b/template/src/components/polling/components/modals/PollFormModal.tsx @@ -1,12 +1,12 @@ import React, {useEffect, useState} from 'react'; -import {BaseModal} from './BaseModal'; -import SelectNewPollTypeModal from './SelectNewPollTypeModal'; -import CreatePollModal from './CreatePollModal'; -import PollPreviewModal from './PollPreviewModal'; -import {PollItem, PollKind, PollStatus} from '../context/poll-context'; -import {useLocalUid} from '../../../../agora-rn-uikit'; -import {usePoll} from '../context/poll-context'; -import {initPollForm} from '../form-config'; +import {BaseModal} from '../../ui/BaseModal'; +import SelectNewPollTypeFormView from '../form/SelectNewPollTypeFormView'; +import CreatePollFormView from '../form/CreatePollFormView'; +import PollPreviewFormView from '../form/PollPreviewFormView'; +import {PollItem, PollKind, PollStatus} from '../../context/poll-context'; +import {useLocalUid} from '../../../../../agora-rn-uikit/src'; +import {usePoll} from '../../context/poll-context'; +import {initPollForm} from '../form/form-config'; export default function PollFormModal() { const {savePollItem, currentStep, setCurrentStep} = usePoll(); @@ -48,17 +48,19 @@ export default function PollFormModal() { function renderSwitch(step) { switch (step) { case 'SELECT_POLL': - return ; + return ; case 'CREATE_POLL': return ( - ); case 'PREVIEW_POLL': - return ; + return ( + + ); default: return <>; } diff --git a/template/src/components/polling/modal/SharePollModal.tsx b/template/src/components/polling/components/modals/SharePollModal.tsx similarity index 96% rename from template/src/components/polling/modal/SharePollModal.tsx rename to template/src/components/polling/components/modals/SharePollModal.tsx index f962c22b4..8dfe4e823 100644 --- a/template/src/components/polling/modal/SharePollModal.tsx +++ b/template/src/components/polling/components/modals/SharePollModal.tsx @@ -1,8 +1,8 @@ import {Text, StyleSheet, View} from 'react-native'; import React from 'react'; -import {BaseModal, BaseModalTitle, BaseModalContent} from './BaseModal'; -import ThemeConfig from '../../../theme'; -import UserAvatar from '../../../atoms/UserAvatar'; +import {BaseModal, BaseModalTitle, BaseModalContent} from '../../ui/BaseModal'; +import ThemeConfig from '../../../../theme'; +import UserAvatar from '../../../../atoms/UserAvatar'; export default function SharePollModal() { return ( diff --git a/template/src/components/polling/context/poll-context.tsx b/template/src/components/polling/context/poll-context.tsx index e47885e6d..c2d85a82f 100644 --- a/template/src/components/polling/context/poll-context.tsx +++ b/template/src/components/polling/context/poll-context.tsx @@ -1,5 +1,6 @@ import React, {createContext, useReducer, Dispatch, useState} from 'react'; import events, {PersistanceLevel} from '../../../rtm-events-api'; +import {usePollEvents} from './poll-events'; enum PollAccess { PUBLIC = 'PUBLIC', @@ -101,6 +102,7 @@ PollContext.displayName = 'PollContext'; function PollProvider({children}: {children: React.ReactNode}) { const [currentStep, setCurrentStep] = useState(null); + const {sendPoll} = usePollEvents(); const [polls, dispatch] = useReducer(pollReducer, {}); console.log('supriya polls state: ', polls); @@ -127,16 +129,6 @@ function PollProvider({children}: {children: React.ReactNode}) { } }; - const sendPoll = async (poll: PollItem) => { - // const rtmEngine: RtmEngine = RTMEngine.getInstance().engine; - // const channelId = RTMEngine.getInstance().channelUid; - // const rtmAttribute = {key: 'poll', value: JSON.stringify({...poll})}; - // await rtmEngine.addOrUpdateChannelAttributes(channelId, [rtmAttribute], { - // enableNotificationToChannelMembers: true, - // }); - events.send('polls', JSON.stringify({...poll}), PersistanceLevel.Channel); - }; - const value = { polls, dispatch, diff --git a/template/src/components/polling/context/poll-events.tsx b/template/src/components/polling/context/poll-events.tsx index c97fa2174..c40df259a 100644 --- a/template/src/components/polling/context/poll-events.tsx +++ b/template/src/components/polling/context/poll-events.tsx @@ -2,8 +2,13 @@ import React, {createContext, useContext, useEffect} from 'react'; import events, {PersistanceLevel} from '../../../rtm-events-api'; import {PollItem} from './poll-context'; +export enum PollEventsEnum { + POLLS = 'POLLS', + POLL_LAUNCHED = 'POLL_LAUNCHED', +} + interface PollEventsContextValue { - launchPollEvent: (poll: PollItem) => void; + sendPoll: (poll: PollItem) => void; } const PollEventsContext = createContext(null); @@ -11,20 +16,28 @@ PollEventsContext.displayName = 'PollEventsContext'; function PollEventsProvider({children}: {children?: React.ReactNode}) { useEffect(() => { - events.on('polls', data => { - console.log('supriya poll event received data', data); + events.on(PollEventsEnum.POLLS, data => { + console.log('supriya POLLS event received data', data); + }); + events.on(PollEventsEnum.POLL_LAUNCHED, data => { + console.log('supriya POLL_LAUNCHED event received data', data); }); return () => { - events.off('polls'); + events.off(PollEventsEnum.POLLS); + events.off(PollEventsEnum.POLL_LAUNCHED); }; }, []); - const launchPollEvent = async (poll: PollItem) => { - events.send('polls', JSON.stringify({...poll}), PersistanceLevel.Channel); + const sendPoll = async (poll: PollItem) => { + events.send( + PollEventsEnum.POLL_LAUNCHED, + JSON.stringify({...poll}), + PersistanceLevel.Channel, + ); }; const value = { - launchPollEvent, + sendPoll, }; return ( diff --git a/template/src/components/polling/modal/BaseModal.tsx b/template/src/components/polling/ui/BaseModal.tsx similarity index 100% rename from template/src/components/polling/modal/BaseModal.tsx rename to template/src/components/polling/ui/BaseModal.tsx From f483ac411f94e0c657df148e58b304430c3ecde6 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Wed, 7 Aug 2024 17:01:13 +0530 Subject: [PATCH 011/174] add poll rtm changes --- .../components/polling/components/Poll.tsx | 26 ++- .../components/form/CreatePollFormView.tsx | 25 ++- .../components/form/poll-response-forms.tsx | 169 +++++++++++++++++ .../components/modals/PollFormModal.tsx | 78 ++++++-- .../modals/PollResponseFormModal.tsx | 154 ++++++++++++++++ .../polling/context/poll-context.tsx | 172 +++++++++++++----- .../polling/context/poll-events.tsx | 97 +++++++--- 7 files changed, 625 insertions(+), 96 deletions(-) create mode 100644 template/src/components/polling/components/form/poll-response-forms.tsx create mode 100644 template/src/components/polling/components/modals/PollResponseFormModal.tsx diff --git a/template/src/components/polling/components/Poll.tsx b/template/src/components/polling/components/Poll.tsx index cd3ffc73a..fced7d0f1 100644 --- a/template/src/components/polling/components/Poll.tsx +++ b/template/src/components/polling/components/Poll.tsx @@ -1,17 +1,27 @@ import React from 'react'; -import {PollProvider} from '../context/poll-context'; +import {PollProvider, usePoll} from '../context/poll-context'; import PollFormModal from './modals/PollFormModal'; -import {PollEventsProvider} from '../context/poll-events'; +import {PollEventsProvider, PollEventsSubscriber} from '../context/poll-events'; +import PollResponseFormModal from './modals/PollResponseFormModal'; function Poll({children}: {children?: React.ReactNode}) { return ( - - - {children} - - - + + + {children} + + + ); } +function PollModals() { + const {currentStep} = usePoll(); + return ( + <> + + {currentStep === 'RESPONSE_POLL' && } + + ); +} export default Poll; diff --git a/template/src/components/polling/components/form/CreatePollFormView.tsx b/template/src/components/polling/components/form/CreatePollFormView.tsx index 406e2e6c8..0f465e80b 100644 --- a/template/src/components/polling/components/form/CreatePollFormView.tsx +++ b/template/src/components/polling/components/form/CreatePollFormView.tsx @@ -10,7 +10,7 @@ import LinkButton from '../../../../atoms/LinkButton'; import Checkbox from '../../../../atoms/Checkbox'; import IconButton from '../../../../atoms/IconButton'; import PrimaryButton from '../../../../atoms/PrimaryButton'; -import {PollCurrentStep, PollItem, PollKind} from '../../context/poll-context'; +import {PollFormErrors, PollItem, PollKind} from '../../context/poll-context'; import {getDefaultPollTimer} from './form-config'; function FormTitle({title}: {title: string}) { @@ -23,13 +23,15 @@ function FormTitle({title}: {title: string}) { interface Props { form: PollItem; setForm: React.Dispatch>; - setCurrentStep: React.Dispatch>; + onPreview: () => void; + errors: Partial; } export default function CreatePollFormView({ form, setForm, - setCurrentStep, + onPreview, + errors, }: Props) { const handleInputChange = (field: string, value: string | boolean) => { setForm({ @@ -126,6 +128,9 @@ export default function CreatePollFormView({ $config.FONT_COLOR + ThemeConfig.EmphasisPlus.low } /> + {errors?.question && ( + {errors.question.message} + )} {/* Options section */} @@ -178,6 +183,11 @@ export default function CreatePollFormView({ }} /> + {errors?.options && ( + + {errors.options.message} + + )} ) : ( <> @@ -248,7 +258,7 @@ export default function CreatePollFormView({ containerStyle={style.btnContainer} textStyle={style.btnText} onPress={() => { - setCurrentStep('PREVIEW_POLL'); + onPreview(); }} text="Preview" /> @@ -363,4 +373,11 @@ export const style = StyleSheet.create({ fontWeight: '600', textTransform: 'capitalize', }, + errorText: { + color: $config.SEMANTIC_ERROR, + fontSize: ThemeConfig.FontSize.tiny, + fontFamily: ThemeConfig.FontFamily.sansPro, + paddingLeft: 5, + paddingTop: 5, + }, }); diff --git a/template/src/components/polling/components/form/poll-response-forms.tsx b/template/src/components/polling/components/form/poll-response-forms.tsx new file mode 100644 index 000000000..e0385b2da --- /dev/null +++ b/template/src/components/polling/components/form/poll-response-forms.tsx @@ -0,0 +1,169 @@ +import {Text, View, StyleSheet, TextInput} from 'react-native'; +import React, {useState} from 'react'; +import {BaseModalContent, BaseModalActions} from '../../ui/BaseModal'; +import ThemeConfig from '../../../../theme'; +import PrimaryButton from '../../../../atoms/PrimaryButton'; +import {PollItem, usePoll} from '../../context/poll-context'; + +interface Props { + pollItem: PollItem; +} + +function PollResponseQuestionForm({pollItem}: Props) { + console.log('supriya pollItem: ', pollItem); + const [answer, setAnswer] = useState(''); + const {onSubmitPollResponse} = usePoll(); + + return ( + <> + + {'2.45'} + {pollItem.question} + + + + + + + { + onSubmitPollResponse(pollItem, answer); + }} + text="Submit" + /> + + + + ); +} + +export {PollResponseQuestionForm}; +export const style = StyleSheet.create({ + timer: { + color: $config.SEMANTIC_WARNING, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontSize: 16, + lineHeight: 20, + paddingBottom: 12, + }, + questionText: { + color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, + fontSize: ThemeConfig.FontSize.medium, + fontFamily: ThemeConfig.FontFamily.sansPro, + lineHeight: 24, + fontWeight: '600', + }, + pFormTextarea: { + color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, + fontSize: ThemeConfig.FontSize.small, + fontFamily: ThemeConfig.FontFamily.sansPro, + lineHeight: 16, + fontWeight: '400', + borderRadius: 8, + borderWidth: 1, + borderColor: $config.INPUT_FIELD_BORDER_COLOR, + backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, + height: 110, + outlineStyle: 'none', + padding: 16, + }, + responseActions: { + flex: 1, + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + }, + btnContainer: { + minWidth: 150, + height: 36, + borderRadius: 4, + }, + btnText: { + color: $config.FONT_COLOR, + fontSize: ThemeConfig.FontSize.small, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontWeight: '600', + textTransform: 'capitalize', + }, + // pFormOptionText: { + // color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, + // fontSize: ThemeConfig.FontSize.small, + // fontFamily: ThemeConfig.FontFamily.sansPro, + // lineHeight: 16, + // fontWeight: '400', + // }, + // pFormOptionPrefix: { + // color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.low, + // paddingRight: 4, + // }, + // pFormOptionLink: { + // fontWeight: '400', + // lineHeight: 24, + // }, + // pFormOptions: { + // paddingVertical: 8, + // gap: 8, + // }, + pFormInput: { + flex: 1, + color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, + fontSize: ThemeConfig.FontSize.small, + fontFamily: ThemeConfig.FontFamily.sansPro, + lineHeight: 16, + fontWeight: '400', + outlineStyle: 'none', + backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, + borderRadius: 9, + paddingVertical: 12, + }, + // pFormOptionCard: { + // display: 'flex', + // paddingHorizontal: 16, + // flexDirection: 'row', + // justifyContent: 'flex-start', + // alignItems: 'center', + // alignSelf: 'stretch', + // gap: 8, + // backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, + // borderRadius: 9, + // }, + // verticalPadding: { + // paddingVertical: 12, + // }, + // pFormCheckboxContainer: { + // paddingHorizontal: 16, + // paddingVertical: 8, + // }, + // previewActions: { + // flex: 1, + // display: 'flex', + // flexDirection: 'row', + // alignItems: 'center', + // justifyContent: 'flex-end', + // }, + // btnContainer: { + // minWidth: 150, + // height: 36, + // borderRadius: 4, + // }, + // btnText: { + // color: $config.FONT_COLOR, + // fontSize: ThemeConfig.FontSize.small, + // fontFamily: ThemeConfig.FontFamily.sansPro, + // fontWeight: '600', + // textTransform: 'capitalize', + // }, +}); diff --git a/template/src/components/polling/components/modals/PollFormModal.tsx b/template/src/components/polling/components/modals/PollFormModal.tsx index 801df35a7..c3b8e9757 100644 --- a/template/src/components/polling/components/modals/PollFormModal.tsx +++ b/template/src/components/polling/components/modals/PollFormModal.tsx @@ -3,15 +3,26 @@ import {BaseModal} from '../../ui/BaseModal'; import SelectNewPollTypeFormView from '../form/SelectNewPollTypeFormView'; import CreatePollFormView from '../form/CreatePollFormView'; import PollPreviewFormView from '../form/PollPreviewFormView'; -import {PollItem, PollKind, PollStatus} from '../../context/poll-context'; +import { + PollItem, + PollKind, + PollStatus, + PollFormErrors, +} from '../../context/poll-context'; import {useLocalUid} from '../../../../../agora-rn-uikit/src'; import {usePoll} from '../../context/poll-context'; import {initPollForm} from '../form/form-config'; +import {filterObject} from '../../../../utils'; export default function PollFormModal() { - const {savePollItem, currentStep, setCurrentStep} = usePoll(); + const {polls, savePollForm, sendPollForm, currentStep, setCurrentStep} = + usePoll(); const [form, setForm] = useState(null); + const [formErrors, setFormErrors] = useState(null); + + console.log('supriya poll formErrors: ', formErrors); console.log('supriya poll form: ', form); + const [type, setType] = useState(null); const localUid = useLocalUid(); @@ -25,26 +36,64 @@ export default function PollFormModal() { const onSave = (launch?: boolean) => { if (launch) { - savePollItem({ - ...form, - status: PollStatus.ACTIVE, - createdBy: localUid, - }); - } else { - savePollItem({ - ...form, - status: PollStatus.LATER, - createdBy: localUid, - }); + // check if there is an already launched poll + const isAnyPollActive = Object.keys( + filterObject(polls, ([_, v]) => v.status === PollStatus.ACTIVE), + ); + if (isAnyPollActive.length > 0) { + console.error( + 'Cannot publish poll now as there is already one poll active', + ); + return; + } + } + const payload = { + ...form, + status: launch ? PollStatus.ACTIVE : PollStatus.LATER, + createdBy: localUid, + }; + savePollForm(payload); + if (launch) { + sendPollForm(payload); } setType(null); setCurrentStep(null); + setForm(null); }; const onEdit = () => { setCurrentStep('CREATE_POLL'); }; + const onPreview = () => { + if (validateForm()) { + setCurrentStep('PREVIEW_POLL'); + } + }; + + const validateForm = () => { + setFormErrors(null); + if (form.question.trim() === '') { + setFormErrors({ + ...formErrors, + question: {message: 'Cannot be blank'}, + }); + return false; + } + if ( + form.type === PollKind.MCQ && + form.options && + form.options.length === 0 + ) { + setFormErrors({ + ...formErrors, + options: {message: 'Cannot be empty'}, + }); + return false; + } + return true; + }; + function renderSwitch(step) { switch (step) { case 'SELECT_POLL': @@ -54,7 +103,8 @@ export default function PollFormModal() { ); case 'PREVIEW_POLL': diff --git a/template/src/components/polling/components/modals/PollResponseFormModal.tsx b/template/src/components/polling/components/modals/PollResponseFormModal.tsx new file mode 100644 index 000000000..99d91fde5 --- /dev/null +++ b/template/src/components/polling/components/modals/PollResponseFormModal.tsx @@ -0,0 +1,154 @@ +import {Text, StyleSheet, View} from 'react-native'; +import React from 'react'; +import {BaseModal, BaseModalTitle} from '../../ui/BaseModal'; +import ThemeConfig from '../../../../theme'; +import UserAvatar from '../../../../atoms/UserAvatar'; +// import RadioButton from '../../ui/RadioButton'; +import {PollResponseQuestionForm} from '../form/poll-response-forms'; +import {PollKind, usePoll} from '../../context/poll-context'; +import {videoRoomUserFallbackText} from '../../../../language/default-labels/videoCallScreenLabels'; +import {useContent} from 'customization-api'; +import {UidType} from '../../../../../agora-rn-uikit/src'; +import {useString} from '../../../../utils/useString'; + +export default function PollResponseFormModal() { + const {polls, launchPollId} = usePoll(); + const poll = polls[launchPollId]; + + const remoteUserDefaultLabel = useString(videoRoomUserFallbackText)(); + const {defaultContent} = useContent(); + const getPollCreaterName = (uid: UidType) => { + return defaultContent[uid]?.name || remoteUserDefaultLabel; + }; + + function renderSwitch(type: PollKind) { + switch (type) { + case PollKind.OPEN_ENDED: + return ; + // case PollKind.OPEN_ENDED: + // return ( + // + // ); + // case PollKind.YES_NO: + // return ( + // + // ); + default: + return <>; + } + } + + return ( + + + + + + + + + {getPollCreaterName(poll.createdBy)} + + hh:mm pm {poll.type} + + + + {renderSwitch(poll.type)} + + ); +} + +export const style = StyleSheet.create({ + timer: { + color: $config.SEMANTIC_WARNING, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontSize: 16, + lineHeight: 20, + paddingBottom: 12, + }, + shareBox: { + width: 550, + }, + titleCard: { + display: 'flex', + flexDirection: 'row', + gap: 12, + }, + title: { + display: 'flex', + flexDirection: 'column', + gap: 2, + }, + titleAvatar: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + titleAvatarContainer: { + width: 36, + height: 36, + borderRadius: 18, + backgroundColor: $config.VIDEO_AUDIO_TILE_AVATAR_COLOR, + }, + titleAvatarContainerText: { + fontSize: ThemeConfig.FontSize.small, + lineHeight: 16, + fontWeight: '600', + color: $config.VIDEO_AUDIO_TILE_COLOR, + }, + titleText: { + color: $config.FONT_COLOR, + fontSize: ThemeConfig.FontSize.normal, + fontWeight: '700', + lineHeight: 20, + }, + titleSubtext: { + color: $config.FONT_COLOR, + fontSize: ThemeConfig.FontSize.tiny, + fontWeight: '400', + lineHeight: 16, + }, + questionText: { + color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, + fontSize: ThemeConfig.FontSize.medium, + fontFamily: ThemeConfig.FontFamily.sansPro, + lineHeight: 24, + fontWeight: '600', + }, + responseSection: { + backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, + borderRadius: 9, + paddingVertical: 8, + display: 'flex', + flexDirection: 'column', + gap: 4, + marginVertical: 20, + }, + responseCard: { + display: 'flex', + flexDirection: 'column', + gap: 4, + }, + responseCardBody: { + display: 'flex', + flexDirection: 'row', + paddingHorizontal: 16, + paddingVertical: 8, + alignItems: 'center', + }, + responseText: { + color: $config.FONT_COLOR, + fontSize: ThemeConfig.FontSize.normal, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontWeight: '400', + lineHeight: 24, + }, +}); diff --git a/template/src/components/polling/context/poll-context.tsx b/template/src/components/polling/context/poll-context.tsx index c2d85a82f..50cbf060a 100644 --- a/template/src/components/polling/context/poll-context.tsx +++ b/template/src/components/polling/context/poll-context.tsx @@ -1,13 +1,20 @@ -import React, {createContext, useReducer, Dispatch, useState} from 'react'; -import events, {PersistanceLevel} from '../../../rtm-events-api'; +import React, { + createContext, + useReducer, + Dispatch, + useState, + useEffect, +} from 'react'; import {usePollEvents} from './poll-events'; +import {useLocalUid} from '../../../../agora-rn-uikit'; +import {useLiveStreamDataContext} from '../../../components/contexts/LiveStreamDataContext'; enum PollAccess { PUBLIC = 'PUBLIC', } enum PollStatus { - ACTIVE = 'PUBLIC', + ACTIVE = 'ACTIVE', FINISHED = 'FINISHED', LATER = 'LATER', } @@ -22,7 +29,8 @@ type PollCurrentStep = | 'START_POLL' | 'SELECT_POLL' | 'CREATE_POLL' - | 'PREVIEW_POLL'; + | 'PREVIEW_POLL' + | 'RESPONSE_POLL'; interface PollItem { id: string; @@ -31,13 +39,11 @@ interface PollItem { status: PollStatus; question: string; answers: - | [ - { - uid: number; - response: string; - timestamp: number; - }, - ] + | { + uid: number; + response: string; + timestamp: number; + }[] | null; options: Array<{ text: string; @@ -51,37 +57,53 @@ interface PollItem { createdBy: number; } -interface Poll { - [key: string]: PollItem[]; +type Poll = Record; + +interface PollFormErrors { + question?: { + message: string; + }; + options?: { + message: string; + }; } enum PollActionKind { ADD_POLL_ITEM = 'ADD_POLL_ITEM', - LAUNCH_POLL = 'LAUNCH_POLL', + LAUNCH_POLL_ITEM = 'LAUNCH_POLL_ITEM', + SUBMIT_POLL_OPEN_ENDED_RESPONSE = 'SUBMIT_POLL_OPEN_ENDED_RESPONSE', } -type PollAction = - | { - type: PollActionKind.ADD_POLL_ITEM; - payload: {item: PollItem}; - } - | { - type: PollActionKind.LAUNCH_POLL; - payload: {pollID: string}; - }; +type PollAction = { + type: PollActionKind.ADD_POLL_ITEM; + payload: {item: PollItem}; +}; +// | { +// type: PollActionKind.LAUNCH_POLL_ITEM; +// payload: {pollID: string}; +// } +// | { +// type: PollActionKind.SUBMIT_POLL_OPEN_ENDED_RESPONSE; +// payload: { +// pollId: string; +// answerItem: { +// uid: number; +// response: string; +// timestamp: number; +// }; +// }; +// }; function pollReducer(state: Poll, action: PollAction): Poll { switch (action.type) { case PollActionKind.ADD_POLL_ITEM: { - const userUid = action.payload.item.createdBy; const pollId = action.payload.item.id; return { ...state, - [userUid]: { - [pollId]: {...action.payload.item}, - }, + [pollId]: {...action.payload.item}, }; } + default: { return state; } @@ -92,50 +114,106 @@ interface PollContextValue { polls: Poll; dispatch: Dispatch; startPollForm: () => void; - savePollItem: (item: PollItem) => void; + savePollForm: (item: PollItem) => void; + sendPollForm: (item: PollItem) => void; currentStep: PollCurrentStep; setCurrentStep: (item: PollCurrentStep) => void; + launchPollForm: (item: PollItem, launchId: string) => void; + launchPollId: string; + onSubmitPollResponse: (item: PollItem, response: any) => void; } const PollContext = createContext(null); PollContext.displayName = 'PollContext'; function PollProvider({children}: {children: React.ReactNode}) { - const [currentStep, setCurrentStep] = useState(null); - const {sendPoll} = usePollEvents(); const [polls, dispatch] = useReducer(pollReducer, {}); - console.log('supriya polls state: ', polls); + console.log('supriya polls: ', polls); + const [currentStep, setCurrentStep] = useState(null); + const [launchPollId, setLaunchPollId] = useState(null); + const localUid = useLocalUid(); + const {audienceUids, hostUids} = useLiveStreamDataContext(); + + const {sendPollEvt, sendPollResponseEvt} = usePollEvents(); + + useEffect(() => { + if (!launchPollId) { + return; + } + if (launchPollId && polls.hasOwnProperty(launchPollId)) { + setCurrentStep('RESPONSE_POLL'); + } + }, [launchPollId, polls]); const startPollForm = () => { setCurrentStep('SELECT_POLL'); }; - const savePollItem = (item: PollItem) => { + const savePollForm = (item: PollItem) => { + dispatch({ + type: PollActionKind.ADD_POLL_ITEM, + payload: { + item: {...item}, + }, + }); + }; + + const sendPollForm = (item: PollItem) => { if (item.status === PollStatus.ACTIVE) { - dispatch({ - type: PollActionKind.ADD_POLL_ITEM, - payload: { - item: {...item}, - }, - }); - sendPoll(item); + sendPollEvt(item); } else { - dispatch({ - type: PollActionKind.ADD_POLL_ITEM, - payload: { - item: {...item}, - }, + console.error('Poll: Cannot send poll as the status is not active'); + } + }; + + const launchPollForm = (item: PollItem, launchId: string) => { + dispatch({ + type: PollActionKind.ADD_POLL_ITEM, + payload: { + item: {...item}, + }, + }); + if (audienceUids.includes(localUid)) { + setLaunchPollId(launchId); + } + }; + + const onSubmitPollResponse = (item: PollItem, response: any) => { + if (item.type === PollKind.OPEN_ENDED) { + // dispatch({ + // type: PollActionKind.SUBMIT_POLL_OPEN_ENDED_RESPONSE, + // payload: { + // pollId: id, + // answerItem: { + // uid: localUid, + // response, + // timestamp: Date.now(), + // }, + // }, + // }); + sendPollResponseEvt({ + ...item, + answers: [ + ...(Array.isArray(item.answers) ? item.answers : []), + {uid: localUid, response, timestamp: Date.now()}, + ], }); + setCurrentStep(null); + setLaunchPollId(null); } }; const value = { polls, dispatch, - savePollItem, + startPollForm, + savePollForm, + sendPollForm, + launchPollForm, currentStep, setCurrentStep, - startPollForm, + launchPollId, + onSubmitPollResponse, }; return {children}; @@ -158,4 +236,4 @@ export { PollAccess, }; -export type {PollItem, PollCurrentStep}; +export type {PollItem, PollCurrentStep, PollFormErrors}; diff --git a/template/src/components/polling/context/poll-events.tsx b/template/src/components/polling/context/poll-events.tsx index c40df259a..81405f922 100644 --- a/template/src/components/polling/context/poll-events.tsx +++ b/template/src/components/polling/context/poll-events.tsx @@ -1,43 +1,51 @@ import React, {createContext, useContext, useEffect} from 'react'; import events, {PersistanceLevel} from '../../../rtm-events-api'; -import {PollItem} from './poll-context'; +import {PollItem, usePoll} from './poll-context'; -export enum PollEventsEnum { - POLLS = 'POLLS', - POLL_LAUNCHED = 'POLL_LAUNCHED', +const POLL_ATTRIBUTE = 'polls'; +enum PollEventActions { + sendPoll = 'SEND_POLL', + sendPollResponse = 'SEND_POLL_RESPONSE', } interface PollEventsContextValue { - sendPoll: (poll: PollItem) => void; + sendPollEvt: (poll: PollItem) => void; + sendPollResponseEvt: (poll: PollItem) => void; } const PollEventsContext = createContext(null); PollEventsContext.displayName = 'PollEventsContext'; +// Event Dispatcher function PollEventsProvider({children}: {children?: React.ReactNode}) { - useEffect(() => { - events.on(PollEventsEnum.POLLS, data => { - console.log('supriya POLLS event received data', data); - }); - events.on(PollEventsEnum.POLL_LAUNCHED, data => { - console.log('supriya POLL_LAUNCHED event received data', data); - }); - return () => { - events.off(PollEventsEnum.POLLS); - events.off(PollEventsEnum.POLL_LAUNCHED); - }; - }, []); - - const sendPoll = async (poll: PollItem) => { + const sendPollEvt = async (poll: PollItem) => { + console.log('supriya poll to be sent in channel poll: ', poll); events.send( - PollEventsEnum.POLL_LAUNCHED, - JSON.stringify({...poll}), + POLL_ATTRIBUTE, + JSON.stringify({ + action: PollEventActions.sendPoll, + item: {...poll}, + activePollId: poll.id, + }), + PersistanceLevel.Channel, + ); + }; + const sendPollResponseEvt = async (poll: PollItem) => { + console.log('supriya poll to be sent in channel poll: ', poll); + events.send( + POLL_ATTRIBUTE, + JSON.stringify({ + action: PollEventActions.sendPollResponse, + item: {...poll}, + activePollId: poll.id, + }), PersistanceLevel.Channel, ); }; const value = { - sendPoll, + sendPollEvt, + sendPollResponseEvt, }; return ( @@ -55,4 +63,47 @@ function usePollEvents() { return context; } -export {usePollEvents, PollEventsProvider}; +// Event Subscriber +const PollEventsSubscriberContext = createContext(null); +PollEventsSubscriberContext.displayName = 'PollEventsContext'; + +function PollEventsSubscriber({children}: {children?: React.ReactNode}) { + const {savePollForm, launchPollForm} = usePoll(); + + useEffect(() => { + events.on(POLL_ATTRIBUTE, args => { + const {payload, sender, ts} = args; + const data = JSON.parse(payload); + const {action, item, activePollId} = data; + console.log( + 'supriya POLLS event received data', + args, + data, + item, + activePollId, + ); + switch (action) { + case PollEventActions.sendPoll: + launchPollForm(item, activePollId); + break; + case PollEventActions.sendPollResponse: + savePollForm(item); + break; + default: + break; + } + }); + + return () => { + events.off(POLL_ATTRIBUTE); + }; + }, [launchPollForm, savePollForm]); + + return ( + + {children} + + ); +} + +export {usePollEvents, PollEventsProvider, PollEventsSubscriber}; From 6aeb4816a7c1e84c7173986352d2663fbfe68884 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Wed, 7 Aug 2024 17:09:43 +0530 Subject: [PATCH 012/174] redo the code --- .../components/polling/components/Poll.tsx | 6 ++++-- .../polling/context/poll-context.tsx | 21 +------------------ 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/template/src/components/polling/components/Poll.tsx b/template/src/components/polling/components/Poll.tsx index fced7d0f1..94d210070 100644 --- a/template/src/components/polling/components/Poll.tsx +++ b/template/src/components/polling/components/Poll.tsx @@ -16,11 +16,13 @@ function Poll({children}: {children?: React.ReactNode}) { } function PollModals() { - const {currentStep} = usePoll(); + const {currentStep, launchPollId} = usePoll(); return ( <> - {currentStep === 'RESPONSE_POLL' && } + {currentStep === 'RESPONSE_POLL' && launchPollId && ( + + )} ); } diff --git a/template/src/components/polling/context/poll-context.tsx b/template/src/components/polling/context/poll-context.tsx index 50cbf060a..af9402ca4 100644 --- a/template/src/components/polling/context/poll-context.tsx +++ b/template/src/components/polling/context/poll-context.tsx @@ -136,15 +136,6 @@ function PollProvider({children}: {children: React.ReactNode}) { const {sendPollEvt, sendPollResponseEvt} = usePollEvents(); - useEffect(() => { - if (!launchPollId) { - return; - } - if (launchPollId && polls.hasOwnProperty(launchPollId)) { - setCurrentStep('RESPONSE_POLL'); - } - }, [launchPollId, polls]); - const startPollForm = () => { setCurrentStep('SELECT_POLL'); }; @@ -175,22 +166,12 @@ function PollProvider({children}: {children: React.ReactNode}) { }); if (audienceUids.includes(localUid)) { setLaunchPollId(launchId); + setCurrentStep('RESPONSE_POLL'); } }; const onSubmitPollResponse = (item: PollItem, response: any) => { if (item.type === PollKind.OPEN_ENDED) { - // dispatch({ - // type: PollActionKind.SUBMIT_POLL_OPEN_ENDED_RESPONSE, - // payload: { - // pollId: id, - // answerItem: { - // uid: localUid, - // response, - // timestamp: Date.now(), - // }, - // }, - // }); sendPollResponseEvt({ ...item, answers: [ From 4d0b496ff295cfa4fd1977d80bb8df916a60bc4d Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Thu, 8 Aug 2024 15:29:52 +0530 Subject: [PATCH 013/174] add poll timer --- .../polling/components/PollTimer.tsx | 47 +++++++++++++++++++ .../components/form/CreatePollFormView.tsx | 3 -- .../polling/components/form/form-config.ts | 37 +++++++++++---- .../components/form/poll-response-forms.tsx | 4 +- .../polling/context/poll-context.tsx | 15 +++--- .../polling/hook/useCountdownTimer.tsx | 42 +++++++++++++++++ 6 files changed, 126 insertions(+), 22 deletions(-) create mode 100644 template/src/components/polling/components/PollTimer.tsx create mode 100644 template/src/components/polling/hook/useCountdownTimer.tsx diff --git a/template/src/components/polling/components/PollTimer.tsx b/template/src/components/polling/components/PollTimer.tsx new file mode 100644 index 000000000..3864f4a14 --- /dev/null +++ b/template/src/components/polling/components/PollTimer.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import {Text, View, StyleSheet} from 'react-native'; +import {useCountdown} from '../hook/useCountdownTimer'; +import ThemeConfig from '../../../theme'; + +interface Props { + expiresAt: number; +} + +const padZero = (value: number) => { + return value.toString().padStart(2, '0'); +}; + +export default function PollTimer({expiresAt}: Props) { + const [days, hours, minutes, seconds] = useCountdown(expiresAt); + + const getTime = () => { + if (days) { + return `${padZero(days)} : ${padZero(hours)} : ${padZero( + minutes, + )} : ${padZero(seconds)}`; + } + if (hours) { + return `${padZero(hours)} : ${padZero(minutes)} : ${padZero(seconds)}`; + } + if (minutes || seconds) { + return `${padZero(minutes)} : ${padZero(seconds)}`; + } + return '00 : 00'; + }; + + return ( + + {getTime()} + + ); +} + +export const style = StyleSheet.create({ + timer: { + color: $config.SEMANTIC_WARNING, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontSize: 16, + lineHeight: 20, + paddingBottom: 12, + }, +}); diff --git a/template/src/components/polling/components/form/CreatePollFormView.tsx b/template/src/components/polling/components/form/CreatePollFormView.tsx index 0f465e80b..c98238b9e 100644 --- a/template/src/components/polling/components/form/CreatePollFormView.tsx +++ b/template/src/components/polling/components/form/CreatePollFormView.tsx @@ -44,9 +44,6 @@ export default function CreatePollFormView({ setForm({ ...form, [field]: value, - ...(field === 'duration' && { - timer: getDefaultPollTimer(value as boolean), - }), }); }; diff --git a/template/src/components/polling/components/form/form-config.ts b/template/src/components/polling/components/form/form-config.ts index 0adf4a50d..554790770 100644 --- a/template/src/components/polling/components/form/form-config.ts +++ b/template/src/components/polling/components/form/form-config.ts @@ -6,11 +6,12 @@ import { PollStatus, } from '../../context/poll-context'; -const getDefaultPollTimer = (isDurationEnabled: boolean) => { - if (isDurationEnabled) { - return 10000; - } - return -1; +const POLL_DURATION = 10; + +const getPollExpiresAtTime = (interval: number): number => { + const t = new Date(); + const expiresAT = t.setSeconds(t.getSeconds() + interval); + return expiresAT; }; const initPollForm = (kind: PollKind): PollItem => { @@ -26,7 +27,7 @@ const initPollForm = (kind: PollKind): PollItem => { multiple_response: false, share: false, duration: false, - timer: -1, + expiresAt: null, createdBy: -1, }; } @@ -58,7 +59,7 @@ const initPollForm = (kind: PollKind): PollItem => { multiple_response: true, share: false, duration: false, - timer: -1, + expiresAt: null, createdBy: -1, }; } @@ -85,10 +86,28 @@ const initPollForm = (kind: PollKind): PollItem => { multiple_response: false, share: false, duration: false, - timer: -1, + expiresAt: null, createdBy: -1, }; } }; -export {getDefaultPollTimer, initPollForm}; +const getAttributeLengthInKb = (attribute: string): string => { + const b = attribute.length * 2; + const kb = (b / 1024).toFixed(2); + return kb; +}; + +const isAttributeLengthValid = (attribute: string) => { + if (getAttributeLengthInKb(attribute) > '8') { + return false; + } + return true; +}; + +export { + getPollExpiresAtTime, + initPollForm, + isAttributeLengthValid, + POLL_DURATION, +}; diff --git a/template/src/components/polling/components/form/poll-response-forms.tsx b/template/src/components/polling/components/form/poll-response-forms.tsx index e0385b2da..9b82e0c0f 100644 --- a/template/src/components/polling/components/form/poll-response-forms.tsx +++ b/template/src/components/polling/components/form/poll-response-forms.tsx @@ -4,20 +4,20 @@ import {BaseModalContent, BaseModalActions} from '../../ui/BaseModal'; import ThemeConfig from '../../../../theme'; import PrimaryButton from '../../../../atoms/PrimaryButton'; import {PollItem, usePoll} from '../../context/poll-context'; +import PollTimer from '../PollTimer'; interface Props { pollItem: PollItem; } function PollResponseQuestionForm({pollItem}: Props) { - console.log('supriya pollItem: ', pollItem); const [answer, setAnswer] = useState(''); const {onSubmitPollResponse} = usePoll(); return ( <> - {'2.45'} + {pollItem.question} { if (item.status === PollStatus.ACTIVE) { + item.expiresAt = getPollExpiresAtTime(POLL_DURATION); sendPollEvt(item); } else { console.error('Poll: Cannot send poll as the status is not active'); diff --git a/template/src/components/polling/hook/useCountdownTimer.tsx b/template/src/components/polling/hook/useCountdownTimer.tsx new file mode 100644 index 000000000..13bc5ab6a --- /dev/null +++ b/template/src/components/polling/hook/useCountdownTimer.tsx @@ -0,0 +1,42 @@ +import {useEffect, useState, useRef} from 'react'; + +const useCountdown = (targetDate: number) => { + const countDownDate = new Date(targetDate).getTime(); + const intervalRef = useRef(null); // Add a ref to store the interval id + + const [countDown, setCountDown] = useState( + countDownDate - new Date().getTime(), + ); + + useEffect(() => { + intervalRef.current = setInterval(() => { + setCountDown(countDownDate - new Date().getTime()); + }, 1000); + + return () => clearInterval(intervalRef.current); + }, [countDownDate]); + + useEffect(() => { + const time = getReturnValues(countDown); + const isAllZero = time.every(item => item === 0); + if (isAllZero) { + clearInterval(intervalRef.current); + } + }, [countDown]); + + return getReturnValues(countDown); +}; + +const getReturnValues = countDown => { + // calculate time left + const days = Math.floor(countDown / (1000 * 60 * 60 * 24)); + const hours = Math.floor( + (countDown % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60), + ); + const minutes = Math.floor((countDown % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((countDown % (1000 * 60)) / 1000); + + return [days, hours, minutes, seconds]; +}; + +export {useCountdown}; From f1fc666298a9f30bb35d1acff34fb827d9931873 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Thu, 8 Aug 2024 23:32:13 +0530 Subject: [PATCH 014/174] add timer --- .../components/polling/components/Poll.tsx | 6 +- ...PollFormView.tsx => DraftPollFormView.tsx} | 3 +- ...ewFormView.tsx => PreviewPollFormView.tsx} | 2 +- .../polling/components/form/form-config.ts | 2 +- .../components/form/poll-response-forms.tsx | 132 +++++++++++++-- ...lFormModal.tsx => PollFormWizardModal.tsx} | 46 +++-- .../modals/PollResponseFormModal.tsx | 158 +++--------------- .../polling/context/poll-context.tsx | 71 ++++---- .../polling/context/poll-events.tsx | 16 +- .../src/components/polling/ui/BaseModal.tsx | 47 +++--- 10 files changed, 241 insertions(+), 242 deletions(-) rename template/src/components/polling/components/form/{CreatePollFormView.tsx => DraftPollFormView.tsx} (99%) rename template/src/components/polling/components/form/{PollPreviewFormView.tsx => PreviewPollFormView.tsx} (97%) rename template/src/components/polling/components/modals/{PollFormModal.tsx => PollFormWizardModal.tsx} (72%) diff --git a/template/src/components/polling/components/Poll.tsx b/template/src/components/polling/components/Poll.tsx index 94d210070..0e3b4d72a 100644 --- a/template/src/components/polling/components/Poll.tsx +++ b/template/src/components/polling/components/Poll.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {PollProvider, usePoll} from '../context/poll-context'; -import PollFormModal from './modals/PollFormModal'; +import PollFormWizardModal from './modals/PollFormWizardModal'; import {PollEventsProvider, PollEventsSubscriber} from '../context/poll-events'; import PollResponseFormModal from './modals/PollResponseFormModal'; @@ -19,8 +19,8 @@ function PollModals() { const {currentStep, launchPollId} = usePoll(); return ( <> - - {currentStep === 'RESPONSE_POLL' && launchPollId && ( + {currentStep === 'CREATE_POLL' && } + {currentStep === 'RESPOND_TO_POLL' && launchPollId && ( )} diff --git a/template/src/components/polling/components/form/CreatePollFormView.tsx b/template/src/components/polling/components/form/DraftPollFormView.tsx similarity index 99% rename from template/src/components/polling/components/form/CreatePollFormView.tsx rename to template/src/components/polling/components/form/DraftPollFormView.tsx index c98238b9e..ef7fe4830 100644 --- a/template/src/components/polling/components/form/CreatePollFormView.tsx +++ b/template/src/components/polling/components/form/DraftPollFormView.tsx @@ -11,7 +11,6 @@ import Checkbox from '../../../../atoms/Checkbox'; import IconButton from '../../../../atoms/IconButton'; import PrimaryButton from '../../../../atoms/PrimaryButton'; import {PollFormErrors, PollItem, PollKind} from '../../context/poll-context'; -import {getDefaultPollTimer} from './form-config'; function FormTitle({title}: {title: string}) { return ( @@ -27,7 +26,7 @@ interface Props { errors: Partial; } -export default function CreatePollFormView({ +export default function DraftPollFormView({ form, setForm, onPreview, diff --git a/template/src/components/polling/components/form/PollPreviewFormView.tsx b/template/src/components/polling/components/form/PreviewPollFormView.tsx similarity index 97% rename from template/src/components/polling/components/form/PollPreviewFormView.tsx rename to template/src/components/polling/components/form/PreviewPollFormView.tsx index 0ebc9ca9b..be04cd1bb 100644 --- a/template/src/components/polling/components/form/PollPreviewFormView.tsx +++ b/template/src/components/polling/components/form/PreviewPollFormView.tsx @@ -15,7 +15,7 @@ interface Props { onSave: (launch: boolean) => void; } -export default function PollPreviewFormView({form, onEdit, onSave}: Props) { +export default function PreviewPollFormView({form, onEdit, onSave}: Props) { return ( <> diff --git a/template/src/components/polling/components/form/form-config.ts b/template/src/components/polling/components/form/form-config.ts index 554790770..f95bb6c89 100644 --- a/template/src/components/polling/components/form/form-config.ts +++ b/template/src/components/polling/components/form/form-config.ts @@ -6,7 +6,7 @@ import { PollStatus, } from '../../context/poll-context'; -const POLL_DURATION = 10; +const POLL_DURATION = 600; // takes seconds const getPollExpiresAtTime = (interval: number): number => { const t = new Date(); diff --git a/template/src/components/polling/components/form/poll-response-forms.tsx b/template/src/components/polling/components/form/poll-response-forms.tsx index 9b82e0c0f..442f0a7af 100644 --- a/template/src/components/polling/components/form/poll-response-forms.tsx +++ b/template/src/components/polling/components/form/poll-response-forms.tsx @@ -5,12 +5,72 @@ import ThemeConfig from '../../../../theme'; import PrimaryButton from '../../../../atoms/PrimaryButton'; import {PollItem, usePoll} from '../../context/poll-context'; import PollTimer from '../PollTimer'; +import {videoRoomUserFallbackText} from '../../../../language/default-labels/videoCallScreenLabels'; +import {useContent} from 'customization-api'; +import {UidType} from '../../../../../agora-rn-uikit/src'; +import {useString} from '../../../../utils/useString'; +import UserAvatar from '../../../../atoms/UserAvatar'; +import ImageIcon from '../../../../atoms/ImageIcon'; interface Props { pollItem: PollItem; } -function PollResponseQuestionForm({pollItem}: Props) { +function PollResponseFormModalTitle({pollItem}: Props) { + const remoteUserDefaultLabel = useString(videoRoomUserFallbackText)(); + const {defaultContent} = useContent(); + const getPollCreaterName = (uid: UidType) => { + return defaultContent[uid]?.name || remoteUserDefaultLabel; + }; + + return ( + + + + + + + {getPollCreaterName(pollItem.createdBy)} + + {pollItem.type} + + + ); +} + +function PollResponseFormComplete() { + return ( + + + + + + + Thank you for your response + + + + ); +} + +interface PollResponseFormProps { + pollItem: PollItem; + onComplete: () => void; +} + +function PollResponseQuestionForm({ + pollItem, + onComplete, +}: PollResponseFormProps) { const [answer, setAnswer] = useState(''); const {onSubmitPollResponse} = usePoll(); @@ -18,7 +78,7 @@ function PollResponseQuestionForm({pollItem}: Props) { <> - {pollItem.question} + {pollItem.question} { - onSubmitPollResponse(pollItem, answer); + try { + onSubmitPollResponse(pollItem, answer); + onComplete(); + } catch (error) { + console.error('error: ', error); + } }} text="Submit" /> @@ -50,16 +115,53 @@ function PollResponseQuestionForm({pollItem}: Props) { ); } -export {PollResponseQuestionForm}; +export { + PollResponseQuestionForm, + PollResponseFormModalTitle, + PollResponseFormComplete, +}; + export const style = StyleSheet.create({ - timer: { - color: $config.SEMANTIC_WARNING, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontSize: 16, + titleCard: { + display: 'flex', + flexDirection: 'row', + gap: 12, + }, + title: { + display: 'flex', + flexDirection: 'column', + gap: 2, + }, + titleAvatar: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + titleAvatarContainer: { + width: 36, + height: 36, + borderRadius: 18, + backgroundColor: $config.VIDEO_AUDIO_TILE_AVATAR_COLOR, + }, + titleAvatarContainerText: { + fontSize: ThemeConfig.FontSize.small, + lineHeight: 16, + fontWeight: '600', + color: $config.VIDEO_AUDIO_TILE_COLOR, + }, + titleText: { + color: $config.FONT_COLOR, + fontSize: ThemeConfig.FontSize.normal, + fontWeight: '700', lineHeight: 20, - paddingBottom: 12, }, - questionText: { + titleSubtext: { + color: $config.FONT_COLOR, + fontSize: ThemeConfig.FontSize.tiny, + fontWeight: '400', + lineHeight: 16, + }, + heading4: { color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, fontSize: ThemeConfig.FontSize.medium, fontFamily: ThemeConfig.FontFamily.sansPro, @@ -129,6 +231,16 @@ export const style = StyleSheet.create({ borderRadius: 9, paddingVertical: 12, }, + centerAlign: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + gap: 12, + }, + mediumHeight: { + height: 272, + }, // pFormOptionCard: { // display: 'flex', // paddingHorizontal: 16, diff --git a/template/src/components/polling/components/modals/PollFormModal.tsx b/template/src/components/polling/components/modals/PollFormWizardModal.tsx similarity index 72% rename from template/src/components/polling/components/modals/PollFormModal.tsx rename to template/src/components/polling/components/modals/PollFormWizardModal.tsx index c3b8e9757..ac3fb80a9 100644 --- a/template/src/components/polling/components/modals/PollFormModal.tsx +++ b/template/src/components/polling/components/modals/PollFormWizardModal.tsx @@ -1,8 +1,8 @@ import React, {useEffect, useState} from 'react'; import {BaseModal} from '../../ui/BaseModal'; import SelectNewPollTypeFormView from '../form/SelectNewPollTypeFormView'; -import CreatePollFormView from '../form/CreatePollFormView'; -import PollPreviewFormView from '../form/PollPreviewFormView'; +import DraftPollFormView from '../form/DraftPollFormView'; +import PreviewPollFormView from '../form/PreviewPollFormView'; import { PollItem, PollKind, @@ -14,16 +14,15 @@ import {usePoll} from '../../context/poll-context'; import {initPollForm} from '../form/form-config'; import {filterObject} from '../../../../utils'; -export default function PollFormModal() { - const {polls, savePollForm, sendPollForm, currentStep, setCurrentStep} = - usePoll(); +type FormWizardStep = 'SELECT' | 'DRAFT' | 'PREVIEW'; + +export default function PollFormWizardModal() { + const {polls, savePollForm, sendPollForm} = usePoll(); + const [step, setStep] = useState('SELECT'); + const [type, setType] = useState(null); const [form, setForm] = useState(null); const [formErrors, setFormErrors] = useState(null); - console.log('supriya poll formErrors: ', formErrors); - console.log('supriya poll form: ', form); - - const [type, setType] = useState(null); const localUid = useLocalUid(); useEffect(() => { @@ -31,8 +30,8 @@ export default function PollFormModal() { return; } setForm(initPollForm(type)); - setCurrentStep('CREATE_POLL'); - }, [type, setCurrentStep]); + setStep('DRAFT'); + }, [type]); const onSave = (launch?: boolean) => { if (launch) { @@ -56,18 +55,15 @@ export default function PollFormModal() { if (launch) { sendPollForm(payload); } - setType(null); - setCurrentStep(null); - setForm(null); }; const onEdit = () => { - setCurrentStep('CREATE_POLL'); + setStep('DRAFT'); }; const onPreview = () => { if (validateForm()) { - setCurrentStep('PREVIEW_POLL'); + setStep('PREVIEW'); } }; @@ -94,31 +90,27 @@ export default function PollFormModal() { return true; }; - function renderSwitch(step) { + function renderSwitch() { switch (step) { - case 'SELECT_POLL': + case 'SELECT': return ; - case 'CREATE_POLL': + case 'DRAFT': return ( - ); - case 'PREVIEW_POLL': + case 'PREVIEW': return ( - + ); default: return <>; } } - return ( - - {renderSwitch(currentStep)} - - ); + return {renderSwitch()}; } diff --git a/template/src/components/polling/components/modals/PollResponseFormModal.tsx b/template/src/components/polling/components/modals/PollResponseFormModal.tsx index 99d91fde5..7f3552f81 100644 --- a/template/src/components/polling/components/modals/PollResponseFormModal.tsx +++ b/template/src/components/polling/components/modals/PollResponseFormModal.tsx @@ -1,42 +1,35 @@ -import {Text, StyleSheet, View} from 'react-native'; -import React from 'react'; +import React, {useState} from 'react'; import {BaseModal, BaseModalTitle} from '../../ui/BaseModal'; -import ThemeConfig from '../../../../theme'; -import UserAvatar from '../../../../atoms/UserAvatar'; -// import RadioButton from '../../ui/RadioButton'; -import {PollResponseQuestionForm} from '../form/poll-response-forms'; +import { + PollResponseFormComplete, + PollResponseFormModalTitle, + PollResponseQuestionForm, +} from '../form/poll-response-forms'; import {PollKind, usePoll} from '../../context/poll-context'; -import {videoRoomUserFallbackText} from '../../../../language/default-labels/videoCallScreenLabels'; -import {useContent} from 'customization-api'; -import {UidType} from '../../../../../agora-rn-uikit/src'; -import {useString} from '../../../../utils/useString'; export default function PollResponseFormModal() { - const {polls, launchPollId} = usePoll(); - const poll = polls[launchPollId]; + const {polls, launchPollId, goToShareResponseModal} = usePoll(); + const [hasResponded, setHasResponded] = useState(false); + const pollItem = polls[launchPollId]; - const remoteUserDefaultLabel = useString(videoRoomUserFallbackText)(); - const {defaultContent} = useContent(); - const getPollCreaterName = (uid: UidType) => { - return defaultContent[uid]?.name || remoteUserDefaultLabel; + const onFormComplete = () => { + if (pollItem.share) { + goToShareResponseModal(); + } else { + setHasResponded(true); + } }; - function renderSwitch(type: PollKind) { + function renderForm(type: PollKind) { switch (type) { case PollKind.OPEN_ENDED: - return ; - // case PollKind.OPEN_ENDED: - // return ( - // - // ); - // case PollKind.YES_NO: - // return ( - // - // ); + return ( + + ); + default: return <>; } @@ -45,110 +38,9 @@ export default function PollResponseFormModal() { return ( - - - - - - - {getPollCreaterName(poll.createdBy)} - - hh:mm pm {poll.type} - - + - {renderSwitch(poll.type)} + {hasResponded ? : renderForm(pollItem.type)} ); } - -export const style = StyleSheet.create({ - timer: { - color: $config.SEMANTIC_WARNING, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontSize: 16, - lineHeight: 20, - paddingBottom: 12, - }, - shareBox: { - width: 550, - }, - titleCard: { - display: 'flex', - flexDirection: 'row', - gap: 12, - }, - title: { - display: 'flex', - flexDirection: 'column', - gap: 2, - }, - titleAvatar: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - }, - titleAvatarContainer: { - width: 36, - height: 36, - borderRadius: 18, - backgroundColor: $config.VIDEO_AUDIO_TILE_AVATAR_COLOR, - }, - titleAvatarContainerText: { - fontSize: ThemeConfig.FontSize.small, - lineHeight: 16, - fontWeight: '600', - color: $config.VIDEO_AUDIO_TILE_COLOR, - }, - titleText: { - color: $config.FONT_COLOR, - fontSize: ThemeConfig.FontSize.normal, - fontWeight: '700', - lineHeight: 20, - }, - titleSubtext: { - color: $config.FONT_COLOR, - fontSize: ThemeConfig.FontSize.tiny, - fontWeight: '400', - lineHeight: 16, - }, - questionText: { - color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, - fontSize: ThemeConfig.FontSize.medium, - fontFamily: ThemeConfig.FontFamily.sansPro, - lineHeight: 24, - fontWeight: '600', - }, - responseSection: { - backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, - borderRadius: 9, - paddingVertical: 8, - display: 'flex', - flexDirection: 'column', - gap: 4, - marginVertical: 20, - }, - responseCard: { - display: 'flex', - flexDirection: 'column', - gap: 4, - }, - responseCardBody: { - display: 'flex', - flexDirection: 'row', - paddingHorizontal: 16, - paddingVertical: 8, - alignItems: 'center', - }, - responseText: { - color: $config.FONT_COLOR, - fontSize: ThemeConfig.FontSize.normal, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontWeight: '400', - lineHeight: 24, - }, -}); diff --git a/template/src/components/polling/context/poll-context.tsx b/template/src/components/polling/context/poll-context.tsx index 68aa62111..3a7408130 100644 --- a/template/src/components/polling/context/poll-context.tsx +++ b/template/src/components/polling/context/poll-context.tsx @@ -23,12 +23,7 @@ enum PollKind { YES_NO = 'YES_NO', } -type PollCurrentStep = - | 'START_POLL' - | 'SELECT_POLL' - | 'CREATE_POLL' - | 'PREVIEW_POLL' - | 'RESPONSE_POLL'; +type PollCurrentStep = 'CREATE_POLL' | 'RESPOND_TO_POLL' | 'SHARE_POLL'; interface PollItem { id: string; @@ -67,19 +62,21 @@ interface PollFormErrors { } enum PollActionKind { - ADD_POLL_ITEM = 'ADD_POLL_ITEM', + ADD_OR_UPDATE_POLL_ITEM = 'ADD_OR_UPDATE_POLL_ITEM', LAUNCH_POLL_ITEM = 'LAUNCH_POLL_ITEM', SUBMIT_POLL_OPEN_ENDED_RESPONSE = 'SUBMIT_POLL_OPEN_ENDED_RESPONSE', + START_POLL_TIMER = 'START_POLL_TIMER', } -type PollAction = { - type: PollActionKind.ADD_POLL_ITEM; - payload: {item: PollItem}; -}; -// | { -// type: PollActionKind.LAUNCH_POLL_ITEM; -// payload: {pollID: string}; -// } +type PollAction = + | { + type: PollActionKind.ADD_OR_UPDATE_POLL_ITEM; + payload: {item: PollItem; pollId?: string}; + } + | { + type: PollActionKind.START_POLL_TIMER; + payload: {item: PollItem}; + }; // | { // type: PollActionKind.SUBMIT_POLL_OPEN_ENDED_RESPONSE; // payload: { @@ -94,14 +91,13 @@ type PollAction = { function pollReducer(state: Poll, action: PollAction): Poll { switch (action.type) { - case PollActionKind.ADD_POLL_ITEM: { + case PollActionKind.ADD_OR_UPDATE_POLL_ITEM: { const pollId = action.payload.item.id; return { ...state, [pollId]: {...action.payload.item}, }; } - default: { return state; } @@ -115,10 +111,10 @@ interface PollContextValue { savePollForm: (item: PollItem) => void; sendPollForm: (item: PollItem) => void; currentStep: PollCurrentStep; - setCurrentStep: (item: PollCurrentStep) => void; launchPollForm: (item: PollItem, launchId: string) => void; launchPollId: string; onSubmitPollResponse: (item: PollItem, response: any) => void; + goToShareResponseModal: () => void; } const PollContext = createContext(null); @@ -126,7 +122,6 @@ PollContext.displayName = 'PollContext'; function PollProvider({children}: {children: React.ReactNode}) { const [polls, dispatch] = useReducer(pollReducer, {}); - console.log('supriya polls: ', polls); const [currentStep, setCurrentStep] = useState(null); const [launchPollId, setLaunchPollId] = useState(null); const localUid = useLocalUid(); @@ -135,37 +130,29 @@ function PollProvider({children}: {children: React.ReactNode}) { const {sendPollEvt, sendPollResponseEvt} = usePollEvents(); const startPollForm = () => { - setCurrentStep('SELECT_POLL'); + setCurrentStep('CREATE_POLL'); }; const savePollForm = (item: PollItem) => { - dispatch({ - type: PollActionKind.ADD_POLL_ITEM, - payload: { - item: {...item}, - }, - }); + addOrUpdatePollItem(item); + setCurrentStep(null); }; const sendPollForm = (item: PollItem) => { if (item.status === PollStatus.ACTIVE) { item.expiresAt = getPollExpiresAtTime(POLL_DURATION); sendPollEvt(item); + setCurrentStep(null); } else { console.error('Poll: Cannot send poll as the status is not active'); } }; const launchPollForm = (item: PollItem, launchId: string) => { - dispatch({ - type: PollActionKind.ADD_POLL_ITEM, - payload: { - item: {...item}, - }, - }); + addOrUpdatePollItem(item); if (audienceUids.includes(localUid)) { setLaunchPollId(launchId); - setCurrentStep('RESPONSE_POLL'); + setCurrentStep('RESPOND_TO_POLL'); } }; @@ -178,11 +165,23 @@ function PollProvider({children}: {children: React.ReactNode}) { {uid: localUid, response, timestamp: Date.now()}, ], }); - setCurrentStep(null); - setLaunchPollId(null); + // setLaunchPollId(null); } }; + const addOrUpdatePollItem = (item: PollItem) => { + dispatch({ + type: PollActionKind.ADD_OR_UPDATE_POLL_ITEM, + payload: { + item: {...item}, + }, + }); + }; + + const goToShareResponseModal = () => { + setCurrentStep('SHARE_POLL'); + }; + const value = { polls, dispatch, @@ -191,9 +190,9 @@ function PollProvider({children}: {children: React.ReactNode}) { sendPollForm, launchPollForm, currentStep, - setCurrentStep, launchPollId, onSubmitPollResponse, + goToShareResponseModal, }; return {children}; diff --git a/template/src/components/polling/context/poll-events.tsx b/template/src/components/polling/context/poll-events.tsx index 81405f922..4e9803072 100644 --- a/template/src/components/polling/context/poll-events.tsx +++ b/template/src/components/polling/context/poll-events.tsx @@ -19,7 +19,6 @@ PollEventsContext.displayName = 'PollEventsContext'; // Event Dispatcher function PollEventsProvider({children}: {children?: React.ReactNode}) { const sendPollEvt = async (poll: PollItem) => { - console.log('supriya poll to be sent in channel poll: ', poll); events.send( POLL_ATTRIBUTE, JSON.stringify({ @@ -31,7 +30,6 @@ function PollEventsProvider({children}: {children?: React.ReactNode}) { ); }; const sendPollResponseEvt = async (poll: PollItem) => { - console.log('supriya poll to be sent in channel poll: ', poll); events.send( POLL_ATTRIBUTE, JSON.stringify({ @@ -75,13 +73,13 @@ function PollEventsSubscriber({children}: {children?: React.ReactNode}) { const {payload, sender, ts} = args; const data = JSON.parse(payload); const {action, item, activePollId} = data; - console.log( - 'supriya POLLS event received data', - args, - data, - item, - activePollId, - ); + // console.log( + // 'supriya POLLS event received data', + // args, + // data, + // item, + // activePollId, + // ); switch (action) { case PollEventActions.sendPoll: launchPollForm(item, activePollId); diff --git a/template/src/components/polling/ui/BaseModal.tsx b/template/src/components/polling/ui/BaseModal.tsx index 84c74e1bb..f4596a7f7 100644 --- a/template/src/components/polling/ui/BaseModal.tsx +++ b/template/src/components/polling/ui/BaseModal.tsx @@ -7,11 +7,10 @@ import {isMobileUA} from '../../../utils/common'; interface TitleProps { title?: string; - close?: boolean; children?: ReactNode | ReactNode[]; } -function BaseModalTitle({title, close = false, children}: TitleProps) { +function BaseModalTitle({title, children}: TitleProps) { return ( {title && ( @@ -20,23 +19,6 @@ function BaseModalTitle({title, close = false, children}: TitleProps) { )} {children} - {close && ( - - { - //set close - }} - /> - - )} ); } @@ -73,7 +55,32 @@ const BaseModal = ({children, visible = false}: BaseModalProps) => { ); }; -export {BaseModal, BaseModalTitle, BaseModalContent, BaseModalActions}; +type BaseModalCloseIconProps = { + onClose: () => void; +}; + +const BaseModalCloseIcon = ({onClose}: BaseModalCloseIconProps) => { + + + ; +}; +export { + BaseModal, + BaseModalTitle, + BaseModalContent, + BaseModalActions, + BaseModalCloseIcon, +}; const style = StyleSheet.create({ baseModalBackDrop: { From 4f7f58d834fe1b601f03444c8c763125eb3f20a2 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Thu, 8 Aug 2024 23:42:12 +0530 Subject: [PATCH 015/174] improve variable names --- .../components/modals/PollFormWizardModal.tsx | 6 +++--- .../polling/context/poll-context.tsx | 20 +++++++++---------- .../polling/context/poll-events.tsx | 15 ++++---------- 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/template/src/components/polling/components/modals/PollFormWizardModal.tsx b/template/src/components/polling/components/modals/PollFormWizardModal.tsx index ac3fb80a9..71fa59a99 100644 --- a/template/src/components/polling/components/modals/PollFormWizardModal.tsx +++ b/template/src/components/polling/components/modals/PollFormWizardModal.tsx @@ -17,7 +17,7 @@ import {filterObject} from '../../../../utils'; type FormWizardStep = 'SELECT' | 'DRAFT' | 'PREVIEW'; export default function PollFormWizardModal() { - const {polls, savePollForm, sendPollForm} = usePoll(); + const {polls, savePoll, sendPoll} = usePoll(); const [step, setStep] = useState('SELECT'); const [type, setType] = useState(null); const [form, setForm] = useState(null); @@ -51,9 +51,9 @@ export default function PollFormWizardModal() { status: launch ? PollStatus.ACTIVE : PollStatus.LATER, createdBy: localUid, }; - savePollForm(payload); + savePoll(payload); if (launch) { - sendPollForm(payload); + sendPoll(payload); } }; diff --git a/template/src/components/polling/context/poll-context.tsx b/template/src/components/polling/context/poll-context.tsx index 3a7408130..6e4b3ac4e 100644 --- a/template/src/components/polling/context/poll-context.tsx +++ b/template/src/components/polling/context/poll-context.tsx @@ -106,12 +106,12 @@ function pollReducer(state: Poll, action: PollAction): Poll { interface PollContextValue { polls: Poll; + currentStep: PollCurrentStep; dispatch: Dispatch; startPollForm: () => void; - savePollForm: (item: PollItem) => void; - sendPollForm: (item: PollItem) => void; - currentStep: PollCurrentStep; - launchPollForm: (item: PollItem, launchId: string) => void; + savePoll: (item: PollItem) => void; + sendPoll: (item: PollItem) => void; + onPollReceived: (item: PollItem, launchId: string) => void; launchPollId: string; onSubmitPollResponse: (item: PollItem, response: any) => void; goToShareResponseModal: () => void; @@ -133,12 +133,12 @@ function PollProvider({children}: {children: React.ReactNode}) { setCurrentStep('CREATE_POLL'); }; - const savePollForm = (item: PollItem) => { + const savePoll = (item: PollItem) => { addOrUpdatePollItem(item); setCurrentStep(null); }; - const sendPollForm = (item: PollItem) => { + const sendPoll = (item: PollItem) => { if (item.status === PollStatus.ACTIVE) { item.expiresAt = getPollExpiresAtTime(POLL_DURATION); sendPollEvt(item); @@ -148,7 +148,7 @@ function PollProvider({children}: {children: React.ReactNode}) { } }; - const launchPollForm = (item: PollItem, launchId: string) => { + const onPollReceived = (item: PollItem, launchId: string) => { addOrUpdatePollItem(item); if (audienceUids.includes(localUid)) { setLaunchPollId(launchId); @@ -186,9 +186,9 @@ function PollProvider({children}: {children: React.ReactNode}) { polls, dispatch, startPollForm, - savePollForm, - sendPollForm, - launchPollForm, + savePoll, + sendPoll, + onPollReceived, currentStep, launchPollId, onSubmitPollResponse, diff --git a/template/src/components/polling/context/poll-events.tsx b/template/src/components/polling/context/poll-events.tsx index 4e9803072..a2ef7dc42 100644 --- a/template/src/components/polling/context/poll-events.tsx +++ b/template/src/components/polling/context/poll-events.tsx @@ -66,26 +66,19 @@ const PollEventsSubscriberContext = createContext(null); PollEventsSubscriberContext.displayName = 'PollEventsContext'; function PollEventsSubscriber({children}: {children?: React.ReactNode}) { - const {savePollForm, launchPollForm} = usePoll(); + const {savePoll, onPollReceived} = usePoll(); useEffect(() => { events.on(POLL_ATTRIBUTE, args => { const {payload, sender, ts} = args; const data = JSON.parse(payload); const {action, item, activePollId} = data; - // console.log( - // 'supriya POLLS event received data', - // args, - // data, - // item, - // activePollId, - // ); switch (action) { case PollEventActions.sendPoll: - launchPollForm(item, activePollId); + onPollReceived(item, activePollId); break; case PollEventActions.sendPollResponse: - savePollForm(item); + savePoll(item); break; default: break; @@ -95,7 +88,7 @@ function PollEventsSubscriber({children}: {children?: React.ReactNode}) { return () => { events.off(POLL_ATTRIBUTE); }; - }, [launchPollForm, savePollForm]); + }, [onPollReceived, savePoll]); return ( From fa9a5d9af962bf420b9648d4c33740f2eba624c0 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Thu, 8 Aug 2024 23:48:57 +0530 Subject: [PATCH 016/174] improve variable names --- .../src/components/polling/context/poll-context.tsx | 4 ++-- .../src/components/polling/context/poll-events.tsx | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/template/src/components/polling/context/poll-context.tsx b/template/src/components/polling/context/poll-context.tsx index 6e4b3ac4e..05697c9cc 100644 --- a/template/src/components/polling/context/poll-context.tsx +++ b/template/src/components/polling/context/poll-context.tsx @@ -127,7 +127,7 @@ function PollProvider({children}: {children: React.ReactNode}) { const localUid = useLocalUid(); const {audienceUids, hostUids} = useLiveStreamDataContext(); - const {sendPollEvt, sendPollResponseEvt} = usePollEvents(); + const {sendPollEvt, sendResponseToPollEvt} = usePollEvents(); const startPollForm = () => { setCurrentStep('CREATE_POLL'); @@ -158,7 +158,7 @@ function PollProvider({children}: {children: React.ReactNode}) { const onSubmitPollResponse = (item: PollItem, response: any) => { if (item.type === PollKind.OPEN_ENDED) { - sendPollResponseEvt({ + sendResponseToPollEvt({ ...item, answers: [ ...(Array.isArray(item.answers) ? item.answers : []), diff --git a/template/src/components/polling/context/poll-events.tsx b/template/src/components/polling/context/poll-events.tsx index a2ef7dc42..c90fb920f 100644 --- a/template/src/components/polling/context/poll-events.tsx +++ b/template/src/components/polling/context/poll-events.tsx @@ -5,12 +5,12 @@ import {PollItem, usePoll} from './poll-context'; const POLL_ATTRIBUTE = 'polls'; enum PollEventActions { sendPoll = 'SEND_POLL', - sendPollResponse = 'SEND_POLL_RESPONSE', + sendResponseToPoll = 'SEND_RESONSE_TO_POLL', } interface PollEventsContextValue { sendPollEvt: (poll: PollItem) => void; - sendPollResponseEvt: (poll: PollItem) => void; + sendResponseToPollEvt: (poll: PollItem) => void; } const PollEventsContext = createContext(null); @@ -29,11 +29,11 @@ function PollEventsProvider({children}: {children?: React.ReactNode}) { PersistanceLevel.Channel, ); }; - const sendPollResponseEvt = async (poll: PollItem) => { + const sendResponseToPollEvt = async (poll: PollItem) => { events.send( POLL_ATTRIBUTE, JSON.stringify({ - action: PollEventActions.sendPollResponse, + action: PollEventActions.sendResponseToPoll, item: {...poll}, activePollId: poll.id, }), @@ -43,7 +43,7 @@ function PollEventsProvider({children}: {children?: React.ReactNode}) { const value = { sendPollEvt, - sendPollResponseEvt, + sendResponseToPollEvt, }; return ( @@ -77,7 +77,7 @@ function PollEventsSubscriber({children}: {children?: React.ReactNode}) { case PollEventActions.sendPoll: onPollReceived(item, activePollId); break; - case PollEventActions.sendPollResponse: + case PollEventActions.sendResponseToPoll: savePoll(item); break; default: From 878ff82e2aa0ebfd3be81f671b4a5bd1117a0823 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Sat, 10 Aug 2024 00:39:01 +0530 Subject: [PATCH 017/174] add response mcq --- .../components/polling/components/Poll.tsx | 2 + .../polling/components/PollTimer.tsx | 12 +- .../components/form/DraftPollFormView.tsx | 14 +- .../components/form/PreviewPollFormView.tsx | 37 +++- .../form/SelectNewPollTypeFormView.tsx | 3 +- .../polling/components/form/form-config.ts | 10 +- .../components/form/poll-response-forms.tsx | 130 +++++++++++++- .../modals/PollResponseFormModal.tsx | 26 ++- .../polling/context/poll-context.tsx | 158 ++++++++++++++---- .../polling/context/poll-events.tsx | 51 ++++-- .../polling/hook/useCountdownTimer.tsx | 25 +-- .../src/components/polling/ui/RadioButton.tsx | 78 +++++++++ 12 files changed, 451 insertions(+), 95 deletions(-) create mode 100644 template/src/components/polling/ui/RadioButton.tsx diff --git a/template/src/components/polling/components/Poll.tsx b/template/src/components/polling/components/Poll.tsx index 0e3b4d72a..030f9b773 100644 --- a/template/src/components/polling/components/Poll.tsx +++ b/template/src/components/polling/components/Poll.tsx @@ -3,6 +3,7 @@ import {PollProvider, usePoll} from '../context/poll-context'; import PollFormWizardModal from './modals/PollFormWizardModal'; import {PollEventsProvider, PollEventsSubscriber} from '../context/poll-events'; import PollResponseFormModal from './modals/PollResponseFormModal'; +import SharePollModal from './modals/SharePollModal'; function Poll({children}: {children?: React.ReactNode}) { return ( @@ -23,6 +24,7 @@ function PollModals() { {currentStep === 'RESPOND_TO_POLL' && launchPollId && ( )} + {currentStep === 'SHARE_POLL' && } ); } diff --git a/template/src/components/polling/components/PollTimer.tsx b/template/src/components/polling/components/PollTimer.tsx index 3864f4a14..babe3aea2 100644 --- a/template/src/components/polling/components/PollTimer.tsx +++ b/template/src/components/polling/components/PollTimer.tsx @@ -1,17 +1,18 @@ -import React from 'react'; +import React, {useEffect} from 'react'; import {Text, View, StyleSheet} from 'react-native'; import {useCountdown} from '../hook/useCountdownTimer'; import ThemeConfig from '../../../theme'; interface Props { expiresAt: number; + setFreezeForm?: React.Dispatch>; } const padZero = (value: number) => { return value.toString().padStart(2, '0'); }; -export default function PollTimer({expiresAt}: Props) { +export default function PollTimer({expiresAt, setFreezeForm}: Props) { const [days, hours, minutes, seconds] = useCountdown(expiresAt); const getTime = () => { @@ -29,6 +30,12 @@ export default function PollTimer({expiresAt}: Props) { return '00 : 00'; }; + useEffect(() => { + if (days + hours + minutes + seconds === 0) { + setFreezeForm(true); + } + }, [days, hours, minutes, seconds, setFreezeForm]); + return ( {getTime()} @@ -42,6 +49,5 @@ export const style = StyleSheet.create({ fontFamily: ThemeConfig.FontFamily.sansPro, fontSize: 16, lineHeight: 20, - paddingBottom: 12, }, }); diff --git a/template/src/components/polling/components/form/DraftPollFormView.tsx b/template/src/components/polling/components/form/DraftPollFormView.tsx index ef7fe4830..06c8ebbf5 100644 --- a/template/src/components/polling/components/form/DraftPollFormView.tsx +++ b/template/src/components/polling/components/form/DraftPollFormView.tsx @@ -11,6 +11,7 @@ import Checkbox from '../../../../atoms/Checkbox'; import IconButton from '../../../../atoms/IconButton'; import PrimaryButton from '../../../../atoms/PrimaryButton'; import {PollFormErrors, PollItem, PollKind} from '../../context/poll-context'; +import {nanoid} from 'nanoid'; function FormTitle({title}: {title: string}) { return ( @@ -39,7 +40,7 @@ export default function DraftPollFormView({ }); }; - const handleCheckboxChange = (field: string, value: boolean) => { + const handleCheckboxChange = (field: keyof typeof form, value: boolean) => { setForm({ ...form, [field]: value, @@ -69,7 +70,11 @@ export default function DraftPollFormView({ ...form, options: form.options.map((option, i) => { if (i === index) { - const lowerText = value.replace(/\s+/g, '-').toLowerCase(); + const lowerText = value + .replace(/\s+/g, '-') + .toLowerCase() + .concat('-') + .concat(nanoid(2)); return { ...option, text: value, @@ -217,7 +222,10 @@ export default function DraftPollFormView({ label={'Allow mutiple selections'} labelStye={style.pFormOptionText} onChange={() => { - handleCheckboxChange('multiple', !form.multiple_response); + handleCheckboxChange( + 'multiple_response', + !form.multiple_response, + ); }} /> ) : ( diff --git a/template/src/components/polling/components/form/PreviewPollFormView.tsx b/template/src/components/polling/components/form/PreviewPollFormView.tsx index be04cd1bb..da79d0c90 100644 --- a/template/src/components/polling/components/form/PreviewPollFormView.tsx +++ b/template/src/components/polling/components/form/PreviewPollFormView.tsx @@ -8,6 +8,9 @@ import { import ThemeConfig from '../../../../theme'; import TertiaryButton from '../../../../atoms/TertiaryButton'; import {PollItem} from '../../context/poll-context'; +import {POLL_DURATION} from './form-config'; +import RadioButton from '../../ui/RadioButton'; +import Checkbox from '../../../../atoms/Checkbox'; interface Props { form: PollItem; @@ -22,16 +25,38 @@ export default function PreviewPollFormView({form, onEdit, onSave}: Props) { {form.duration && ( - {form.timer} + {POLL_DURATION} )} {form.question} {form?.options ? ( - {form.options.map((option, index) => ( - - {option.text} - - ))} + {form.multiple_response + ? form.options.map((option, index) => ( + + {}} + /> + + )) + : form.options.map((option, index) => ( + + {}} + /> + + ))} ) : ( <> diff --git a/template/src/components/polling/components/form/SelectNewPollTypeFormView.tsx b/template/src/components/polling/components/form/SelectNewPollTypeFormView.tsx index 4403122a3..d37fa3131 100644 --- a/template/src/components/polling/components/form/SelectNewPollTypeFormView.tsx +++ b/template/src/components/polling/components/form/SelectNewPollTypeFormView.tsx @@ -44,7 +44,8 @@ export default function SelectNewPollTypeFormView({ {newPollTypeConfig.map((item: newPollType) => ( { setType(item.key); diff --git a/template/src/components/polling/components/form/form-config.ts b/template/src/components/polling/components/form/form-config.ts index f95bb6c89..3cd941002 100644 --- a/template/src/components/polling/components/form/form-config.ts +++ b/template/src/components/polling/components/form/form-config.ts @@ -43,17 +43,17 @@ const initPollForm = (kind: PollKind): PollItem => { { text: '', value: '', - votes: null, + votes: [], }, { text: '', value: '', - votes: null, + votes: [], }, { text: '', value: '', - votes: null, + votes: [], }, ], multiple_response: true, @@ -75,12 +75,12 @@ const initPollForm = (kind: PollKind): PollItem => { { text: 'YES', value: 'yes', - votes: null, + votes: [], }, { text: 'No', value: 'no', - votes: null, + votes: [], }, ], multiple_response: false, diff --git a/template/src/components/polling/components/form/poll-response-forms.tsx b/template/src/components/polling/components/form/poll-response-forms.tsx index 442f0a7af..0dd75a618 100644 --- a/template/src/components/polling/components/form/poll-response-forms.tsx +++ b/template/src/components/polling/components/form/poll-response-forms.tsx @@ -3,7 +3,7 @@ import React, {useState} from 'react'; import {BaseModalContent, BaseModalActions} from '../../ui/BaseModal'; import ThemeConfig from '../../../../theme'; import PrimaryButton from '../../../../atoms/PrimaryButton'; -import {PollItem, usePoll} from '../../context/poll-context'; +import {PollItem} from '../../context/poll-context'; import PollTimer from '../PollTimer'; import {videoRoomUserFallbackText} from '../../../../language/default-labels/videoCallScreenLabels'; import {useContent} from 'customization-api'; @@ -11,6 +11,9 @@ import {UidType} from '../../../../../agora-rn-uikit/src'; import {useString} from '../../../../utils/useString'; import UserAvatar from '../../../../atoms/UserAvatar'; import ImageIcon from '../../../../atoms/ImageIcon'; +import Checkbox from '../../../../atoms/Checkbox'; +import RadioButton from '../../ui/RadioButton'; +import Spacer from '../../../../atoms/Spacer'; interface Props { pollItem: PollItem; @@ -64,7 +67,7 @@ function PollResponseFormComplete() { interface PollResponseFormProps { pollItem: PollItem; - onComplete: () => void; + onComplete: (responses: string | string[]) => void; } function PollResponseQuestionForm({ @@ -72,15 +75,19 @@ function PollResponseQuestionForm({ onComplete, }: PollResponseFormProps) { const [answer, setAnswer] = useState(''); - const {onSubmitPollResponse} = usePoll(); + const [isFormFreezed, setFreezeForm] = useState(false); return ( <> - + {pollItem.question} { - try { - onSubmitPollResponse(pollItem, answer); - onComplete(); - } catch (error) { - console.error('error: ', error); + if (!answer || answer.trim() === '') { + return; } + onComplete(answer); }} text="Submit" /> @@ -115,8 +121,91 @@ function PollResponseQuestionForm({ ); } +function PollResponseMCQForm({pollItem, onComplete}: PollResponseFormProps) { + const [isFormFreezed, setFreezeForm] = useState(false); + const [selectedOption, setSelectedOption] = useState(null); + const [selectedOptions, setSelectedOptions] = useState([]); + + const handleCheckboxToggle = (value: string) => { + setSelectedOptions(prevSelectedOptions => { + if (prevSelectedOptions.includes(value)) { + return prevSelectedOptions.filter(option => option !== value); + } else { + return [...prevSelectedOptions, value]; + } + }); + }; + + const handleRadioSelect = (option: string) => { + setSelectedOption(option); + }; + + const handleSubmit = () => { + if (selectedOptions.length === 0 || !selectedOption) { + return; + } + if (pollItem.multiple_response) { + onComplete(selectedOption); + } else { + onComplete(selectedOptions); + } + }; + + return ( + + + + + {pollItem.question} + + + + {pollItem.multiple_response + ? pollItem.options.map((option, index) => ( + + handleCheckboxToggle(option.value)} + /> + + )) + : pollItem.options.map((option, index) => ( + + + + ))} + + + + + + + ); +} + export { PollResponseQuestionForm, + PollResponseMCQForm, PollResponseFormModalTitle, PollResponseFormComplete, }; @@ -200,6 +289,29 @@ export const style = StyleSheet.create({ fontWeight: '600', textTransform: 'capitalize', }, + optionsSection: { + backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, + borderRadius: 9, + marginBottom: 32, + display: 'flex', + flexDirection: 'column', + gap: 4, + paddingVertical: 8, + }, + optionCard: { + display: 'flex', + flexDirection: 'row', + paddingHorizontal: 16, + paddingVertical: 8, + alignItems: 'center', + }, + optionCardText: { + color: $config.FONT_COLOR, + fontSize: ThemeConfig.FontSize.normal, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontWeight: '400', + lineHeight: 24, + }, // pFormOptionText: { // color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, // fontSize: ThemeConfig.FontSize.small, diff --git a/template/src/components/polling/components/modals/PollResponseFormModal.tsx b/template/src/components/polling/components/modals/PollResponseFormModal.tsx index 7f3552f81..a9f978f3e 100644 --- a/template/src/components/polling/components/modals/PollResponseFormModal.tsx +++ b/template/src/components/polling/components/modals/PollResponseFormModal.tsx @@ -4,20 +4,25 @@ import { PollResponseFormComplete, PollResponseFormModalTitle, PollResponseQuestionForm, + PollResponseMCQForm, } from '../form/poll-response-forms'; import {PollKind, usePoll} from '../../context/poll-context'; export default function PollResponseFormModal() { - const {polls, launchPollId, goToShareResponseModal} = usePoll(); + const {polls, launchPollId, sendResponseToPoll} = usePoll(); const [hasResponded, setHasResponded] = useState(false); + const pollItem = polls[launchPollId]; - const onFormComplete = () => { - if (pollItem.share) { - goToShareResponseModal(); - } else { - setHasResponded(true); - } + const onFormComplete = (responses: string | string[]) => { + // console.log('response: ', response); + // sendResponseToPoll(pollItem, response); + // if (pollItem.share) { + // // goToShareResponseModal(); + // } else { + // setHasResponded(true); + // } + // } }; function renderForm(type: PollKind) { @@ -29,6 +34,13 @@ export default function PollResponseFormModal() { onComplete={onFormComplete} /> ); + case PollKind.MCQ: + return ( + + ); default: return <>; diff --git a/template/src/components/polling/context/poll-context.tsx b/template/src/components/polling/context/poll-context.tsx index 05697c9cc..5da4b3efb 100644 --- a/template/src/components/polling/context/poll-context.tsx +++ b/template/src/components/polling/context/poll-context.tsx @@ -25,24 +25,23 @@ enum PollKind { type PollCurrentStep = 'CREATE_POLL' | 'RESPOND_TO_POLL' | 'SHARE_POLL'; +interface PollItemOptionItem { + text: string; + value: string; + votes: Array<{uid: number; access: PollAccess; timestamp: number}> | []; +} interface PollItem { id: string; type: PollKind; access: PollAccess; // remove it as poll are not private or public but the response will be public or private status: PollStatus; question: string; - answers: - | { - uid: number; - response: string; - timestamp: number; - }[] - | null; - options: Array<{ - text: string; - value: string; - votes: [{uid: number; access: PollAccess; timestamp: number}]; + answers: Array<{ + uid: number; + response: string; + timestamp: number; }> | null; + options: Array | null; multiple_response: boolean; share: boolean; duration: boolean; @@ -62,7 +61,8 @@ interface PollFormErrors { } enum PollActionKind { - ADD_OR_UPDATE_POLL_ITEM = 'ADD_OR_UPDATE_POLL_ITEM', + ADD_POLL_ITEM = 'ADD_POLL_ITEM', + UPDATE_POLL_ITEM_RESPONSES = 'UPDATE_POLL_ITEM_RESPONSES', LAUNCH_POLL_ITEM = 'LAUNCH_POLL_ITEM', SUBMIT_POLL_OPEN_ENDED_RESPONSE = 'SUBMIT_POLL_OPEN_ENDED_RESPONSE', START_POLL_TIMER = 'START_POLL_TIMER', @@ -70,12 +70,22 @@ enum PollActionKind { type PollAction = | { - type: PollActionKind.ADD_OR_UPDATE_POLL_ITEM; - payload: {item: PollItem; pollId?: string}; + type: PollActionKind.ADD_POLL_ITEM; + payload: {item: PollItem}; } | { type: PollActionKind.START_POLL_TIMER; payload: {item: PollItem}; + } + | { + type: PollActionKind.UPDATE_POLL_ITEM_RESPONSES; + payload: { + id: string; + type: PollKind; + responses: string | string[]; + uid: number; + timestamp: number; + }; }; // | { // type: PollActionKind.SUBMIT_POLL_OPEN_ENDED_RESPONSE; @@ -90,14 +100,69 @@ type PollAction = // }; function pollReducer(state: Poll, action: PollAction): Poll { + function addVote( + responses: string[], + options: PollItemOptionItem[], + ): PollItemOptionItem[] { + return options.map((option: PollItemOptionItem) => { + // Count how many times the value appears in the strings array + const exists = responses.findIndex(str => str === option.value); + if (exists > 0) { + // Return a new object with an updated votes array + return { + ...option, + votes: [ + ...option.votes, + { + uid: 123, + access: PollAccess.PUBLIC, + timestamp: Date.now(), + }, + ], + }; + } + // If no matches, return the option as is + return option; + }); + } switch (action.type) { - case PollActionKind.ADD_OR_UPDATE_POLL_ITEM: { + case PollActionKind.ADD_POLL_ITEM: { const pollId = action.payload.item.id; return { ...state, [pollId]: {...action.payload.item}, }; } + case PollActionKind.UPDATE_POLL_ITEM_RESPONSES: + { + const {id: pollId, uid, responses, type, timestamp} = action.payload; + if (type === PollKind.OPEN_ENDED && typeof responses === 'string') { + return { + ...state, + [pollId]: { + ...state[pollId], + answers: [ + ...state[pollId].answers, + { + uid, + response: responses, + timestamp, + }, + ], + }, + }; + } + if (type === PollKind.MCQ && Array.isArray(responses)) { + return { + ...state, + [pollId]: { + ...state[pollId], + options: addVote(responses, state[pollId].options), + }, + }; + } + } + break; default: { return state; } @@ -112,8 +177,15 @@ interface PollContextValue { savePoll: (item: PollItem) => void; sendPoll: (item: PollItem) => void; onPollReceived: (item: PollItem, launchId: string) => void; + onPollResponseReceived: ( + id: string, + type: PollKind, + responses: string | string[], + sender: number, + ts: number, + ) => void; launchPollId: string; - onSubmitPollResponse: (item: PollItem, response: any) => void; + sendResponseToPoll: (item: PollItem, responses: string | string[]) => void; goToShareResponseModal: () => void; } @@ -125,7 +197,7 @@ function PollProvider({children}: {children: React.ReactNode}) { const [currentStep, setCurrentStep] = useState(null); const [launchPollId, setLaunchPollId] = useState(null); const localUid = useLocalUid(); - const {audienceUids, hostUids} = useLiveStreamDataContext(); + const {audienceUids} = useLiveStreamDataContext(); const {sendPollEvt, sendResponseToPollEvt} = usePollEvents(); @@ -134,7 +206,7 @@ function PollProvider({children}: {children: React.ReactNode}) { }; const savePoll = (item: PollItem) => { - addOrUpdatePollItem(item); + addPollItem(item); setCurrentStep(null); }; @@ -149,29 +221,48 @@ function PollProvider({children}: {children: React.ReactNode}) { }; const onPollReceived = (item: PollItem, launchId: string) => { - addOrUpdatePollItem(item); + addPollItem(item); if (audienceUids.includes(localUid)) { setLaunchPollId(launchId); setCurrentStep('RESPOND_TO_POLL'); } }; - const onSubmitPollResponse = (item: PollItem, response: any) => { - if (item.type === PollKind.OPEN_ENDED) { - sendResponseToPollEvt({ - ...item, - answers: [ - ...(Array.isArray(item.answers) ? item.answers : []), - {uid: localUid, response, timestamp: Date.now()}, - ], - }); - // setLaunchPollId(null); + const sendResponseToPoll = (item: PollItem, responses: string | string[]) => { + if ( + (item.type === PollKind.OPEN_ENDED && typeof responses === 'string') || + (item.type === PollKind.MCQ && Array.isArray(responses)) + ) { + sendResponseToPollEvt(item, responses); + } else { + throw new Error( + 'sendResponseToPoll received incorrect type response. Unable to send poll response', + ); } }; - const addOrUpdatePollItem = (item: PollItem) => { + const onPollResponseReceived = ( + id: string, + type: PollKind, + responses: string | string[], + sender: number, + ts: number, + ) => { + dispatch({ + type: PollActionKind.UPDATE_POLL_ITEM_RESPONSES, + payload: { + id, + type, + responses, + uid: sender, + timestamp: ts, + }, + }); + }; + + const addPollItem = (item: PollItem) => { dispatch({ - type: PollActionKind.ADD_OR_UPDATE_POLL_ITEM, + type: PollActionKind.ADD_POLL_ITEM, payload: { item: {...item}, }, @@ -189,9 +280,10 @@ function PollProvider({children}: {children: React.ReactNode}) { savePoll, sendPoll, onPollReceived, + onPollResponseReceived, currentStep, launchPollId, - onSubmitPollResponse, + sendResponseToPoll, goToShareResponseModal, }; @@ -215,4 +307,4 @@ export { PollAccess, }; -export type {PollItem, PollCurrentStep, PollFormErrors}; +export type {PollItem, PollCurrentStep, PollFormErrors, PollItemOptionItem}; diff --git a/template/src/components/polling/context/poll-events.tsx b/template/src/components/polling/context/poll-events.tsx index c90fb920f..36291057a 100644 --- a/template/src/components/polling/context/poll-events.tsx +++ b/template/src/components/polling/context/poll-events.tsx @@ -2,15 +2,22 @@ import React, {createContext, useContext, useEffect} from 'react'; import events, {PersistanceLevel} from '../../../rtm-events-api'; import {PollItem, usePoll} from './poll-context'; -const POLL_ATTRIBUTE = 'polls'; +enum PollEventNames { + polls = 'POLLS', + pollResponse = 'POLL_RESPONSE', +} enum PollEventActions { sendPoll = 'SEND_POLL', sendResponseToPoll = 'SEND_RESONSE_TO_POLL', } +type sendResponseToPollEvtFunction = ( + poll: PollItem, + responses: string | string[], +) => void; interface PollEventsContextValue { sendPollEvt: (poll: PollItem) => void; - sendResponseToPollEvt: (poll: PollItem) => void; + sendResponseToPollEvt: sendResponseToPollEvtFunction; } const PollEventsContext = createContext(null); @@ -20,7 +27,7 @@ PollEventsContext.displayName = 'PollEventsContext'; function PollEventsProvider({children}: {children?: React.ReactNode}) { const sendPollEvt = async (poll: PollItem) => { events.send( - POLL_ATTRIBUTE, + PollEventNames.polls, JSON.stringify({ action: PollEventActions.sendPoll, item: {...poll}, @@ -29,15 +36,18 @@ function PollEventsProvider({children}: {children?: React.ReactNode}) { PersistanceLevel.Channel, ); }; - const sendResponseToPollEvt = async (poll: PollItem) => { + const sendResponseToPollEvt: sendResponseToPollEvtFunction = ( + item, + responses, + ) => { events.send( - POLL_ATTRIBUTE, + PollEventNames.pollResponse, JSON.stringify({ - action: PollEventActions.sendResponseToPoll, - item: {...poll}, - activePollId: poll.id, + type: item.type, + id: item.id, + responses, }), - PersistanceLevel.Channel, + PersistanceLevel.None, ); }; @@ -66,29 +76,36 @@ const PollEventsSubscriberContext = createContext(null); PollEventsSubscriberContext.displayName = 'PollEventsContext'; function PollEventsSubscriber({children}: {children?: React.ReactNode}) { - const {savePoll, onPollReceived} = usePoll(); + const {savePoll, onPollReceived, onPollResponseReceived} = usePoll(); useEffect(() => { - events.on(POLL_ATTRIBUTE, args => { - const {payload, sender, ts} = args; + events.on(PollEventNames.polls, args => { + // const {payload, sender, ts} = args; + const {payload} = args; const data = JSON.parse(payload); const {action, item, activePollId} = data; + console.log('supriya event received args: ', action, item); switch (action) { case PollEventActions.sendPoll: onPollReceived(item, activePollId); break; - case PollEventActions.sendResponseToPoll: - savePoll(item); - break; + default: break; } }); + events.on(PollEventNames.pollResponse, args => { + const {payload, sender, ts} = args; + const data = JSON.parse(payload); + const {type, id, responses} = data; + onPollResponseReceived(id, type, responses, sender, ts); + }); return () => { - events.off(POLL_ATTRIBUTE); + events.off(PollEventNames.polls); + events.off(PollEventNames.pollResponse); }; - }, [onPollReceived, savePoll]); + }, [onPollReceived, onPollResponseReceived, savePoll]); return ( diff --git a/template/src/components/polling/hook/useCountdownTimer.tsx b/template/src/components/polling/hook/useCountdownTimer.tsx index 13bc5ab6a..238f3adbe 100644 --- a/template/src/components/polling/hook/useCountdownTimer.tsx +++ b/template/src/components/polling/hook/useCountdownTimer.tsx @@ -2,7 +2,7 @@ import {useEffect, useState, useRef} from 'react'; const useCountdown = (targetDate: number) => { const countDownDate = new Date(targetDate).getTime(); - const intervalRef = useRef(null); // Add a ref to store the interval id + const intervalRef = useRef(null); // Add a ref to store the interval id const [countDown, setCountDown] = useState( countDownDate - new Date().getTime(), @@ -10,20 +10,23 @@ const useCountdown = (targetDate: number) => { useEffect(() => { intervalRef.current = setInterval(() => { - setCountDown(countDownDate - new Date().getTime()); + setCountDown(_ => { + const newCountDown = countDownDate - new Date().getTime(); + if (newCountDown <= 0) { + clearInterval(intervalRef.current!); + return 0; + } + return newCountDown; + }); }, 1000); - return () => clearInterval(intervalRef.current); + return () => { + if (intervalRef.current) { + clearInterval(intervalRef.current); + } + }; }, [countDownDate]); - useEffect(() => { - const time = getReturnValues(countDown); - const isAllZero = time.every(item => item === 0); - if (isAllZero) { - clearInterval(intervalRef.current); - } - }, [countDown]); - return getReturnValues(countDown); }; diff --git a/template/src/components/polling/ui/RadioButton.tsx b/template/src/components/polling/ui/RadioButton.tsx new file mode 100644 index 000000000..1ae3ab825 --- /dev/null +++ b/template/src/components/polling/ui/RadioButton.tsx @@ -0,0 +1,78 @@ +import { + TouchableOpacity, + View, + StyleSheet, + Text, + StyleProp, + TextStyle, +} from 'react-native'; +import React from 'react'; +import ThemeConfig from '../../../theme'; + +interface Props { + option: { + label: string; + value: string; + }; + checked: boolean; + onChange: (option: string) => void; + labelStyle?: StyleProp; + disabled?: boolean; +} +export default function RadioButton(props: Props) { + const {option, checked, onChange, disabled, labelStyle = {}} = props; + return ( + + !disabled && onChange(option.value)}> + + {option.label} + + + ); +} + +const style = StyleSheet.create({ + optionsContainer: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 10, + }, + disabledContainer: { + opacity: 0.5, + }, + radioCircle: { + height: 22, + width: 22, + borderRadius: 11, + borderWidth: 2, + borderColor: $config.PRIMARY_ACTION_BRAND_COLOR, + alignItems: 'center', + justifyContent: 'center', + }, + disabledCircle: { + // borderColor: $config.FONT_COLOR, + }, + radioFilled: { + height: 14, + width: 14, + borderRadius: 7, + backgroundColor: $config.PRIMARY_ACTION_BRAND_COLOR, + }, + optionText: { + color: $config.FONT_COLOR, + fontSize: ThemeConfig.FontSize.normal, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontWeight: '400', + lineHeight: 24, + marginLeft: 10, + }, +}); From 2ec4054459fee628d4fa7e26ce69ed0900f19948 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Sat, 10 Aug 2024 01:11:23 +0530 Subject: [PATCH 018/174] add percentage --- .../polling/components/form/form-config.ts | 5 ++ .../polling/context/poll-context.tsx | 65 +++++++++++-------- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/template/src/components/polling/components/form/form-config.ts b/template/src/components/polling/components/form/form-config.ts index 3cd941002..d80240727 100644 --- a/template/src/components/polling/components/form/form-config.ts +++ b/template/src/components/polling/components/form/form-config.ts @@ -44,16 +44,19 @@ const initPollForm = (kind: PollKind): PollItem => { text: '', value: '', votes: [], + percent: '', }, { text: '', value: '', votes: [], + percent: '', }, { text: '', value: '', votes: [], + percent: '', }, ], multiple_response: true, @@ -76,11 +79,13 @@ const initPollForm = (kind: PollKind): PollItem => { text: 'YES', value: 'yes', votes: [], + percent: '', }, { text: 'No', value: 'no', votes: [], + percent: '', }, ], multiple_response: false, diff --git a/template/src/components/polling/context/poll-context.tsx b/template/src/components/polling/context/poll-context.tsx index 5da4b3efb..5519908b8 100644 --- a/template/src/components/polling/context/poll-context.tsx +++ b/template/src/components/polling/context/poll-context.tsx @@ -29,6 +29,7 @@ interface PollItemOptionItem { text: string; value: string; votes: Array<{uid: number; access: PollAccess; timestamp: number}> | []; + percent: string; } interface PollItem { id: string; @@ -99,32 +100,39 @@ type PollAction = // }; // }; +function addVote( + responses: string[], + options: PollItemOptionItem[], + uid: number, + timestamp: number, +): PollItemOptionItem[] { + return options.map((option: PollItemOptionItem) => { + // Count how many times the value appears in the strings array + const exists = responses.findIndex(str => str === option.value); + if (exists > 0) { + // Return a new object with an updated votes array + const totalVotes = + options.reduce((total, item) => total + item.votes.length, 0) + 1; + const optionVotes = option.votes.length + 1; + return { + ...option, + votes: [ + ...option.votes, + { + uid, + access: PollAccess.PUBLIC, + timestamp, + }, + ], + percent: ((optionVotes / totalVotes) * 100).toFixed(2), + }; + } + // If no matches, return the option as is + return option; + }); +} + function pollReducer(state: Poll, action: PollAction): Poll { - function addVote( - responses: string[], - options: PollItemOptionItem[], - ): PollItemOptionItem[] { - return options.map((option: PollItemOptionItem) => { - // Count how many times the value appears in the strings array - const exists = responses.findIndex(str => str === option.value); - if (exists > 0) { - // Return a new object with an updated votes array - return { - ...option, - votes: [ - ...option.votes, - { - uid: 123, - access: PollAccess.PUBLIC, - timestamp: Date.now(), - }, - ], - }; - } - // If no matches, return the option as is - return option; - }); - } switch (action.type) { case PollActionKind.ADD_POLL_ITEM: { const pollId = action.payload.item.id; @@ -157,7 +165,12 @@ function pollReducer(state: Poll, action: PollAction): Poll { ...state, [pollId]: { ...state[pollId], - options: addVote(responses, state[pollId].options), + options: addVote( + responses, + state[pollId].options, + uid, + timestamp, + ), }, }; } From 0dae3af1e2e65abfbd2221c40f421dfd10402710 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Mon, 12 Aug 2024 12:41:23 +0530 Subject: [PATCH 019/174] add poll percentage --- .../components/polling/components/Poll.tsx | 3 +- .../components/form/DraftPollFormView.tsx | 5 +- .../components/form/poll-response-forms.tsx | 18 ++++--- .../modals/PollResponseFormModal.tsx | 17 ++++--- .../polling/context/poll-context.tsx | 47 ++++++++++++++----- .../polling/context/poll-events.tsx | 3 +- 6 files changed, 60 insertions(+), 33 deletions(-) diff --git a/template/src/components/polling/components/Poll.tsx b/template/src/components/polling/components/Poll.tsx index 030f9b773..735c8e2e9 100644 --- a/template/src/components/polling/components/Poll.tsx +++ b/template/src/components/polling/components/Poll.tsx @@ -17,7 +17,8 @@ function Poll({children}: {children?: React.ReactNode}) { } function PollModals() { - const {currentStep, launchPollId} = usePoll(); + const {currentStep, launchPollId, polls} = usePoll(); + console.log('supriya polls data chnaged: ', polls); return ( <> {currentStep === 'CREATE_POLL' && } diff --git a/template/src/components/polling/components/form/DraftPollFormView.tsx b/template/src/components/polling/components/form/DraftPollFormView.tsx index 06c8ebbf5..a5699661c 100644 --- a/template/src/components/polling/components/form/DraftPollFormView.tsx +++ b/template/src/components/polling/components/form/DraftPollFormView.tsx @@ -60,7 +60,8 @@ export default function DraftPollFormView({ { text: '', value: '', - votes: null, + votes: [], + percent: '', }, ], }); @@ -79,7 +80,7 @@ export default function DraftPollFormView({ ...option, text: value, value: lowerText, - votes: null, + votes: [], }; } return option; diff --git a/template/src/components/polling/components/form/poll-response-forms.tsx b/template/src/components/polling/components/form/poll-response-forms.tsx index 0dd75a618..ba70d9d1e 100644 --- a/template/src/components/polling/components/form/poll-response-forms.tsx +++ b/template/src/components/polling/components/form/poll-response-forms.tsx @@ -80,10 +80,14 @@ function PollResponseQuestionForm({ return ( <> - + {pollItem.duration ? ( + + ) : ( + <> + )} {pollItem.question} { - if (selectedOptions.length === 0 || !selectedOption) { + if (selectedOptions.length === 0 && !selectedOption) { return; } if (pollItem.multiple_response) { - onComplete(selectedOption); - } else { onComplete(selectedOptions); + } else { + onComplete(selectedOption); } }; diff --git a/template/src/components/polling/components/modals/PollResponseFormModal.tsx b/template/src/components/polling/components/modals/PollResponseFormModal.tsx index a9f978f3e..c9188288d 100644 --- a/template/src/components/polling/components/modals/PollResponseFormModal.tsx +++ b/template/src/components/polling/components/modals/PollResponseFormModal.tsx @@ -9,20 +9,19 @@ import { import {PollKind, usePoll} from '../../context/poll-context'; export default function PollResponseFormModal() { - const {polls, launchPollId, sendResponseToPoll} = usePoll(); + const {polls, launchPollId, sendResponseToPoll, goToShareResponseModal} = + usePoll(); const [hasResponded, setHasResponded] = useState(false); const pollItem = polls[launchPollId]; const onFormComplete = (responses: string | string[]) => { - // console.log('response: ', response); - // sendResponseToPoll(pollItem, response); - // if (pollItem.share) { - // // goToShareResponseModal(); - // } else { - // setHasResponded(true); - // } - // } + sendResponseToPoll(pollItem, responses); + if (pollItem.share) { + goToShareResponseModal(); + } else { + setHasResponded(true); + } }; function renderForm(type: PollKind) { diff --git a/template/src/components/polling/context/poll-context.tsx b/template/src/components/polling/context/poll-context.tsx index 5519908b8..a637b59e2 100644 --- a/template/src/components/polling/context/poll-context.tsx +++ b/template/src/components/polling/context/poll-context.tsx @@ -108,12 +108,8 @@ function addVote( ): PollItemOptionItem[] { return options.map((option: PollItemOptionItem) => { // Count how many times the value appears in the strings array - const exists = responses.findIndex(str => str === option.value); - if (exists > 0) { - // Return a new object with an updated votes array - const totalVotes = - options.reduce((total, item) => total + item.votes.length, 0) + 1; - const optionVotes = option.votes.length + 1; + const exists = responses.includes(option.value); + if (exists) { return { ...option, votes: [ @@ -124,7 +120,6 @@ function addVote( timestamp, }, ], - percent: ((optionVotes / totalVotes) * 100).toFixed(2), }; } // If no matches, return the option as is @@ -132,6 +127,29 @@ function addVote( }); } +function calculatePercentage( + options: PollItemOptionItem[], +): PollItemOptionItem[] { + const totalVotes = options.reduce( + (total, item) => total + item.votes.length, + 0, + ); + if (totalVotes === 0) { + // As none of the users have voted, there is no need to calulate the percentage, + // we can return the options as it is + return options; + } + return options.map((option: PollItemOptionItem) => { + let percentage = 0; + if (option.votes.length > 0) { + percentage = (option.votes.length / totalVotes) * 100; + } + return { + ...option, + percentage: percentage.toFixed(2), // Format to 2 decimal places + }; + }); +} function pollReducer(state: Poll, action: PollAction): Poll { switch (action.type) { case PollActionKind.ADD_POLL_ITEM: { @@ -161,16 +179,19 @@ function pollReducer(state: Poll, action: PollAction): Poll { }; } if (type === PollKind.MCQ && Array.isArray(responses)) { + const newCopyOptions = state[pollId].options.map(item => ({...item})); + const withVotesOptions = addVote( + responses, + newCopyOptions, + uid, + timestamp, + ); + const withPercentOptions = calculatePercentage(withVotesOptions); return { ...state, [pollId]: { ...state[pollId], - options: addVote( - responses, - state[pollId].options, - uid, - timestamp, - ), + options: [...withPercentOptions], }, }; } diff --git a/template/src/components/polling/context/poll-events.tsx b/template/src/components/polling/context/poll-events.tsx index 36291057a..b12aff7e9 100644 --- a/template/src/components/polling/context/poll-events.tsx +++ b/template/src/components/polling/context/poll-events.tsx @@ -84,7 +84,7 @@ function PollEventsSubscriber({children}: {children?: React.ReactNode}) { const {payload} = args; const data = JSON.parse(payload); const {action, item, activePollId} = data; - console.log('supriya event received args: ', action, item); + console.log('supriya poll received', data); switch (action) { case PollEventActions.sendPoll: onPollReceived(item, activePollId); @@ -97,6 +97,7 @@ function PollEventsSubscriber({children}: {children?: React.ReactNode}) { events.on(PollEventNames.pollResponse, args => { const {payload, sender, ts} = args; const data = JSON.parse(payload); + console.log('supriya poll response received', data); const {type, id, responses} = data; onPollResponseReceived(id, type, responses, sender, ts); }); From 8148de9b88436c82daf33de6e322309672fd7da2 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Mon, 12 Aug 2024 14:59:59 +0530 Subject: [PATCH 020/174] remove code readability --- .../components/polling/components/Poll.tsx | 16 ++++++++++++---- .../polling/context/poll-context.tsx | 19 ++++++++++++------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/template/src/components/polling/components/Poll.tsx b/template/src/components/polling/components/Poll.tsx index 735c8e2e9..d5d8424f3 100644 --- a/template/src/components/polling/components/Poll.tsx +++ b/template/src/components/polling/components/Poll.tsx @@ -1,9 +1,12 @@ import React from 'react'; -import {PollProvider, usePoll} from '../context/poll-context'; +import {PollModalState, PollProvider, usePoll} from '../context/poll-context'; import PollFormWizardModal from './modals/PollFormWizardModal'; import {PollEventsProvider, PollEventsSubscriber} from '../context/poll-events'; import PollResponseFormModal from './modals/PollResponseFormModal'; import SharePollModal from './modals/SharePollModal'; +// const DraftPollModal = React.lazy(() => import('./DraftPollModal')); +// const RespondToPollModal = React.lazy(() => import('./RespondToPollModal')); +// const SharePollResultModal = React.lazy(() => import('./SharePollResultModal')); function Poll({children}: {children?: React.ReactNode}) { return ( @@ -21,12 +24,17 @@ function PollModals() { console.log('supriya polls data chnaged: ', polls); return ( <> - {currentStep === 'CREATE_POLL' && } - {currentStep === 'RESPOND_TO_POLL' && launchPollId && ( + {currentStep === PollModalState.DRAFT_POLL && } + {currentStep === PollModalState.RESPOND_TO_POLL && launchPollId && ( )} - {currentStep === 'SHARE_POLL' && } + {currentStep === PollModalState.SHARE_POLL_RESULTS && } + // Loading...}> + // {activePollModal === PollAction.DraftPoll && } + // {activePollModal === PollAction.RespondToPoll && } + // {activePollModal === PollAction.SharePollResult && } + // ); } export default Poll; diff --git a/template/src/components/polling/context/poll-context.tsx b/template/src/components/polling/context/poll-context.tsx index a637b59e2..0136318e3 100644 --- a/template/src/components/polling/context/poll-context.tsx +++ b/template/src/components/polling/context/poll-context.tsx @@ -23,7 +23,11 @@ enum PollKind { YES_NO = 'YES_NO', } -type PollCurrentStep = 'CREATE_POLL' | 'RESPOND_TO_POLL' | 'SHARE_POLL'; +enum PollModalState { + DRAFT_POLL = 'DRAFT_POLL', + RESPOND_TO_POLL = 'RESPOND_TO_POLL', + SHARE_POLL_RESULTS = 'SHARE_POLL_RESULTS', +} interface PollItemOptionItem { text: string; @@ -205,7 +209,7 @@ function pollReducer(state: Poll, action: PollAction): Poll { interface PollContextValue { polls: Poll; - currentStep: PollCurrentStep; + currentStep: PollModalState; dispatch: Dispatch; startPollForm: () => void; savePoll: (item: PollItem) => void; @@ -228,7 +232,7 @@ PollContext.displayName = 'PollContext'; function PollProvider({children}: {children: React.ReactNode}) { const [polls, dispatch] = useReducer(pollReducer, {}); - const [currentStep, setCurrentStep] = useState(null); + const [currentStep, setCurrentStep] = useState(null); const [launchPollId, setLaunchPollId] = useState(null); const localUid = useLocalUid(); const {audienceUids} = useLiveStreamDataContext(); @@ -236,7 +240,7 @@ function PollProvider({children}: {children: React.ReactNode}) { const {sendPollEvt, sendResponseToPollEvt} = usePollEvents(); const startPollForm = () => { - setCurrentStep('CREATE_POLL'); + setCurrentStep(PollModalState.DRAFT_POLL); }; const savePoll = (item: PollItem) => { @@ -258,7 +262,7 @@ function PollProvider({children}: {children: React.ReactNode}) { addPollItem(item); if (audienceUids.includes(localUid)) { setLaunchPollId(launchId); - setCurrentStep('RESPOND_TO_POLL'); + setCurrentStep(PollModalState.RESPOND_TO_POLL); } }; @@ -304,7 +308,7 @@ function PollProvider({children}: {children: React.ReactNode}) { }; const goToShareResponseModal = () => { - setCurrentStep('SHARE_POLL'); + setCurrentStep(PollModalState.SHARE_POLL_RESULTS); }; const value = { @@ -339,6 +343,7 @@ export { PollKind, PollStatus, PollAccess, + PollModalState, }; -export type {PollItem, PollCurrentStep, PollFormErrors, PollItemOptionItem}; +export type {PollItem, PollFormErrors, PollItemOptionItem}; From 983fb007935d974ed3519ce917464349ce32ce97 Mon Sep 17 00:00:00 2001 From: HariharanIT Date: Mon, 12 Aug 2024 15:30:05 +0530 Subject: [PATCH 021/174] Added customization api to inject side panel --- template/customization-api/typeDefinition.ts | 8 ++ template/src/components/CustomSidePanel.tsx | 85 +++++++++++++++++++ .../src/pages/video-call/VideoCallScreen.tsx | 65 +++++++++++++- template/src/utils/useSidePanel.tsx | 4 +- 4 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 template/src/components/CustomSidePanel.tsx diff --git a/template/customization-api/typeDefinition.ts b/template/customization-api/typeDefinition.ts index 7434c8001..13dd1281b 100644 --- a/template/customization-api/typeDefinition.ts +++ b/template/customization-api/typeDefinition.ts @@ -47,6 +47,13 @@ export type LayoutComponent = React.ComponentType<{ renderData: ContentStateInterface['activeUids']; }>; +export interface SidePanelItem { + name: string; + title: string; + component: React.ComponentType; + onClose?: () => void; +} + export interface LayoutItem { name: string; label: string; @@ -76,6 +83,7 @@ export interface VideoCallInterface extends BeforeAndAfterInterface { virtualBackgroundPanel?: React.ComponentType; customLayout?: (layouts: LayoutItem[]) => LayoutItem[]; wrapper?: React.ComponentType; + customSidePanel?: () => SidePanelItem[]; invitePopup?: { title: string; renderComponent?: React.ComponentType; diff --git a/template/src/components/CustomSidePanel.tsx b/template/src/components/CustomSidePanel.tsx new file mode 100644 index 000000000..98a03349e --- /dev/null +++ b/template/src/components/CustomSidePanel.tsx @@ -0,0 +1,85 @@ +/* +******************************************** + Copyright © 2021 Agora Lab, Inc., all rights reserved. + AppBuilder and all associated components, source code, APIs, services, and documentation + (the “Materials”) are owned by Agora Lab, Inc. and its licensors. The Materials may not be + accessed, used, modified, or distributed for any purpose without a license from Agora Lab, Inc. + Use without a license or in violation of any license terms and conditions (including use for + any purpose competitive to Agora Lab, Inc.’s business) is strictly prohibited. For more + information visit https://appbuilder.agora.io. +********************************************* +*/ +import React from 'react'; +import {View, Text, StyleSheet, ScrollView} from 'react-native'; +import {isMobileUA, isWebInternal, useIsSmall} from '../utils/common'; +import CommonStyles from './CommonStyles'; +import SidePanelHeader, { + SidePanelStyles, +} from '../subComponents/SidePanelHeader'; +import {useLayout} from '../utils/useLayout'; +import {getGridLayoutName} from '../pages/video-call/DefaultLayouts'; +import useCaptionWidth from '../subComponents/caption/useCaptionWidth'; +import {useSidePanel} from '../utils/useSidePanel'; +import {SidePanelType} from '../subComponents/SidePanelEnum'; + +export interface CustomSidePanelViewInterface { + name: string; + title: string; + content: React.ComponentType; + onClose: () => void; +} + +const CustomSidePanelView = (props: CustomSidePanelViewInterface) => { + const {name, title, content: CustomSidePanelContent, onClose} = props; + const {currentLayout} = useLayout(); + const {transcriptHeight} = useCaptionWidth(); + const {setSidePanel} = useSidePanel(); + const isSmall = useIsSmall(); + + return ( + + {title}} + trailingIconName="close" + trailingIconOnPress={() => { + setSidePanel(SidePanelType.None); + try { + onClose && onClose(); + } catch (error) { + console.error( + `Error on calling onClose in custom side panel ${name}`, + ); + } + }} + /> + + {CustomSidePanelContent ? : <>} + + + ); +}; + +const style = StyleSheet.create({ + bodyContainer: { + flex: 1, + padding: 20, + }, +}); + +export default CustomSidePanelView; diff --git a/template/src/pages/video-call/VideoCallScreen.tsx b/template/src/pages/video-call/VideoCallScreen.tsx index 7bf4e4993..fc5fe6ff8 100644 --- a/template/src/pages/video-call/VideoCallScreen.tsx +++ b/template/src/pages/video-call/VideoCallScreen.tsx @@ -26,7 +26,12 @@ import {videoView} from '../../../theme.json'; import {ToolbarProvider, ToolbarPosition} from '../../utils/useToolbar'; import SDKEvents from '../../utils/SdkEvents'; import {useRoomInfo} from '../../components/room-info/useRoomInfo'; -import {controlMessageEnum, useCaption, useUserName} from 'customization-api'; +import { + controlMessageEnum, + SidePanelItem, + useCaption, + useUserName, +} from 'customization-api'; import events, {PersistanceLevel} from '../../rtm-events-api'; import VideoCallMobileView from './VideoCallMobileView'; import CaptionContainer from '../../subComponents/caption/CaptionContainer'; @@ -52,11 +57,16 @@ import { ToolbarLeftPresetProps, ToolbarRightPresetProps, ToolbarTopPresetProps, -} from 'src/atoms/ToolbarPreset'; +} from '../../atoms/ToolbarPreset'; +import CustomSidePanelView from '../../components/CustomSidePanel'; const VideoCallScreen = () => { useFindActiveSpeaker(); const {sidePanel} = useSidePanel(); + const [showCustomSidePanel, setShowCustomSidePanel] = useState(false); + const [customSidePanelIndex, setCustomSidePanelIndex] = useState< + undefined | number + >(undefined); const [name] = useUserName(); const { data: {meetingTitle, isHost}, @@ -81,6 +91,7 @@ const VideoCallScreen = () => { LeftbarProps, RightbarProps, VideocallWrapper, + SidePanelArray, } = useCustomization(data => { let components: { VideocallWrapper: React.ComponentType; @@ -101,6 +112,7 @@ const VideoCallScreen = () => { TopbarProps?: any; LeftbarProps?: any; RightbarProps?: any; + SidePanelArray?: SidePanelItem[]; } = { BottombarComponent: Controls, TopbarComponent: Navbar, @@ -119,6 +131,7 @@ const VideoCallScreen = () => { TopbarProps: {}, LeftbarProps: {}, RightbarProps: {}, + SidePanelArray: [], }; if ( data?.components?.videoCall && @@ -265,6 +278,14 @@ const VideoCallScreen = () => { components.VideocallWrapper = data?.components?.videoCall.wrapper; } + if ( + data?.components?.videoCall.customSidePanel && + typeof data?.components?.videoCall.customSidePanel === 'function' + ) { + components.SidePanelArray = + data?.components?.videoCall?.customSidePanel(); + } + // commented for v1 release // if ( // data?.components?.videoCall.settingsPanel && @@ -280,7 +301,6 @@ const VideoCallScreen = () => { }); const isDesktop = useIsDesktop(); - const isSmall = useIsSmall(); useEffect(() => { logger.log( @@ -289,6 +309,24 @@ const VideoCallScreen = () => { 'User has landed on video call room', ); }, []); + + useEffect(() => { + const selectedIndex = SidePanelArray?.findIndex(item => { + if (item?.name === sidePanel && item?.component) { + return true; + } else { + return false; + } + }); + if (selectedIndex < 0) { + setShowCustomSidePanel(false); + setCustomSidePanelIndex(undefined); + } else { + setShowCustomSidePanel(true); + setCustomSidePanelIndex(selectedIndex); + } + }, [sidePanel, SidePanelArray]); + const {isRecordingBot, recordingBotUIConfig} = useIsRecordingBot(); return VideocallComponent ? ( @@ -344,6 +382,27 @@ const VideoCallScreen = () => { : {marginVertical: 20}, ]}> + {/** + * To display custom side panel + * it will be shown when customer inject the custom side panel content + * and call setSidePanel using the name they given + */} + {showCustomSidePanel && customSidePanelIndex !== undefined ? ( + SidePanelArray && + SidePanelArray?.length && + SidePanelArray[customSidePanelIndex]?.component ? ( + + ) : ( + <> + ) + ) : ( + <> + )} {sidePanel === SidePanelType.Participants ? ( ) : ( diff --git a/template/src/utils/useSidePanel.tsx b/template/src/utils/useSidePanel.tsx index 80540f4b4..5ef41fc9c 100644 --- a/template/src/utils/useSidePanel.tsx +++ b/template/src/utils/useSidePanel.tsx @@ -16,8 +16,8 @@ import {createHook} from 'customization-implementation'; import {LogSource, logger} from '../logger/AppBuilderLogger'; export interface SidePanelContextInterface { - sidePanel: SidePanelType; - setSidePanel: React.Dispatch>; + sidePanel: SidePanelType | string; + setSidePanel: React.Dispatch>; } const SidePanelContext = React.createContext({ From cf7eb97c11e717b2186560b4dccbb461d340fbf9 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Mon, 12 Aug 2024 16:17:09 +0530 Subject: [PATCH 022/174] rename radio button --- .../polling/components/form/PreviewPollFormView.tsx | 4 ++-- .../polling/components/form/poll-response-forms.tsx | 4 ++-- .../polling/ui/{RadioButton.tsx => BaseRadioButton.tsx} | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) rename template/src/components/polling/ui/{RadioButton.tsx => BaseRadioButton.tsx} (96%) diff --git a/template/src/components/polling/components/form/PreviewPollFormView.tsx b/template/src/components/polling/components/form/PreviewPollFormView.tsx index da79d0c90..9a7ff96b8 100644 --- a/template/src/components/polling/components/form/PreviewPollFormView.tsx +++ b/template/src/components/polling/components/form/PreviewPollFormView.tsx @@ -9,7 +9,7 @@ import ThemeConfig from '../../../../theme'; import TertiaryButton from '../../../../atoms/TertiaryButton'; import {PollItem} from '../../context/poll-context'; import {POLL_DURATION} from './form-config'; -import RadioButton from '../../ui/RadioButton'; +import BaseRadioButton from '../../ui/BaseRadioButton'; import Checkbox from '../../../../atoms/Checkbox'; interface Props { @@ -45,7 +45,7 @@ export default function PreviewPollFormView({form, onEdit, onSave}: Props) { )) : form.options.map((option, index) => ( - ( - ; disabled?: boolean; } -export default function RadioButton(props: Props) { +export default function BaseRadioButton(props: Props) { const {option, checked, onChange, disabled, labelStyle = {}} = props; return ( From d7dae4d60bd32f4cee7764ca2d80114ea83b55d7 Mon Sep 17 00:00:00 2001 From: HariharanIT Date: Mon, 12 Aug 2024 18:32:12 +0530 Subject: [PATCH 023/174] Updated action sheet to add custom one using customization --- template/src/components/CustomSidePanel.tsx | 31 ++-- .../pages/video-call/ActionSheet.native.tsx | 144 +++++++++++++----- template/src/pages/video-call/ActionSheet.tsx | 136 +++++++++++++---- .../pages/video-call/ActionSheetHandle.tsx | 14 +- .../src/pages/video-call/SidePanelHeader.tsx | 26 ++++ template/src/subComponents/SidePanelEnum.tsx | 12 +- 6 files changed, 277 insertions(+), 86 deletions(-) diff --git a/template/src/components/CustomSidePanel.tsx b/template/src/components/CustomSidePanel.tsx index 98a03349e..2f47efe4c 100644 --- a/template/src/components/CustomSidePanel.tsx +++ b/template/src/components/CustomSidePanel.tsx @@ -21,16 +21,24 @@ import {getGridLayoutName} from '../pages/video-call/DefaultLayouts'; import useCaptionWidth from '../subComponents/caption/useCaptionWidth'; import {useSidePanel} from '../utils/useSidePanel'; import {SidePanelType} from '../subComponents/SidePanelEnum'; +import {CustomSidePanelHeader} from '../pages/video-call/SidePanelHeader'; export interface CustomSidePanelViewInterface { name: string; - title: string; + title?: string; content: React.ComponentType; - onClose: () => void; + onClose?: () => void; + showHeader?: boolean; } const CustomSidePanelView = (props: CustomSidePanelViewInterface) => { - const {name, title, content: CustomSidePanelContent, onClose} = props; + const { + content: CustomSidePanelContent, + showHeader = true, + name, + title, + onClose, + } = props; const {currentLayout} = useLayout(); const {transcriptHeight} = useCaptionWidth(); const {setSidePanel} = useSidePanel(); @@ -54,20 +62,9 @@ const CustomSidePanelView = (props: CustomSidePanelViewInterface) => { //@ts-ignore transcriptHeight && !isMobileUA() && {height: transcriptHeight}, ]}> - {title}} - trailingIconName="close" - trailingIconOnPress={() => { - setSidePanel(SidePanelType.None); - try { - onClose && onClose(); - } catch (error) { - console.error( - `Error on calling onClose in custom side panel ${name}`, - ); - } - }} - /> + {showHeader && ( + + )} {CustomSidePanelContent ? : <>} diff --git a/template/src/pages/video-call/ActionSheet.native.tsx b/template/src/pages/video-call/ActionSheet.native.tsx index ea7db3c81..6eb1ec862 100644 --- a/template/src/pages/video-call/ActionSheet.native.tsx +++ b/template/src/pages/video-call/ActionSheet.native.tsx @@ -24,10 +24,30 @@ import {isAndroid, isIOS} from '../../utils/common'; import ActionSheetHandle from './ActionSheetHandle'; import Spacer from '../../atoms/Spacer'; import Transcript from '../../subComponents/caption/Transcript'; +import {useCustomization} from 'customization-implementation'; +import CustomSidePanelView from '../../components/CustomSidePanel'; //topbar btn template is used to show icons without label text (as in desktop : bottomBar) const ActionSheet = props => { + const [showCustomSidePanel, setShowCustomSidePanel] = useState(false); + const [customSidePanelIndex, setCustomSidePanelIndex] = useState< + undefined | number + >(undefined); + const sidePanelArray = useCustomization(data => { + if ( + data?.components && + data?.components?.videoCall && + typeof data?.components?.videoCall === 'object' + ) { + if ( + data?.components?.videoCall?.customSidePanel && + typeof data?.components?.videoCall?.customSidePanel === 'function' + ) { + return data?.components?.videoCall?.customSidePanel(); + } + } + }); const {snapPointsMinMax = [100, 400]} = props; const [isExpanded, setIsExpanded] = React.useState(false); const {sidePanel, setSidePanel} = useSidePanel(); @@ -36,6 +56,7 @@ const ActionSheet = props => { const participantsSheetRef = useRef(null); const settingsSheetRef = useRef(null); const transcriptSheetRef = useRef(null); + const customActionSheetRef = useRef(null); // callbacks const handleSheetChanges = useCallback((index: number) => { @@ -47,49 +68,74 @@ const ActionSheet = props => { bottomSheetRef?.current.present(); }, []); + React.useEffect(() => { + if (showCustomSidePanel) { + customActionSheetRef?.current.present(); + } else { + customActionSheetRef?.current.close(); + } + }, [showCustomSidePanel]); + // updating on sidepanel changes React.useEffect(() => { let timeout; - switch (sidePanel) { - case SidePanelType.Participants: { - participantsSheetRef?.current.present(); - break; - } - case SidePanelType.Chat: { - chatSheetRef?.current.present(); - break; - } - case SidePanelType.Settings: { - settingsSheetRef?.current.present(); - break; - } - case SidePanelType.Transcript: { - transcriptSheetRef?.current.present(); - break; + + const selectedIndex = sidePanelArray?.findIndex(item => { + if (item?.name === sidePanel && item?.component) { + return true; + } else { + return false; } - case SidePanelType.None: { - if (isAndroid()) { - timeout = setTimeout(() => { - // Code to be executed after the timeout until https://github.com/gorhom/react-native-bottom-sheet/pull/1164/files is merged - chatSheetRef?.current.close(); + }); + if (selectedIndex < 0) { + setShowCustomSidePanel(false); + setCustomSidePanelIndex(undefined); + switch (sidePanel) { + case SidePanelType.Participants: { + participantsSheetRef?.current.present(); + break; + } + case SidePanelType.Chat: { + chatSheetRef?.current.present(); + break; + } + case SidePanelType.Settings: { + settingsSheetRef?.current.present(); + break; + } + case SidePanelType.Transcript: { + transcriptSheetRef?.current.present(); + break; + } + case SidePanelType.None: { + if (isAndroid()) { + timeout = setTimeout(() => { + // Code to be executed after the timeout until https://github.com/gorhom/react-native-bottom-sheet/pull/1164/files is merged + chatSheetRef?.current.close(); + participantsSheetRef?.current.close(); + settingsSheetRef?.current.close(); + transcriptSheetRef?.current.close(); + customActionSheetRef?.current.close(); + bottomSheetRef?.current.present(); + }, 200); + } else { + // Code to be executed immediately without a timer + chatSheetRef?.current.dismiss(); participantsSheetRef?.current.close(); settingsSheetRef?.current.close(); transcriptSheetRef?.current.close(); - bottomSheetRef?.current.present(); - }, 200); - } else { - // Code to be executed immediately without a timer - chatSheetRef?.current.dismiss(); - participantsSheetRef?.current.close(); - settingsSheetRef?.current.close(); - transcriptSheetRef?.current.close(); - } + customActionSheetRef?.current.close(); + } - handleSheetChanges(0); - break; + handleSheetChanges(0); + break; + } + default: + bottomSheetRef?.current.present(); } - default: - bottomSheetRef?.current.present(); + } else { + setShowCustomSidePanel(true); + setCustomSidePanelIndex(selectedIndex); } return () => { clearTimeout(timeout); @@ -217,6 +263,36 @@ const ActionSheet = props => { + + {/* Custom Action Sheet */} + ( + + )} + stackBehavior="push"> + + + + ); }; diff --git a/template/src/pages/video-call/ActionSheet.tsx b/template/src/pages/video-call/ActionSheet.tsx index 1626cf340..fcdfc47ff 100644 --- a/template/src/pages/video-call/ActionSheet.tsx +++ b/template/src/pages/video-call/ActionSheet.tsx @@ -1,5 +1,11 @@ import {StyleSheet, Text, View, TouchableWithoutFeedback} from 'react-native'; -import React, {useRef, useCallback, useLayoutEffect, useEffect} from 'react'; +import React, { + useRef, + useCallback, + useLayoutEffect, + useEffect, + useState, +} from 'react'; import {BottomSheet, BottomSheetRef} from 'react-spring-bottom-sheet'; import './ActionSheetStyles.css'; import ActionSheetContent from './ActionSheetContent'; @@ -19,8 +25,28 @@ import Transcript from '../../subComponents/caption/Transcript'; import {ToolbarProvider} from '../../utils/useToolbar'; import {ActionSheetProvider} from '../../utils/useActionSheet'; import {useOrientation} from '../../utils/useOrientation'; +import {useCustomization} from 'customization-implementation'; +import CustomSidePanelView from '../../components/CustomSidePanel'; const ActionSheet = props => { + const [showCustomSidePanel, setShowCustomSidePanel] = useState(false); + const [customSidePanelIndex, setCustomSidePanelIndex] = useState< + undefined | number + >(undefined); + const sidePanelArray = useCustomization(data => { + if ( + data?.components && + data?.components?.videoCall && + typeof data?.components?.videoCall === 'object' + ) { + if ( + data?.components?.videoCall?.customSidePanel && + typeof data?.components?.videoCall?.customSidePanel === 'function' + ) { + return data?.components?.videoCall?.customSidePanel(); + } + } + }); const {snapPointsMinMax = [100, 400]} = props; const {setActionSheetVisible} = useToast(); const [isExpanded, setIsExpanded] = React.useState(false); @@ -32,10 +58,15 @@ const ActionSheet = props => { const chatSheetRef = useRef(null); const participantsSheetRef = useRef(null); const settingsSheetRef = useRef(null); + const customActionSheetRef = useRef(null); const transcriptSheetRef = useRef(null); const ToastComponentRender = isMobileUA() && - (isChatOpen || isSettingsOpen || isParticipantsOpen || isTranscriptOpen) ? ( + (isChatOpen || + isSettingsOpen || + isParticipantsOpen || + isTranscriptOpen || + showCustomSidePanel) ? ( ) : ( <> @@ -59,41 +90,63 @@ const ActionSheet = props => { isChatOpen || isSettingsOpen || isParticipantsOpen || - isTranscriptOpen + isTranscriptOpen || + showCustomSidePanel ) { setActionSheetVisible(true); } else { setActionSheetVisible(false); } - }, [isChatOpen, isSettingsOpen, isParticipantsOpen, isTranscriptOpen]); + }, [ + isChatOpen, + isSettingsOpen, + isParticipantsOpen, + isTranscriptOpen, + showCustomSidePanel, + setActionSheetVisible, + ]); // updating on sidepanel changes useEffect(() => { - switch (sidePanel) { - case SidePanelType.Participants: { - setIsParticipantsOpen(true); - break; - } - case SidePanelType.Chat: { - setIsChatOpen(true); - break; + const selectedIndex = sidePanelArray?.findIndex(item => { + if (item?.name === sidePanel && item?.component) { + return true; + } else { + return false; } - case SidePanelType.Settings: { - setIsSettingsOpen(true); - break; - } - case SidePanelType.Transcript: { - setIsTranscriptOpen(true); - break; - } - case SidePanelType.None: { - setIsChatOpen(false); - setIsParticipantsOpen(false); - setIsSettingsOpen(false); - setIsTranscriptOpen(false); - handleSheetChanges(0); + }); + if (selectedIndex < 0) { + setShowCustomSidePanel(false); + setCustomSidePanelIndex(undefined); + switch (sidePanel) { + case SidePanelType.Participants: { + setIsParticipantsOpen(true); + break; + } + case SidePanelType.Chat: { + setIsChatOpen(true); + break; + } + case SidePanelType.Settings: { + setIsSettingsOpen(true); + break; + } + case SidePanelType.Transcript: { + setIsTranscriptOpen(true); + break; + } + case SidePanelType.None: { + setIsChatOpen(false); + setIsParticipantsOpen(false); + setIsSettingsOpen(false); + setIsTranscriptOpen(false); + handleSheetChanges(0); + } + default: } - default: + } else { + setShowCustomSidePanel(true); + setCustomSidePanelIndex(selectedIndex); } }, [sidePanel]); @@ -238,6 +291,35 @@ const ActionSheet = props => { blocking={false}> + {showCustomSidePanel && customSidePanelIndex !== undefined ? ( + [1 * maxHeight]} + defaultSnap={({lastSnap, snapPoints}) => snapPoints[0]} + header={ + + } + blocking={false}> + + + ) : ( + <> + )} ); diff --git a/template/src/pages/video-call/ActionSheetHandle.tsx b/template/src/pages/video-call/ActionSheetHandle.tsx index 418a84fd8..4d91f8fbb 100644 --- a/template/src/pages/video-call/ActionSheetHandle.tsx +++ b/template/src/pages/video-call/ActionSheetHandle.tsx @@ -5,15 +5,24 @@ import {SidePanelType} from '../../subComponents/SidePanelEnum'; import { ChatHeader, + CustomSidePanelHeader, PeopleHeader, SettingsHeader, TranscriptHeader, } from './SidePanelHeader'; -const ActionSheetHandle = (props: {sidePanel: SidePanelType}) => { +const ActionSheetHandle = (props: { + sidePanel?: SidePanelType | string; + isCustomSidePanel?: boolean; + customSidePanelProps?: { + title: string; + onClose?: () => void; + name: string; + }; +}) => { const Header = null; - const {sidePanel} = props; + const {sidePanel, isCustomSidePanel = false, customSidePanelProps} = props; return ( @@ -23,6 +32,7 @@ const ActionSheetHandle = (props: {sidePanel: SidePanelType}) => { {sidePanel === SidePanelType.Chat && } {sidePanel === SidePanelType.Settings && } {sidePanel === SidePanelType.Transcript && } + {isCustomSidePanel && } ); }; diff --git a/template/src/pages/video-call/SidePanelHeader.tsx b/template/src/pages/video-call/SidePanelHeader.tsx index 92d448257..667e039f3 100644 --- a/template/src/pages/video-call/SidePanelHeader.tsx +++ b/template/src/pages/video-call/SidePanelHeader.tsx @@ -188,6 +188,32 @@ export const VBHeader = () => { ); }; +export const CustomSidePanelHeader = (props: { + title: string; + onClose?: () => void; + name: string; +}) => { + const {setSidePanel} = useSidePanel(); + return ( + {props?.title} + } + trailingIconName="close" + trailingIconOnPress={() => { + setSidePanel(SidePanelType.None); + try { + props?.onClose && props?.onClose(); + } catch (error) { + console.error( + `Error on calling onClose in custom side panel ${name}`, + ); + } + }} + /> + ); +}; + export const TranscriptHeader = props => { const {setSidePanel} = useSidePanel(); const moreIconRef = React.useRef(null); diff --git a/template/src/subComponents/SidePanelEnum.tsx b/template/src/subComponents/SidePanelEnum.tsx index 595713d97..0ac6cec07 100644 --- a/template/src/subComponents/SidePanelEnum.tsx +++ b/template/src/subComponents/SidePanelEnum.tsx @@ -10,10 +10,10 @@ ********************************************* */ export enum SidePanelType { - None, - Participants, - Chat, - Settings, - Transcript, - VirtualBackground, + None = 'None', + Participants = 'Participants', + Chat = 'Chat', + Settings = 'Settings', + Transcript = 'Transcript', + VirtualBackground = 'VirtualBackground', } From 1a3f6eb62260d32c2965d1fe5b6a687f49763c92 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Mon, 12 Aug 2024 23:48:02 +0530 Subject: [PATCH 024/174] poll side abr --- .../polling/components/PollSidebar.tsx | 113 +++++++++++ .../components/polling/components/cards.tsx | 180 ++++++++++++++++++ .../polling/context/poll-context.tsx | 24 +-- 3 files changed, 302 insertions(+), 15 deletions(-) create mode 100644 template/src/components/polling/components/PollSidebar.tsx create mode 100644 template/src/components/polling/components/cards.tsx diff --git a/template/src/components/polling/components/PollSidebar.tsx b/template/src/components/polling/components/PollSidebar.tsx new file mode 100644 index 000000000..b6be76410 --- /dev/null +++ b/template/src/components/polling/components/PollSidebar.tsx @@ -0,0 +1,113 @@ +/* +******************************************** + Copyright © 2021 Agora Lab, Inc., all rights reserved. + AppBuilder and all associated components, source code, APIs, services, and documentation + (the “Materials”) are owned by Agora Lab, Inc. and its licensors. The Materials may not be + accessed, used, modified, or distributed for any purpose without a license from Agora Lab, Inc. + Use without a license or in violation of any license terms and conditions (including use for + any purpose competitive to Agora Lab, Inc.’s business) is strictly prohibited. For more + information visit https://appbuilder.agora.io. +********************************************* +*/ +import React from 'react'; +import {Text, View, StyleSheet} from 'react-native'; +import ThemeConfig from '../../../theme'; +import PrimaryButton from '../../../atoms/PrimaryButton'; +import {PollResultCard} from './cards'; + +const PollSidebar = () => { + return ( + + {/* Header */} + + + + Create a new poll and boost interaction with your audience members + now! + + + {}} + text="Create Poll" + /> + + + + + + + Past Polls (01) + + + + + + + ); +}; + +const style = StyleSheet.create({ + pollSidebar: { + backgroundColor: $config.CARD_LAYER_1_COLOR, + }, + headerSection: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + padding: 20, + }, + headerCard: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'flex-start', + gap: 16, + padding: 20, + backgroundColor: $config.CARD_LAYER_3_COLOR, + borderRadius: 15, + }, + bodyXSmallText: { + color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.medium, + fontSize: ThemeConfig.FontSize.small, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontWeight: '400', + lineHeight: 16, + }, + btnContainer: { + minWidth: 150, + height: 36, + borderRadius: 4, + paddingVertical: 10, + paddingHorizontal: 8, + }, + btnText: { + color: $config.FONT_COLOR, + fontSize: ThemeConfig.FontSize.small, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontWeight: '600', + textTransform: 'capitalize', + }, + separator: { + paddingVertical: 24, + height: 1, + marginHorizontal: 16, + display: 'flex', + background: $config.INPUT_FIELD_BORDER_COLOR, + }, + bodySection: { + paddingHorizontal: 16, + }, + bodySectionTitleText: { + color: $config.FONT_COLOR, + fontSize: ThemeConfig.FontSize.tiny, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontWeight: '600', + lineHeight: 12, + }, + pollList: {}, +}); + +export default PollSidebar; diff --git a/template/src/components/polling/components/cards.tsx b/template/src/components/polling/components/cards.tsx new file mode 100644 index 000000000..9d87581c2 --- /dev/null +++ b/template/src/components/polling/components/cards.tsx @@ -0,0 +1,180 @@ +import React from 'react'; +import {Text, View, StyleSheet, DimensionValue} from 'react-native'; +import ThemeConfig from '../../../theme'; +import TertiaryButton from '../../../atoms/TertiaryButton'; +import {PollItemOptionItem} from '../context/poll-context'; + +interface PollListItemWithResultProps { + options?: PollItemOptionItem[]; + showYourVote?: boolean; +} + +function PollListItemWithResult({ + options = [], + showYourVote = false, +}: PollListItemWithResultProps) { + return ( + + + Great + {showYourVote && ( + Your Response + )} + + {/* {option.percent} {option.votes.lastIndexOf} */} + 75% (15) + + + + + + + + + ); +} + +function PollResultCard() { + return ( + + + + Active + + More + + + + + + How confident do you feel about your javascript skills after + today’s session? + + + + + + + + + + + {}} /> + + + + + ); +} +const style = StyleSheet.create({ + fullWidth: { + alignSelf: 'stretch', + }, + pollItem: { + marginVertical: 12, + }, + pollCard: { + padding: 12, + display: 'flex', + flexDirection: 'column', + gap: 12, + alignSelf: 'stretch', + backgroundColor: $config.CARD_LAYER_3_COLOR, + borderRadius: 15, + }, + pollCardHeader: { + height: 24, + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + }, + pollCardHeaderText: { + color: '#04D000', + fontSize: ThemeConfig.FontSize.tiny, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontWeight: '600', + lineHeight: 12, + }, + pollCardContent: { + display: 'flex', + flexDirection: 'column', + gap: 12, + alignSelf: 'stretch', + alignItems: 'flex-start', + }, + pollCardContentQuestionText: { + color: $config.FONT_COLOR, + fontSize: ThemeConfig.FontSize.small, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontWeight: '600', + lineHeight: 16, + }, + pollCardFooter: {}, + pollCardFooterActions: { + alignSelf: 'flex-start', + }, + optionsList: { + backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, + borderRadius: 9, + paddingHorizontal: 12, + paddingVertical: 8, + display: 'flex', + flexDirection: 'column', + gap: 4, + marginVertical: 20, + }, + extraBottomPadding: { + paddingBottom: 32, + }, + optionListItem: { + display: 'flex', + flexDirection: 'column', + gap: 4, + }, + optionListItemHeader: { + display: 'flex', + flexDirection: 'row', + paddingHorizontal: 16, + paddingVertical: 8, + alignItems: 'center', + }, + optionListItemFooter: {}, + optionText: { + color: $config.FONT_COLOR, + fontSize: ThemeConfig.FontSize.normal, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontWeight: '400', + lineHeight: 24, + }, + yourResponseText: { + color: $config.SEMANTIC_SUCCESS, + fontSize: ThemeConfig.FontSize.tiny, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontWeight: '600', + lineHeight: 12, + paddingLeft: 16, + }, + pushRight: { + marginLeft: 'auto', + }, + progressBar: { + height: 4, + borderRadius: 8, + backgroundColor: $config.CARD_LAYER_3_COLOR, + width: '100%', + }, + progressBarFill: { + borderRadius: 8, + backgroundColor: $config.PRIMARY_ACTION_BRAND_COLOR, + }, +}); + +export {PollResultCard}; diff --git a/template/src/components/polling/context/poll-context.tsx b/template/src/components/polling/context/poll-context.tsx index 0136318e3..5b972380e 100644 --- a/template/src/components/polling/context/poll-context.tsx +++ b/template/src/components/polling/context/poll-context.tsx @@ -92,17 +92,6 @@ type PollAction = timestamp: number; }; }; -// | { -// type: PollActionKind.SUBMIT_POLL_OPEN_ENDED_RESPONSE; -// payload: { -// pollId: string; -// answerItem: { -// uid: number; -// response: string; -// timestamp: number; -// }; -// }; -// }; function addVote( responses: string[], @@ -114,7 +103,9 @@ function addVote( // Count how many times the value appears in the strings array const exists = responses.includes(option.value); if (exists) { - return { + // Creating a new object explicitly + const newOption: PollItemOptionItem = { + ...option, ...option, votes: [ ...option.votes, @@ -125,6 +116,7 @@ function addVote( }, ], }; + return newOption; } // If no matches, return the option as is return option; @@ -148,11 +140,13 @@ function calculatePercentage( if (option.votes.length > 0) { percentage = (option.votes.length / totalVotes) * 100; } - return { + // Creating a new object explicitly + const newOption: PollItemOptionItem = { ...option, - percentage: percentage.toFixed(2), // Format to 2 decimal places + percent: percentage.toFixed(2), }; - }); + return newOption; + }) as PollItemOptionItem[]; } function pollReducer(state: Poll, action: PollAction): Poll { switch (action.type) { From 5a3f455ba647cc6616c0bebadfebf5feb7de30f3 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Tue, 13 Aug 2024 09:03:08 +0530 Subject: [PATCH 025/174] rename currentstep var --- .../src/components/polling/components/Poll.tsx | 8 ++++---- .../components/polling/context/poll-context.tsx | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/template/src/components/polling/components/Poll.tsx b/template/src/components/polling/components/Poll.tsx index d5d8424f3..a9a8bcc99 100644 --- a/template/src/components/polling/components/Poll.tsx +++ b/template/src/components/polling/components/Poll.tsx @@ -20,15 +20,15 @@ function Poll({children}: {children?: React.ReactNode}) { } function PollModals() { - const {currentStep, launchPollId, polls} = usePoll(); + const {currentModal, launchPollId, polls} = usePoll(); console.log('supriya polls data chnaged: ', polls); return ( <> - {currentStep === PollModalState.DRAFT_POLL && } - {currentStep === PollModalState.RESPOND_TO_POLL && launchPollId && ( + {currentModal === PollModalState.DRAFT_POLL && } + {currentModal === PollModalState.RESPOND_TO_POLL && launchPollId && ( )} - {currentStep === PollModalState.SHARE_POLL_RESULTS && } + {currentModal === PollModalState.SHARE_POLL_RESULTS && } // Loading...}> // {activePollModal === PollAction.DraftPoll && } diff --git a/template/src/components/polling/context/poll-context.tsx b/template/src/components/polling/context/poll-context.tsx index 5b972380e..17ed65d68 100644 --- a/template/src/components/polling/context/poll-context.tsx +++ b/template/src/components/polling/context/poll-context.tsx @@ -203,7 +203,7 @@ function pollReducer(state: Poll, action: PollAction): Poll { interface PollContextValue { polls: Poll; - currentStep: PollModalState; + currentModal: PollModalState; dispatch: Dispatch; startPollForm: () => void; savePoll: (item: PollItem) => void; @@ -226,7 +226,7 @@ PollContext.displayName = 'PollContext'; function PollProvider({children}: {children: React.ReactNode}) { const [polls, dispatch] = useReducer(pollReducer, {}); - const [currentStep, setCurrentStep] = useState(null); + const [currentModal, setCurrentModal] = useState(null); const [launchPollId, setLaunchPollId] = useState(null); const localUid = useLocalUid(); const {audienceUids} = useLiveStreamDataContext(); @@ -234,19 +234,19 @@ function PollProvider({children}: {children: React.ReactNode}) { const {sendPollEvt, sendResponseToPollEvt} = usePollEvents(); const startPollForm = () => { - setCurrentStep(PollModalState.DRAFT_POLL); + setCurrentModal(PollModalState.DRAFT_POLL); }; const savePoll = (item: PollItem) => { addPollItem(item); - setCurrentStep(null); + setCurrentModal(null); }; const sendPoll = (item: PollItem) => { if (item.status === PollStatus.ACTIVE) { item.expiresAt = getPollExpiresAtTime(POLL_DURATION); sendPollEvt(item); - setCurrentStep(null); + setCurrentModal(null); } else { console.error('Poll: Cannot send poll as the status is not active'); } @@ -256,7 +256,7 @@ function PollProvider({children}: {children: React.ReactNode}) { addPollItem(item); if (audienceUids.includes(localUid)) { setLaunchPollId(launchId); - setCurrentStep(PollModalState.RESPOND_TO_POLL); + setCurrentModal(PollModalState.RESPOND_TO_POLL); } }; @@ -302,7 +302,7 @@ function PollProvider({children}: {children: React.ReactNode}) { }; const goToShareResponseModal = () => { - setCurrentStep(PollModalState.SHARE_POLL_RESULTS); + setCurrentModal(PollModalState.SHARE_POLL_RESULTS); }; const value = { @@ -313,7 +313,7 @@ function PollProvider({children}: {children: React.ReactNode}) { sendPoll, onPollReceived, onPollResponseReceived, - currentStep, + currentModal, launchPollId, sendResponseToPoll, goToShareResponseModal, From de2cd5ab8855117605ea14915eba5f4ce4f2751a Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Tue, 13 Aug 2024 10:12:39 +0530 Subject: [PATCH 026/174] fix modals maxwidth --- .../polling/components/form/DraftPollFormView.tsx | 1 - .../components/form/SelectNewPollTypeFormView.tsx | 1 + .../polling/components/modals/PollFormWizardModal.tsx | 6 +++++- template/src/components/polling/ui/BaseModal.tsx | 11 ++++++++--- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/template/src/components/polling/components/form/DraftPollFormView.tsx b/template/src/components/polling/components/form/DraftPollFormView.tsx index a5699661c..61f5a2100 100644 --- a/template/src/components/polling/components/form/DraftPollFormView.tsx +++ b/template/src/components/polling/components/form/DraftPollFormView.tsx @@ -275,7 +275,6 @@ export default function DraftPollFormView({ export const style = StyleSheet.create({ createPollBox: { - width: 620, display: 'flex', flexDirection: 'column', gap: 20, diff --git a/template/src/components/polling/components/form/SelectNewPollTypeFormView.tsx b/template/src/components/polling/components/form/SelectNewPollTypeFormView.tsx index d37fa3131..73a6d5375 100644 --- a/template/src/components/polling/components/form/SelectNewPollTypeFormView.tsx +++ b/template/src/components/polling/components/form/SelectNewPollTypeFormView.tsx @@ -70,6 +70,7 @@ export const style = StyleSheet.create({ display: 'flex', flexDirection: 'row', gap: 20, + justifyContent: 'space-between', }, card: { flexDirection: 'column', diff --git a/template/src/components/polling/components/modals/PollFormWizardModal.tsx b/template/src/components/polling/components/modals/PollFormWizardModal.tsx index 71fa59a99..83a7d68d6 100644 --- a/template/src/components/polling/components/modals/PollFormWizardModal.tsx +++ b/template/src/components/polling/components/modals/PollFormWizardModal.tsx @@ -112,5 +112,9 @@ export default function PollFormWizardModal() { } } - return {renderSwitch()}; + return ( + + {renderSwitch()} + + ); } diff --git a/template/src/components/polling/ui/BaseModal.tsx b/template/src/components/polling/ui/BaseModal.tsx index f4596a7f7..322c251fb 100644 --- a/template/src/components/polling/ui/BaseModal.tsx +++ b/template/src/components/polling/ui/BaseModal.tsx @@ -41,13 +41,18 @@ function BaseModalActions({children}: ActionProps) { type BaseModalProps = { visible?: boolean; children: ReactNode; + width?: number; }; -const BaseModal = ({children, visible = false}: BaseModalProps) => { +const BaseModal = ({ + children, + visible = false, + width = 650, +}: BaseModalProps) => { return ( - + {children} @@ -105,7 +110,7 @@ const style = StyleSheet.create({ shadowOpacity: 0.1, shadowRadius: 4, elevation: 5, - maxWidth: 800, + maxWidth: '90%', maxHeight: 800, overflow: 'scroll', }, From 5849acdeb331ff49560d63c83f7eb4aefe65d805 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Tue, 13 Aug 2024 12:37:49 +0530 Subject: [PATCH 027/174] add close icon on modals --- .../polling/components/PollCard.tsx | 189 ++++++++++++++++++ .../polling/components/PollSidebar.tsx | 4 +- .../components/form/DraftPollFormView.tsx | 7 +- .../components/form/PreviewPollFormView.tsx | 13 +- .../form/SelectNewPollTypeFormView.tsx | 12 +- .../components/modals/PollFormWizardModal.tsx | 21 +- .../polling/context/poll-context.tsx | 6 + .../src/components/polling/ui/BaseModal.tsx | 29 +-- 8 files changed, 258 insertions(+), 23 deletions(-) create mode 100644 template/src/components/polling/components/PollCard.tsx diff --git a/template/src/components/polling/components/PollCard.tsx b/template/src/components/polling/components/PollCard.tsx new file mode 100644 index 000000000..13b660220 --- /dev/null +++ b/template/src/components/polling/components/PollCard.tsx @@ -0,0 +1,189 @@ +import React from 'react'; +import {Text, View, StyleSheet, DimensionValue} from 'react-native'; +import ThemeConfig from '../../../theme'; +import TertiaryButton from '../../../atoms/TertiaryButton'; +import {PollItem, PollItemOptionItem} from '../context/poll-context'; + +interface PollListItemWithResultProps { + optionItem: PollItemOptionItem; + showYourVote?: boolean; +} + +function PollListItemWithResult({ + optionItem, + showYourVote = false, +}: PollListItemWithResultProps) { + return ( + + + {optionItem.text} + {showYourVote && ( + Your Response + )} + + {optionItem.percent} {optionItem.votes.length} + + + + + + + + + ); +} + +function PollListItemWithInput({optionItem}: PollListItemWithResultProps) { + // Later +} +// {pollItem}: {pollItem: PollItem} +function PollCard({pollItem}: {pollItem: PollItem}) { + return ( + + + + Active + + More + + + + + + {pollItem.question} + + + + + {pollItem.options.map((item: PollItemOptionItem) => ( + + ))} + + + + + + { + // show poll share modal + }} + /> + + + + + ); +} +const style = StyleSheet.create({ + fullWidth: { + alignSelf: 'stretch', + }, + pollItem: { + marginVertical: 12, + }, + pollCard: { + padding: 12, + display: 'flex', + flexDirection: 'column', + gap: 12, + alignSelf: 'stretch', + backgroundColor: $config.CARD_LAYER_3_COLOR, + borderRadius: 15, + }, + pollCardHeader: { + height: 24, + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + }, + pollCardHeaderText: { + color: '#04D000', + fontSize: ThemeConfig.FontSize.tiny, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontWeight: '600', + lineHeight: 12, + }, + pollCardContent: { + display: 'flex', + flexDirection: 'column', + gap: 12, + alignSelf: 'stretch', + alignItems: 'flex-start', + }, + pollCardContentQuestionText: { + color: $config.FONT_COLOR, + fontSize: ThemeConfig.FontSize.small, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontWeight: '600', + lineHeight: 16, + }, + pollCardFooter: {}, + pollCardFooterActions: { + alignSelf: 'flex-start', + }, + optionsList: { + backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, + borderRadius: 9, + paddingHorizontal: 12, + paddingVertical: 8, + display: 'flex', + flexDirection: 'column', + gap: 4, + marginVertical: 20, + }, + extraBottomPadding: { + paddingBottom: 32, + }, + optionListItem: { + display: 'flex', + flexDirection: 'column', + gap: 4, + }, + optionListItemHeader: { + display: 'flex', + flexDirection: 'row', + paddingHorizontal: 16, + paddingVertical: 8, + alignItems: 'center', + }, + optionListItemFooter: {}, + optionText: { + color: $config.FONT_COLOR, + fontSize: ThemeConfig.FontSize.normal, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontWeight: '400', + lineHeight: 24, + }, + yourResponseText: { + color: $config.SEMANTIC_SUCCESS, + fontSize: ThemeConfig.FontSize.tiny, + fontFamily: ThemeConfig.FontFamily.sansPro, + fontWeight: '600', + lineHeight: 12, + paddingLeft: 16, + }, + pushRight: { + marginLeft: 'auto', + }, + progressBar: { + height: 4, + borderRadius: 8, + backgroundColor: $config.CARD_LAYER_3_COLOR, + width: '100%', + }, + progressBarFill: { + borderRadius: 8, + backgroundColor: $config.PRIMARY_ACTION_BRAND_COLOR, + }, +}); + +export {PollCard}; diff --git a/template/src/components/polling/components/PollSidebar.tsx b/template/src/components/polling/components/PollSidebar.tsx index b6be76410..e59df55f0 100644 --- a/template/src/components/polling/components/PollSidebar.tsx +++ b/template/src/components/polling/components/PollSidebar.tsx @@ -13,7 +13,7 @@ import React from 'react'; import {Text, View, StyleSheet} from 'react-native'; import ThemeConfig from '../../../theme'; import PrimaryButton from '../../../atoms/PrimaryButton'; -import {PollResultCard} from './cards'; +import {PollCard} from './PollCard'; const PollSidebar = () => { return ( @@ -41,7 +41,7 @@ const PollSidebar = () => { Past Polls (01) - + diff --git a/template/src/components/polling/components/form/DraftPollFormView.tsx b/template/src/components/polling/components/form/DraftPollFormView.tsx index 61f5a2100..008488f21 100644 --- a/template/src/components/polling/components/form/DraftPollFormView.tsx +++ b/template/src/components/polling/components/form/DraftPollFormView.tsx @@ -4,6 +4,7 @@ import { BaseModalTitle, BaseModalContent, BaseModalActions, + BaseModalCloseIcon, } from '../../ui/BaseModal'; import ThemeConfig from '../../../../theme'; import LinkButton from '../../../../atoms/LinkButton'; @@ -25,6 +26,7 @@ interface Props { setForm: React.Dispatch>; onPreview: () => void; errors: Partial; + onClose: () => void; } export default function DraftPollFormView({ @@ -32,6 +34,7 @@ export default function DraftPollFormView({ setForm, onPreview, errors, + onClose, }: Props) { const handleInputChange = (field: string, value: string | boolean) => { setForm({ @@ -109,7 +112,9 @@ export default function DraftPollFormView({ return ( <> - + + + {/* Question section */} diff --git a/template/src/components/polling/components/form/PreviewPollFormView.tsx b/template/src/components/polling/components/form/PreviewPollFormView.tsx index 9a7ff96b8..f556f278d 100644 --- a/template/src/components/polling/components/form/PreviewPollFormView.tsx +++ b/template/src/components/polling/components/form/PreviewPollFormView.tsx @@ -4,6 +4,7 @@ import { BaseModalTitle, BaseModalContent, BaseModalActions, + BaseModalCloseIcon, } from '../../ui/BaseModal'; import ThemeConfig from '../../../../theme'; import TertiaryButton from '../../../../atoms/TertiaryButton'; @@ -16,12 +17,20 @@ interface Props { form: PollItem; onEdit: () => void; onSave: (launch: boolean) => void; + onClose: () => void; } -export default function PreviewPollFormView({form, onEdit, onSave}: Props) { +export default function PreviewPollFormView({ + form, + onEdit, + onSave, + onClose, +}: Props) { return ( <> - + + + {form.duration && ( diff --git a/template/src/components/polling/components/form/SelectNewPollTypeFormView.tsx b/template/src/components/polling/components/form/SelectNewPollTypeFormView.tsx index 73a6d5375..ae2e7a50c 100644 --- a/template/src/components/polling/components/form/SelectNewPollTypeFormView.tsx +++ b/template/src/components/polling/components/form/SelectNewPollTypeFormView.tsx @@ -1,6 +1,10 @@ import {Text, StyleSheet, View, TouchableOpacity} from 'react-native'; import React from 'react'; -import {BaseModalTitle, BaseModalContent} from '../../ui/BaseModal'; +import { + BaseModalTitle, + BaseModalContent, + BaseModalCloseIcon, +} from '../../ui/BaseModal'; import ThemeConfig from '../../../../theme'; import {PollKind} from '../../context/poll-context'; @@ -34,12 +38,16 @@ const newPollTypeConfig: newPollType[] = [ export default function SelectNewPollTypeFormView({ setType, + onClose, }: { setType: React.Dispatch>; + onClose: () => void; }) { return ( <> - + + + {newPollTypeConfig.map((item: newPollType) => ( diff --git a/template/src/components/polling/components/modals/PollFormWizardModal.tsx b/template/src/components/polling/components/modals/PollFormWizardModal.tsx index 83a7d68d6..1a2ffee14 100644 --- a/template/src/components/polling/components/modals/PollFormWizardModal.tsx +++ b/template/src/components/polling/components/modals/PollFormWizardModal.tsx @@ -17,7 +17,7 @@ import {filterObject} from '../../../../utils'; type FormWizardStep = 'SELECT' | 'DRAFT' | 'PREVIEW'; export default function PollFormWizardModal() { - const {polls, savePoll, sendPoll} = usePoll(); + const {polls, savePoll, sendPoll, closeCurrentModal} = usePoll(); const [step, setStep] = useState('SELECT'); const [type, setType] = useState(null); const [form, setForm] = useState(null); @@ -90,10 +90,19 @@ export default function PollFormWizardModal() { return true; }; + const onClose = () => { + setFormErrors(null); + setForm(null); + setType(null); + closeCurrentModal(); + }; + function renderSwitch() { switch (step) { case 'SELECT': - return ; + return ( + + ); case 'DRAFT': return ( ); case 'PREVIEW': return ( - + ); default: return <>; diff --git a/template/src/components/polling/context/poll-context.tsx b/template/src/components/polling/context/poll-context.tsx index 17ed65d68..a9dcd9959 100644 --- a/template/src/components/polling/context/poll-context.tsx +++ b/template/src/components/polling/context/poll-context.tsx @@ -219,6 +219,7 @@ interface PollContextValue { launchPollId: string; sendResponseToPoll: (item: PollItem, responses: string | string[]) => void; goToShareResponseModal: () => void; + closeCurrentModal: () => void; } const PollContext = createContext(null); @@ -305,6 +306,10 @@ function PollProvider({children}: {children: React.ReactNode}) { setCurrentModal(PollModalState.SHARE_POLL_RESULTS); }; + const closeCurrentModal = () => { + setCurrentModal(null); + }; + const value = { polls, dispatch, @@ -317,6 +322,7 @@ function PollProvider({children}: {children: React.ReactNode}) { launchPollId, sendResponseToPoll, goToShareResponseModal, + closeCurrentModal, }; return {children}; diff --git a/template/src/components/polling/ui/BaseModal.tsx b/template/src/components/polling/ui/BaseModal.tsx index 322c251fb..2c0ddc9ba 100644 --- a/template/src/components/polling/ui/BaseModal.tsx +++ b/template/src/components/polling/ui/BaseModal.tsx @@ -65,19 +65,22 @@ type BaseModalCloseIconProps = { }; const BaseModalCloseIcon = ({onClose}: BaseModalCloseIconProps) => { - - - ; + return ( + + + + ); }; export { BaseModal, From acd0192c16c057bc368a97c897127d73d7aac157 Mon Sep 17 00:00:00 2001 From: HariharanIT Date: Tue, 13 Aug 2024 16:03:08 +0530 Subject: [PATCH 028/174] Updated the more menu to inject custom items --- template/src/atoms/ActionMenu.tsx | 203 ++++++++---------- template/src/atoms/ToolbarPreset.tsx | 14 +- template/src/components/Controls.tsx | 26 +-- .../participants/UserActionMenuOptions.tsx | 20 +- .../src/pages/video-call/SidePanelHeader.tsx | 4 +- .../caption/CaptionContainer.tsx | 4 +- .../src/subComponents/chat/ChatActionMenu.tsx | 6 +- .../src/subComponents/chat/ImagePopup.tsx | 8 +- template/src/utils/common.tsx | 31 ++- 9 files changed, 158 insertions(+), 158 deletions(-) diff --git a/template/src/atoms/ActionMenu.tsx b/template/src/atoms/ActionMenu.tsx index fe0974a83..c2c7f027c 100644 --- a/template/src/atoms/ActionMenu.tsx +++ b/template/src/atoms/ActionMenu.tsx @@ -18,10 +18,10 @@ import ThemeConfig from '../theme'; import {isWebInternal} from '../utils/common'; import hexadecimalTransparency from '../utils/hexadecimalTransparency'; import Toggle from './Toggle'; -import {Either} from '../../agora-rn-uikit/src/Controls/types'; -import {ToolbarItemHide, ToolbarMoreMenuCustomItem} from './ToolbarPreset'; +import {ToolbarItemHide} from './ToolbarPreset'; export interface ActionMenuItem { + component?: React.ComponentType; componentName?: string; order?: number; isExternalIcon?: boolean; @@ -33,7 +33,7 @@ export interface ActionMenuItem { textColor: string; title: string; toggleStatus?: boolean; - callback: () => void; + onPress: () => void; onHoverCallback?: (isHovered: boolean) => void; onHoverContent?: JSX.Element; disabled?: boolean; @@ -50,7 +50,7 @@ export interface ActionMenuProps { left?: number; bottom?: number; }; - items: Either; + items: ActionMenuItem[]; hoverMode?: boolean; onHover?: (hover: boolean) => void; containerStyle?: ViewStyle; @@ -68,9 +68,7 @@ const ActionMenu = (props: ActionMenuProps) => { const renderItems = () => { return items?.map((item, index) => { - //rendering the custom item with default UI - const {title, onPress, iconBase64, componentName, hide = false} = item; - + const {hide = false} = item; if (typeof hide === 'boolean' && hide) { return null; } @@ -81,16 +79,58 @@ const ActionMenu = (props: ActionMenuProps) => { } } catch (error) {} - if (title && onPress) { - return ( - - {(isHovered: boolean) => ( - <> + const { + title, + component: CustomActionItem = null, + icon = '', + onHoverIcon, + isBase64Icon = false, + isExternalIcon = false, + externalIconString = '', + toggleStatus, + onPress = () => {}, + iconColor, + textColor, + disabled = false, + onHoverCallback = undefined, + onHoverContent = undefined, + iconSize = 20, + } = item; + return ( + + {(isHovered: boolean) => ( + <> + {/* {onHoverCallback && onHoverCallback(isHovered)} */} + {isHovered ? onHoverContent ? onHoverContent : <> : <>} + {CustomActionItem ? ( + + + + ) : ( { : //middle items don't need any border-radius styles.rowHoveredMiddleItems : {}, - false ? {opacity: 0.4} : {}, + disabled ? {opacity: 0.4} : {}, items?.length - 1 === index ? {borderBottomColor: 'transparent'} : {}, ]} onPress={onPress} - key={componentName + index}> - {iconBase64 ? ( - + key={icon + index}> + + {isExternalIcon ? ( + ) : ( + + )} + + + {title} + + {toggleStatus !== undefined && toggleStatus !== null ? ( + + ) : ( <> )} - {title} - - )} - - ); - } - - const { - icon = '', - onHoverIcon, - isBase64Icon = false, - isExternalIcon = false, - externalIconString = '', - toggleStatus, - callback, - iconColor, - textColor, - disabled = false, - onHoverCallback = undefined, - onHoverContent = undefined, - iconSize = 20, - } = item; - return ( - - {(isHovered: boolean) => ( - <> - {/* {onHoverCallback && onHoverCallback(isHovered)} */} - {isHovered ? onHoverContent ? onHoverContent : <> : <>} - - - {isExternalIcon ? ( - - ) : ( - - )} - - - {title} - - {toggleStatus !== undefined && toggleStatus !== null ? ( - - - - ) : ( - <> - )} - + )} )} diff --git a/template/src/atoms/ToolbarPreset.tsx b/template/src/atoms/ToolbarPreset.tsx index 0d12b6293..2f88757a3 100644 --- a/template/src/atoms/ToolbarPreset.tsx +++ b/template/src/atoms/ToolbarPreset.tsx @@ -45,10 +45,20 @@ export type TopToolbarDefaultKeys = | 'participant' | 'settings'; -export type ToolbarMoreButtonFields = { +export type ToolbarMoreButtonDefaultFields = { [key in MoreButtonDefaultKeys]?: { hide?: ToolbarItemHide; order?: number; + component?: React.ComponentType; + onPress?: () => void; + }; +}; +export type ToolbarMoreButtonCustomFields = { + [key: string]: { + hide?: ToolbarItemHide; + order?: number; + component?: React.ComponentType; + onPress?: () => void; }; }; @@ -63,7 +73,7 @@ export interface ToolbarDefaultItem { order?: number; } export interface ToolbarMoreDefaultItem extends ToolbarDefaultItem { - fields?: ToolbarMoreButtonFields; + fields?: ToolbarMoreButtonDefaultFields | ToolbarMoreButtonCustomFields; } export type ToolbarPresetAlign = 'top' | 'bottom' | 'right' | 'left'; diff --git a/template/src/components/Controls.tsx b/template/src/components/Controls.tsx index 620bd56ad..a083233fb 100644 --- a/template/src/components/Controls.tsx +++ b/template/src/components/Controls.tsx @@ -332,7 +332,7 @@ const MoreButton = (props: {fields: ToolbarMoreButtonFields}) => { textColor: $config.FONT_COLOR, title: noiseCancellationLabel, //isNoiseSupressionEnabled === ToggleState.enabled - callback: () => { + onPress: () => { setActionMenuVisible(false); setNoiseSupression(p => !p); }, @@ -362,7 +362,7 @@ const MoreButton = (props: {fields: ToolbarMoreButtonFields}) => { textColor: $config.FONT_COLOR, //title: `${isVBActive ? 'Hide' : 'Show'} Virtual Background`, title: virtualBackgroundLabel, - callback: () => { + onPress: () => { setActionMenuVisible(false); toggleVB(); }, @@ -464,7 +464,7 @@ const MoreButton = (props: {fields: ToolbarMoreButtonFields}) => { iconColor: $config.SECONDARY_ACTION_COLOR, textColor: $config.FONT_COLOR, title: whiteboardLabel(whiteboardActive), - callback: () => { + onPress: () => { setActionMenuVisible(false); toggleWhiteboard(whiteboardActive, true); }, @@ -486,7 +486,7 @@ const MoreButton = (props: {fields: ToolbarMoreButtonFields}) => { (isHost || (!isHost && isSTTActive)) ), title: captionLabel(isCaptionON), - callback: () => { + onPress: () => { setActionMenuVisible(false); STT_clicked.current = !isCaptionON ? 'caption' : null; if (isSTTError) { @@ -517,7 +517,7 @@ const MoreButton = (props: {fields: ToolbarMoreButtonFields}) => { (isHost || (!isHost && isSTTActive)) ), title: transcriptLabel(isTranscriptON), - callback: () => { + onPress: () => { setActionMenuVisible(false); STT_clicked.current = !isTranscriptON ? 'transcript' : null; if (isSTTError) { @@ -549,7 +549,7 @@ const MoreButton = (props: {fields: ToolbarMoreButtonFields}) => { iconColor: $config.SECONDARY_ACTION_COLOR, textColor: $config.FONT_COLOR, title: viewRecordingsLabel, - callback: () => { + onPress: () => { toggleVRModal(); }, }); @@ -565,7 +565,7 @@ const MoreButton = (props: {fields: ToolbarMoreButtonFields}) => { iconColor: $config.SECONDARY_ACTION_COLOR, textColor: $config.FONT_COLOR, title: peopleLabel, - callback: () => { + onPress: () => { setActionMenuVisible(false); setSidePanel(SidePanelType.Participants); }, @@ -581,7 +581,7 @@ const MoreButton = (props: {fields: ToolbarMoreButtonFields}) => { iconColor: $config.SECONDARY_ACTION_COLOR, textColor: $config.FONT_COLOR, title: chatLabel, - callback: () => { + onPress: () => { setActionMenuVisible(false); setChatType(ChatType.Group); setSidePanel(SidePanelType.Chat); @@ -615,7 +615,7 @@ const MoreButton = (props: {fields: ToolbarMoreButtonFields}) => { ? $config.SEMANTIC_ERROR : $config.FONT_COLOR, title: screenShareButton(isScreenshareActive), - callback: () => { + onPress: () => { setActionMenuVisible(false); isScreenshareActive ? stopScreenshare() : startScreenshare(); }, @@ -638,7 +638,7 @@ const MoreButton = (props: {fields: ToolbarMoreButtonFields}) => { ? $config.SEMANTIC_ERROR : $config.FONT_COLOR, title: recordingButton(isRecordingActive), - callback: () => { + onPress: () => { setActionMenuVisible(false); if (!isRecordingActive) { startRecording(); @@ -662,7 +662,7 @@ const MoreButton = (props: {fields: ToolbarMoreButtonFields}) => { iconColor: $config.SECONDARY_ACTION_COLOR, textColor: $config.FONT_COLOR, title: layoutLabel, - callback: () => { + onPress: () => { //setShowLayoutOption(true); }, onHoverCallback: isHovered => { @@ -693,7 +693,7 @@ const MoreButton = (props: {fields: ToolbarMoreButtonFields}) => { iconColor: $config.SECONDARY_ACTION_COLOR, textColor: $config.FONT_COLOR, title: inviteLabel, - callback: () => { + onPress: () => { setActionMenuVisible(false); setShowInvitePopup(true); }, @@ -709,7 +709,7 @@ const MoreButton = (props: {fields: ToolbarMoreButtonFields}) => { iconColor: $config.SECONDARY_ACTION_COLOR, textColor: $config.FONT_COLOR, title: settingsLabel, - callback: () => { + onPress: () => { setActionMenuVisible(false); setSidePanel(SidePanelType.Settings); }, diff --git a/template/src/components/participants/UserActionMenuOptions.tsx b/template/src/components/participants/UserActionMenuOptions.tsx index 99b192a35..d43ec7b52 100644 --- a/template/src/components/participants/UserActionMenuOptions.tsx +++ b/template/src/components/participants/UserActionMenuOptions.tsx @@ -171,7 +171,7 @@ export default function UserActionMenuOptionsOptions( : user.uid === getWhiteboardUid() ? viewWhiteboardLabel : viewInLargeLabel, - callback: () => { + onPress: () => { setActionMenuVisible(false); dispatch({ type: 'UserPin', @@ -194,7 +194,7 @@ export default function UserActionMenuOptionsOptions( ? removeFromTopLabel : pinToTopLabel : pinToTopLabel, - callback: () => { + onPress: () => { setActionMenuVisible(false); dispatch({ type: 'UserSecondaryPin', @@ -225,7 +225,7 @@ export default function UserActionMenuOptionsOptions( iconColor: $config.SECONDARY_ACTION_COLOR, textColor: $config.SECONDARY_ACTION_COLOR, title: messagePrivatelyLabel, - callback: () => { + onPress: () => { setActionMenuVisible(false); openPrivateChat(user.uid); }, @@ -256,7 +256,7 @@ export default function UserActionMenuOptionsOptions( iconColor: $config.SECONDARY_ACTION_COLOR, textColor: $config.SECONDARY_ACTION_COLOR, title: audioLabel(user.audio), - callback: () => { + onPress: () => { setActionMenuVisible(false); user.audio ? setShowAudioMuteModal(true) @@ -270,7 +270,7 @@ export default function UserActionMenuOptionsOptions( iconColor: $config.SECONDARY_ACTION_COLOR, textColor: $config.SECONDARY_ACTION_COLOR, title: videoLabel(user.video), - callback: () => { + onPress: () => { setActionMenuVisible(false); user.video ? setShowVideoMuteModal(true) @@ -294,7 +294,7 @@ export default function UserActionMenuOptionsOptions( iconColor: $config.SECONDARY_ACTION_COLOR, textColor: $config.SECONDARY_ACTION_COLOR, title: addAsPresenterLabel, - callback: () => { + onPress: () => { setActionMenuVisible(false); promoteAudienceAsCoHost(user.uid); }, @@ -313,7 +313,7 @@ export default function UserActionMenuOptionsOptions( iconColor: $config.SECONDARY_ACTION_COLOR, textColor: $config.SECONDARY_ACTION_COLOR, title: removeAsPresenterLabel, - callback: () => { + onPress: () => { setActionMenuVisible(false); events.send( LiveStreamControlMessageEnum.raiseHandRequestRejected, @@ -331,7 +331,7 @@ export default function UserActionMenuOptionsOptions( iconColor: $config.SEMANTIC_ERROR, textColor: $config.SEMANTIC_ERROR, title: removeFromRoomLabel, - callback: () => { + onPress: () => { setActionMenuVisible(false); setRemoveMeetingPopupVisible(true); }, @@ -349,7 +349,7 @@ export default function UserActionMenuOptionsOptions( iconColor: $config.SECONDARY_ACTION_COLOR, textColor: $config.SECONDARY_ACTION_COLOR, title: changeNameLabel, - callback: () => { + onPress: () => { setFocus(prevState => { return { ...prevState, @@ -375,7 +375,7 @@ export default function UserActionMenuOptionsOptions( localuid === user?.parentUid ? stopScreenShareLabel : removeScreenShareLabel, - callback: () => { + onPress: () => { setActionMenuVisible(false); //for local user directly stop the screenshare if (localuid === user.parentUid) { diff --git a/template/src/pages/video-call/SidePanelHeader.tsx b/template/src/pages/video-call/SidePanelHeader.tsx index 667e039f3..7c302aeb8 100644 --- a/template/src/pages/video-call/SidePanelHeader.tsx +++ b/template/src/pages/video-call/SidePanelHeader.tsx @@ -281,7 +281,7 @@ const TranscriptHeaderActionMenu = (props: TranscriptHeaderActionMenuProps) => { textColor: $config.FONT_COLOR, title: changeSpokenLanguage + ' ', disabled: isLangChangeInProgress, - callback: () => { + onPress: () => { setActionMenuVisible(false); setLanguagePopup(true); }, @@ -293,7 +293,7 @@ const TranscriptHeaderActionMenu = (props: TranscriptHeaderActionMenuProps) => { textColor: $config.FONT_COLOR, title: downloadTranscriptLabel, disabled: meetingTranscript.length === 0, - callback: () => { + onPress: () => { downloadTranscript(); setActionMenuVisible(false); }, diff --git a/template/src/subComponents/caption/CaptionContainer.tsx b/template/src/subComponents/caption/CaptionContainer.tsx index d6c240d51..026d5c3d2 100644 --- a/template/src/subComponents/caption/CaptionContainer.tsx +++ b/template/src/subComponents/caption/CaptionContainer.tsx @@ -218,7 +218,7 @@ const CaptionsActionMenu = (props: CaptionsActionMenuProps) => { textColor: $config.FONT_COLOR, title: changeSpokenLangLabel + ' ', disabled: isLangChangeInProgress, - callback: () => { + onPress: () => { setActionMenuVisible(false); setLanguagePopup(true); }, @@ -229,7 +229,7 @@ const CaptionsActionMenu = (props: CaptionsActionMenuProps) => { iconColor: $config.SECONDARY_ACTION_COLOR, textColor: $config.FONT_COLOR, title: hideCaptionLabel, - callback: () => { + onPress: () => { setActionMenuVisible(false); setIsCaptionON(false); }, diff --git a/template/src/subComponents/chat/ChatActionMenu.tsx b/template/src/subComponents/chat/ChatActionMenu.tsx index a95158e67..83a0059e1 100644 --- a/template/src/subComponents/chat/ChatActionMenu.tsx +++ b/template/src/subComponents/chat/ChatActionMenu.tsx @@ -89,7 +89,7 @@ export const ChatActionMenu = (props: CaptionsActionMenuProps) => { iconColor: $config.SECONDARY_ACTION_COLOR, textColor: $config.FONT_COLOR, title: useString(chatActionMenuDownloadText)(), - callback: () => { + onPress: () => { downloadAttachment(fileName, fileUrl); setActionMenuVisible(false); }, @@ -100,7 +100,7 @@ export const ChatActionMenu = (props: CaptionsActionMenuProps) => { iconColor: $config.SECONDARY_ACTION_COLOR, textColor: $config.FONT_COLOR, title: useString(chatActionMenuCopyLinkText)(), - callback: () => { + onPress: () => { Clipboard.setString(fileUrl); setActionMenuVisible(false); }, @@ -111,7 +111,7 @@ export const ChatActionMenu = (props: CaptionsActionMenuProps) => { textColor: $config.SEMANTIC_ERROR, iconSize: 24, title: useString(chatActionMenuDeleteText)(), - callback: () => { + onPress: () => { if (isLocal) { // confirm dialog : user is deleting for all setShowDeleteMessageModal(true); diff --git a/template/src/subComponents/chat/ImagePopup.tsx b/template/src/subComponents/chat/ImagePopup.tsx index 007ec088e..242edf0f4 100644 --- a/template/src/subComponents/chat/ImagePopup.tsx +++ b/template/src/subComponents/chat/ImagePopup.tsx @@ -167,7 +167,7 @@ const ImagePopup = (props: ImagePopupProps) => { icon: 'delete', iconColor: $config.SECONDARY_ACTION_COLOR, iconSize: 24, - callback: () => { + onPress: () => { if (isLocal) { setShowDeleteMessageModal(true); } else { @@ -184,7 +184,7 @@ const ImagePopup = (props: ImagePopupProps) => { icon: 'download', iconColor: $config.SECONDARY_ACTION_COLOR, iconSize: 24, - callback: () => { + onPress: () => { downloadAttachment(fileName, imageUrl); }, }, @@ -192,7 +192,7 @@ const ImagePopup = (props: ImagePopupProps) => { icon: 'close', iconColor: $config.SECONDARY_ACTION_COLOR, iconSize: 20, - callback: () => { + onPress: () => { setModalVisible(false); }, }, @@ -221,7 +221,7 @@ const ImagePopup = (props: ImagePopupProps) => { name: obj.icon as keyof IconsInterface, tintColor: obj.iconColor, }} - onPress={obj.callback} + onPress={obj.onPress} /> ))} diff --git a/template/src/utils/common.tsx b/template/src/utils/common.tsx index 9ea2a4287..144b78b01 100644 --- a/template/src/utils/common.tsx +++ b/template/src/utils/common.tsx @@ -358,19 +358,32 @@ const updateToolbarDefaultConfig = (data, defaultItemsConfig) => { }); }; -const MergeMoreButtonFields = (data, customizedData) => { - const keys = Object.keys(customizedData); - return data?.map(i => { - if (i?.componentName && keys?.indexOf(i?.componentName) !== -1) { - return { +function MergeMoreButtonFields(sourceArray, updateObject) { + const updateKeys = Object.keys(updateObject); + let result = []; + let sourceKeys = []; + sourceArray.forEach(i => { + sourceKeys.push(i?.componentName); + if (updateKeys?.indexOf(i?.componentName) !== -1) { + result.push({ ...i, - ...customizedData[i?.componentName], - }; + ...updateObject[i?.componentName], + }); } else { - return i; + result.push(i); } }); -}; + //inject new value + updateKeys.forEach(i => { + if (sourceKeys?.indexOf(i) === -1) { + result.push({ + ...updateObject[i], + componentName: i, + }); + } + }); + return result; +} export { getSessionId, From ef6db263af48c16ec823228553302e1c574aafb3 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Tue, 13 Aug 2024 19:25:46 +0530 Subject: [PATCH 029/174] delete polling folder from inside the src --- .../components/polling/components/Poll.tsx | 40 -- .../polling/components/PollCard.tsx | 189 --------- .../polling/components/PollSidebar.tsx | 113 ----- .../polling/components/PollTimer.tsx | 53 --- .../components/polling/components/cards.tsx | 180 -------- .../components/form/DraftPollFormView.tsx | 392 ----------------- .../components/form/PreviewPollFormView.tsx | 155 ------- .../form/SelectNewPollTypeFormView.tsx | 119 ------ .../polling/components/form/form-config.ts | 118 ------ .../components/form/poll-response-forms.tsx | 397 ------------------ .../components/modals/PollFormWizardModal.tsx | 135 ------ .../modals/PollResponseFormModal.tsx | 57 --- .../components/modals/SharePollModal.tsx | 197 --------- .../polling/context/poll-context.tsx | 349 --------------- .../polling/context/poll-events.tsx | 118 ------ .../polling/context/poll-form-context.tsx | 339 --------------- .../polling/hook/useCountdownTimer.tsx | 45 -- .../src/components/polling/ui/BaseModal.tsx | 158 ------- .../components/polling/ui/BaseRadioButton.tsx | 78 ---- 19 files changed, 3232 deletions(-) delete mode 100644 template/src/components/polling/components/Poll.tsx delete mode 100644 template/src/components/polling/components/PollCard.tsx delete mode 100644 template/src/components/polling/components/PollSidebar.tsx delete mode 100644 template/src/components/polling/components/PollTimer.tsx delete mode 100644 template/src/components/polling/components/cards.tsx delete mode 100644 template/src/components/polling/components/form/DraftPollFormView.tsx delete mode 100644 template/src/components/polling/components/form/PreviewPollFormView.tsx delete mode 100644 template/src/components/polling/components/form/SelectNewPollTypeFormView.tsx delete mode 100644 template/src/components/polling/components/form/form-config.ts delete mode 100644 template/src/components/polling/components/form/poll-response-forms.tsx delete mode 100644 template/src/components/polling/components/modals/PollFormWizardModal.tsx delete mode 100644 template/src/components/polling/components/modals/PollResponseFormModal.tsx delete mode 100644 template/src/components/polling/components/modals/SharePollModal.tsx delete mode 100644 template/src/components/polling/context/poll-context.tsx delete mode 100644 template/src/components/polling/context/poll-events.tsx delete mode 100644 template/src/components/polling/context/poll-form-context.tsx delete mode 100644 template/src/components/polling/hook/useCountdownTimer.tsx delete mode 100644 template/src/components/polling/ui/BaseModal.tsx delete mode 100644 template/src/components/polling/ui/BaseRadioButton.tsx diff --git a/template/src/components/polling/components/Poll.tsx b/template/src/components/polling/components/Poll.tsx deleted file mode 100644 index a9a8bcc99..000000000 --- a/template/src/components/polling/components/Poll.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; -import {PollModalState, PollProvider, usePoll} from '../context/poll-context'; -import PollFormWizardModal from './modals/PollFormWizardModal'; -import {PollEventsProvider, PollEventsSubscriber} from '../context/poll-events'; -import PollResponseFormModal from './modals/PollResponseFormModal'; -import SharePollModal from './modals/SharePollModal'; -// const DraftPollModal = React.lazy(() => import('./DraftPollModal')); -// const RespondToPollModal = React.lazy(() => import('./RespondToPollModal')); -// const SharePollResultModal = React.lazy(() => import('./SharePollResultModal')); - -function Poll({children}: {children?: React.ReactNode}) { - return ( - - - {children} - - - - ); -} - -function PollModals() { - const {currentModal, launchPollId, polls} = usePoll(); - console.log('supriya polls data chnaged: ', polls); - return ( - <> - {currentModal === PollModalState.DRAFT_POLL && } - {currentModal === PollModalState.RESPOND_TO_POLL && launchPollId && ( - - )} - {currentModal === PollModalState.SHARE_POLL_RESULTS && } - - // Loading...}> - // {activePollModal === PollAction.DraftPoll && } - // {activePollModal === PollAction.RespondToPoll && } - // {activePollModal === PollAction.SharePollResult && } - // - ); -} -export default Poll; diff --git a/template/src/components/polling/components/PollCard.tsx b/template/src/components/polling/components/PollCard.tsx deleted file mode 100644 index 13b660220..000000000 --- a/template/src/components/polling/components/PollCard.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import React from 'react'; -import {Text, View, StyleSheet, DimensionValue} from 'react-native'; -import ThemeConfig from '../../../theme'; -import TertiaryButton from '../../../atoms/TertiaryButton'; -import {PollItem, PollItemOptionItem} from '../context/poll-context'; - -interface PollListItemWithResultProps { - optionItem: PollItemOptionItem; - showYourVote?: boolean; -} - -function PollListItemWithResult({ - optionItem, - showYourVote = false, -}: PollListItemWithResultProps) { - return ( - - - {optionItem.text} - {showYourVote && ( - Your Response - )} - - {optionItem.percent} {optionItem.votes.length} - - - - - - - - - ); -} - -function PollListItemWithInput({optionItem}: PollListItemWithResultProps) { - // Later -} -// {pollItem}: {pollItem: PollItem} -function PollCard({pollItem}: {pollItem: PollItem}) { - return ( - - - - Active - - More - - - - - - {pollItem.question} - - - - - {pollItem.options.map((item: PollItemOptionItem) => ( - - ))} - - - - - - { - // show poll share modal - }} - /> - - - - - ); -} -const style = StyleSheet.create({ - fullWidth: { - alignSelf: 'stretch', - }, - pollItem: { - marginVertical: 12, - }, - pollCard: { - padding: 12, - display: 'flex', - flexDirection: 'column', - gap: 12, - alignSelf: 'stretch', - backgroundColor: $config.CARD_LAYER_3_COLOR, - borderRadius: 15, - }, - pollCardHeader: { - height: 24, - display: 'flex', - flexDirection: 'row', - justifyContent: 'space-between', - }, - pollCardHeaderText: { - color: '#04D000', - fontSize: ThemeConfig.FontSize.tiny, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontWeight: '600', - lineHeight: 12, - }, - pollCardContent: { - display: 'flex', - flexDirection: 'column', - gap: 12, - alignSelf: 'stretch', - alignItems: 'flex-start', - }, - pollCardContentQuestionText: { - color: $config.FONT_COLOR, - fontSize: ThemeConfig.FontSize.small, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontWeight: '600', - lineHeight: 16, - }, - pollCardFooter: {}, - pollCardFooterActions: { - alignSelf: 'flex-start', - }, - optionsList: { - backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, - borderRadius: 9, - paddingHorizontal: 12, - paddingVertical: 8, - display: 'flex', - flexDirection: 'column', - gap: 4, - marginVertical: 20, - }, - extraBottomPadding: { - paddingBottom: 32, - }, - optionListItem: { - display: 'flex', - flexDirection: 'column', - gap: 4, - }, - optionListItemHeader: { - display: 'flex', - flexDirection: 'row', - paddingHorizontal: 16, - paddingVertical: 8, - alignItems: 'center', - }, - optionListItemFooter: {}, - optionText: { - color: $config.FONT_COLOR, - fontSize: ThemeConfig.FontSize.normal, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontWeight: '400', - lineHeight: 24, - }, - yourResponseText: { - color: $config.SEMANTIC_SUCCESS, - fontSize: ThemeConfig.FontSize.tiny, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontWeight: '600', - lineHeight: 12, - paddingLeft: 16, - }, - pushRight: { - marginLeft: 'auto', - }, - progressBar: { - height: 4, - borderRadius: 8, - backgroundColor: $config.CARD_LAYER_3_COLOR, - width: '100%', - }, - progressBarFill: { - borderRadius: 8, - backgroundColor: $config.PRIMARY_ACTION_BRAND_COLOR, - }, -}); - -export {PollCard}; diff --git a/template/src/components/polling/components/PollSidebar.tsx b/template/src/components/polling/components/PollSidebar.tsx deleted file mode 100644 index e59df55f0..000000000 --- a/template/src/components/polling/components/PollSidebar.tsx +++ /dev/null @@ -1,113 +0,0 @@ -/* -******************************************** - Copyright © 2021 Agora Lab, Inc., all rights reserved. - AppBuilder and all associated components, source code, APIs, services, and documentation - (the “Materials”) are owned by Agora Lab, Inc. and its licensors. The Materials may not be - accessed, used, modified, or distributed for any purpose without a license from Agora Lab, Inc. - Use without a license or in violation of any license terms and conditions (including use for - any purpose competitive to Agora Lab, Inc.’s business) is strictly prohibited. For more - information visit https://appbuilder.agora.io. -********************************************* -*/ -import React from 'react'; -import {Text, View, StyleSheet} from 'react-native'; -import ThemeConfig from '../../../theme'; -import PrimaryButton from '../../../atoms/PrimaryButton'; -import {PollCard} from './PollCard'; - -const PollSidebar = () => { - return ( - - {/* Header */} - - - - Create a new poll and boost interaction with your audience members - now! - - - {}} - text="Create Poll" - /> - - - - - - - Past Polls (01) - - - - - - - ); -}; - -const style = StyleSheet.create({ - pollSidebar: { - backgroundColor: $config.CARD_LAYER_1_COLOR, - }, - headerSection: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - padding: 20, - }, - headerCard: { - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'flex-start', - gap: 16, - padding: 20, - backgroundColor: $config.CARD_LAYER_3_COLOR, - borderRadius: 15, - }, - bodyXSmallText: { - color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.medium, - fontSize: ThemeConfig.FontSize.small, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontWeight: '400', - lineHeight: 16, - }, - btnContainer: { - minWidth: 150, - height: 36, - borderRadius: 4, - paddingVertical: 10, - paddingHorizontal: 8, - }, - btnText: { - color: $config.FONT_COLOR, - fontSize: ThemeConfig.FontSize.small, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontWeight: '600', - textTransform: 'capitalize', - }, - separator: { - paddingVertical: 24, - height: 1, - marginHorizontal: 16, - display: 'flex', - background: $config.INPUT_FIELD_BORDER_COLOR, - }, - bodySection: { - paddingHorizontal: 16, - }, - bodySectionTitleText: { - color: $config.FONT_COLOR, - fontSize: ThemeConfig.FontSize.tiny, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontWeight: '600', - lineHeight: 12, - }, - pollList: {}, -}); - -export default PollSidebar; diff --git a/template/src/components/polling/components/PollTimer.tsx b/template/src/components/polling/components/PollTimer.tsx deleted file mode 100644 index babe3aea2..000000000 --- a/template/src/components/polling/components/PollTimer.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, {useEffect} from 'react'; -import {Text, View, StyleSheet} from 'react-native'; -import {useCountdown} from '../hook/useCountdownTimer'; -import ThemeConfig from '../../../theme'; - -interface Props { - expiresAt: number; - setFreezeForm?: React.Dispatch>; -} - -const padZero = (value: number) => { - return value.toString().padStart(2, '0'); -}; - -export default function PollTimer({expiresAt, setFreezeForm}: Props) { - const [days, hours, minutes, seconds] = useCountdown(expiresAt); - - const getTime = () => { - if (days) { - return `${padZero(days)} : ${padZero(hours)} : ${padZero( - minutes, - )} : ${padZero(seconds)}`; - } - if (hours) { - return `${padZero(hours)} : ${padZero(minutes)} : ${padZero(seconds)}`; - } - if (minutes || seconds) { - return `${padZero(minutes)} : ${padZero(seconds)}`; - } - return '00 : 00'; - }; - - useEffect(() => { - if (days + hours + minutes + seconds === 0) { - setFreezeForm(true); - } - }, [days, hours, minutes, seconds, setFreezeForm]); - - return ( - - {getTime()} - - ); -} - -export const style = StyleSheet.create({ - timer: { - color: $config.SEMANTIC_WARNING, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontSize: 16, - lineHeight: 20, - }, -}); diff --git a/template/src/components/polling/components/cards.tsx b/template/src/components/polling/components/cards.tsx deleted file mode 100644 index 9d87581c2..000000000 --- a/template/src/components/polling/components/cards.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import React from 'react'; -import {Text, View, StyleSheet, DimensionValue} from 'react-native'; -import ThemeConfig from '../../../theme'; -import TertiaryButton from '../../../atoms/TertiaryButton'; -import {PollItemOptionItem} from '../context/poll-context'; - -interface PollListItemWithResultProps { - options?: PollItemOptionItem[]; - showYourVote?: boolean; -} - -function PollListItemWithResult({ - options = [], - showYourVote = false, -}: PollListItemWithResultProps) { - return ( - - - Great - {showYourVote && ( - Your Response - )} - - {/* {option.percent} {option.votes.lastIndexOf} */} - 75% (15) - - - - - - - - - ); -} - -function PollResultCard() { - return ( - - - - Active - - More - - - - - - How confident do you feel about your javascript skills after - today’s session? - - - - - - - - - - - {}} /> - - - - - ); -} -const style = StyleSheet.create({ - fullWidth: { - alignSelf: 'stretch', - }, - pollItem: { - marginVertical: 12, - }, - pollCard: { - padding: 12, - display: 'flex', - flexDirection: 'column', - gap: 12, - alignSelf: 'stretch', - backgroundColor: $config.CARD_LAYER_3_COLOR, - borderRadius: 15, - }, - pollCardHeader: { - height: 24, - display: 'flex', - flexDirection: 'row', - justifyContent: 'space-between', - }, - pollCardHeaderText: { - color: '#04D000', - fontSize: ThemeConfig.FontSize.tiny, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontWeight: '600', - lineHeight: 12, - }, - pollCardContent: { - display: 'flex', - flexDirection: 'column', - gap: 12, - alignSelf: 'stretch', - alignItems: 'flex-start', - }, - pollCardContentQuestionText: { - color: $config.FONT_COLOR, - fontSize: ThemeConfig.FontSize.small, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontWeight: '600', - lineHeight: 16, - }, - pollCardFooter: {}, - pollCardFooterActions: { - alignSelf: 'flex-start', - }, - optionsList: { - backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, - borderRadius: 9, - paddingHorizontal: 12, - paddingVertical: 8, - display: 'flex', - flexDirection: 'column', - gap: 4, - marginVertical: 20, - }, - extraBottomPadding: { - paddingBottom: 32, - }, - optionListItem: { - display: 'flex', - flexDirection: 'column', - gap: 4, - }, - optionListItemHeader: { - display: 'flex', - flexDirection: 'row', - paddingHorizontal: 16, - paddingVertical: 8, - alignItems: 'center', - }, - optionListItemFooter: {}, - optionText: { - color: $config.FONT_COLOR, - fontSize: ThemeConfig.FontSize.normal, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontWeight: '400', - lineHeight: 24, - }, - yourResponseText: { - color: $config.SEMANTIC_SUCCESS, - fontSize: ThemeConfig.FontSize.tiny, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontWeight: '600', - lineHeight: 12, - paddingLeft: 16, - }, - pushRight: { - marginLeft: 'auto', - }, - progressBar: { - height: 4, - borderRadius: 8, - backgroundColor: $config.CARD_LAYER_3_COLOR, - width: '100%', - }, - progressBarFill: { - borderRadius: 8, - backgroundColor: $config.PRIMARY_ACTION_BRAND_COLOR, - }, -}); - -export {PollResultCard}; diff --git a/template/src/components/polling/components/form/DraftPollFormView.tsx b/template/src/components/polling/components/form/DraftPollFormView.tsx deleted file mode 100644 index 008488f21..000000000 --- a/template/src/components/polling/components/form/DraftPollFormView.tsx +++ /dev/null @@ -1,392 +0,0 @@ -import {Text, View, StyleSheet, TextInput} from 'react-native'; -import React from 'react'; -import { - BaseModalTitle, - BaseModalContent, - BaseModalActions, - BaseModalCloseIcon, -} from '../../ui/BaseModal'; -import ThemeConfig from '../../../../theme'; -import LinkButton from '../../../../atoms/LinkButton'; -import Checkbox from '../../../../atoms/Checkbox'; -import IconButton from '../../../../atoms/IconButton'; -import PrimaryButton from '../../../../atoms/PrimaryButton'; -import {PollFormErrors, PollItem, PollKind} from '../../context/poll-context'; -import {nanoid} from 'nanoid'; - -function FormTitle({title}: {title: string}) { - return ( - - {title} - - ); -} -interface Props { - form: PollItem; - setForm: React.Dispatch>; - onPreview: () => void; - errors: Partial; - onClose: () => void; -} - -export default function DraftPollFormView({ - form, - setForm, - onPreview, - errors, - onClose, -}: Props) { - const handleInputChange = (field: string, value: string | boolean) => { - setForm({ - ...form, - [field]: value, - }); - }; - - const handleCheckboxChange = (field: keyof typeof form, value: boolean) => { - setForm({ - ...form, - [field]: value, - }); - }; - - const updateFormOption = ( - action: 'update' | 'delete' | 'add', - value: string, - index: number, - ) => { - if (action === 'add') { - setForm({ - ...form, - options: [ - ...form.options, - { - text: '', - value: '', - votes: [], - percent: '', - }, - ], - }); - } - if (action === 'update') { - setForm({ - ...form, - options: form.options.map((option, i) => { - if (i === index) { - const lowerText = value - .replace(/\s+/g, '-') - .toLowerCase() - .concat('-') - .concat(nanoid(2)); - return { - ...option, - text: value, - value: lowerText, - votes: [], - }; - } - return option; - }), - }); - } - if (action === 'delete') { - setForm({ - ...form, - options: form.options.filter((option, i) => i !== index), - }); - } - }; - - const getTitle = (type: PollKind) => { - if (type === PollKind.MCQ) { - return 'Multiple Choice'; - } - if (type === PollKind.OPEN_ENDED) { - return 'Open Ended Poll'; - } - if (type === PollKind.YES_NO) { - return 'Yes/No'; - } - }; - - return ( - <> - - - - - {/* Question section */} - - - - - { - handleInputChange('question', text); - }} - placeholder="Enter poll question here..." - placeholderTextColor={ - $config.FONT_COLOR + ThemeConfig.EmphasisPlus.low - } - /> - {errors?.question && ( - {errors.question.message} - )} - - - {/* Options section */} - {form.type === PollKind.MCQ || form.type === PollKind.YES_NO ? ( - - - - {form.type === PollKind.MCQ ? ( - <> - {form.options.map((option, index) => ( - - {index + 1} - { - updateFormOption('update', text, index); - }} - placeholder="Add text here..." - placeholderTextColor={ - $config.FONT_COLOR + ThemeConfig.EmphasisPlus.low - } - /> - - { - updateFormOption('delete', option.text, index); - }} - /> - - - ))} - - { - updateFormOption('add', null, null); - }} - /> - - {errors?.options && ( - - {errors.options.message} - - )} - - ) : ( - <> - )} - {form.type === PollKind.YES_NO ? ( - <> - - Yes - - - No - - - ) : ( - <> - )} - - - ) : ( - <> - )} - {/* Sections templete */} - - - - {form.type === PollKind.MCQ ? ( - { - handleCheckboxChange( - 'multiple_response', - !form.multiple_response, - ); - }} - /> - ) : ( - <> - )} - - - { - handleCheckboxChange('share', !form.share); - }} - /> - - - { - handleCheckboxChange('duration', !form.duration); - }} - /> - - - - - - - - { - onPreview(); - }} - text="Preview" - /> - - - - ); -} - -export const style = StyleSheet.create({ - createPollBox: { - display: 'flex', - flexDirection: 'column', - gap: 20, - }, - pFormSection: { - gap: 12, - }, - pFormAddOptionLinkSection: { - marginTop: -8, - paddingVertical: 8, - paddingHorizontal: 16, - alignItems: 'flex-start', - }, - pFormTitle: { - color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, - fontSize: ThemeConfig.FontSize.small, - fontFamily: ThemeConfig.FontFamily.sansPro, - lineHeight: 16, - fontWeight: '600', - }, - pFormTextarea: { - color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, - fontSize: ThemeConfig.FontSize.small, - fontFamily: ThemeConfig.FontFamily.sansPro, - lineHeight: 16, - fontWeight: '400', - borderRadius: 8, - borderWidth: 1, - borderColor: $config.INPUT_FIELD_BORDER_COLOR, - backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, - height: 110, - outlineStyle: 'none', - padding: 20, - }, - pFormOptionText: { - color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, - fontSize: ThemeConfig.FontSize.small, - fontFamily: ThemeConfig.FontFamily.sansPro, - lineHeight: 16, - fontWeight: '400', - }, - pFormOptionPrefix: { - color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.low, - paddingRight: 4, - }, - pFormOptionLink: { - fontWeight: '400', - lineHeight: 24, - }, - pFormOptions: { - paddingVertical: 8, - gap: 8, - }, - pFormInput: { - flex: 1, - color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, - fontSize: ThemeConfig.FontSize.small, - fontFamily: ThemeConfig.FontFamily.sansPro, - lineHeight: 16, - fontWeight: '400', - outlineStyle: 'none', - backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, - borderRadius: 9, - paddingVertical: 12, - }, - pFormOptionCard: { - display: 'flex', - paddingHorizontal: 16, - flexDirection: 'row', - justifyContent: 'flex-start', - alignItems: 'center', - alignSelf: 'stretch', - gap: 8, - backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, - borderRadius: 9, - }, - verticalPadding: { - paddingVertical: 12, - }, - pFormCheckboxContainer: { - paddingHorizontal: 16, - paddingVertical: 8, - }, - previewActions: { - flex: 1, - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'flex-end', - }, - btnContainer: { - minWidth: 150, - height: 36, - borderRadius: 4, - }, - btnText: { - color: $config.FONT_COLOR, - fontSize: ThemeConfig.FontSize.small, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontWeight: '600', - textTransform: 'capitalize', - }, - errorText: { - color: $config.SEMANTIC_ERROR, - fontSize: ThemeConfig.FontSize.tiny, - fontFamily: ThemeConfig.FontFamily.sansPro, - paddingLeft: 5, - paddingTop: 5, - }, -}); diff --git a/template/src/components/polling/components/form/PreviewPollFormView.tsx b/template/src/components/polling/components/form/PreviewPollFormView.tsx deleted file mode 100644 index f556f278d..000000000 --- a/template/src/components/polling/components/form/PreviewPollFormView.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import {Text, StyleSheet, View} from 'react-native'; -import React from 'react'; -import { - BaseModalTitle, - BaseModalContent, - BaseModalActions, - BaseModalCloseIcon, -} from '../../ui/BaseModal'; -import ThemeConfig from '../../../../theme'; -import TertiaryButton from '../../../../atoms/TertiaryButton'; -import {PollItem} from '../../context/poll-context'; -import {POLL_DURATION} from './form-config'; -import BaseRadioButton from '../../ui/BaseRadioButton'; -import Checkbox from '../../../../atoms/Checkbox'; - -interface Props { - form: PollItem; - onEdit: () => void; - onSave: (launch: boolean) => void; - onClose: () => void; -} - -export default function PreviewPollFormView({ - form, - onEdit, - onSave, - onClose, -}: Props) { - return ( - <> - - - - - - {form.duration && ( - {POLL_DURATION} - )} - {form.question} - {form?.options ? ( - - {form.multiple_response - ? form.options.map((option, index) => ( - - {}} - /> - - )) - : form.options.map((option, index) => ( - - {}} - /> - - ))} - - ) : ( - <> - )} - - - - - - { - onEdit(); - }} - text="Edit" - /> - - - { - onSave(false); - }} - /> - - - { - onSave(true); - }} - /> - - - - - ); -} - -export const style = StyleSheet.create({ - previewContainer: { - width: 550, - }, - previewTimer: { - color: $config.SEMANTIC_WARNING, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontSize: 16, - lineHeight: 20, - paddingBottom: 12, - }, - previewQuestion: { - color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, - fontSize: ThemeConfig.FontSize.medium, - fontFamily: ThemeConfig.FontFamily.sansPro, - lineHeight: 24, - fontWeight: '600', - paddingBottom: 20, - }, - previewOptionSection: { - backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, - borderRadius: 9, - paddingVertical: 8, - display: 'flex', - flexDirection: 'column', - gap: 4, - }, - previewOptionCard: { - display: 'flex', - paddingHorizontal: 16, - paddingVertical: 8, - }, - previewOptionText: { - color: $config.FONT_COLOR, - fontSize: ThemeConfig.FontSize.normal, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontWeight: '400', - lineHeight: 24, - }, - previewActions: { - flex: 1, - display: 'flex', - flexDirection: 'row', - gap: 16, - }, - btnContainer: { - flex: 1, - }, -}); diff --git a/template/src/components/polling/components/form/SelectNewPollTypeFormView.tsx b/template/src/components/polling/components/form/SelectNewPollTypeFormView.tsx deleted file mode 100644 index ae2e7a50c..000000000 --- a/template/src/components/polling/components/form/SelectNewPollTypeFormView.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import {Text, StyleSheet, View, TouchableOpacity} from 'react-native'; -import React from 'react'; -import { - BaseModalTitle, - BaseModalContent, - BaseModalCloseIcon, -} from '../../ui/BaseModal'; -import ThemeConfig from '../../../../theme'; -import {PollKind} from '../../context/poll-context'; - -interface newPollType { - key: PollKind; - image: null; - title: string; - description: string; -} - -const newPollTypeConfig: newPollType[] = [ - { - key: PollKind.MCQ, - image: null, - title: 'Multiple Choice', - description: 'Quick stand-alone question with different options', - }, - { - key: PollKind.OPEN_ENDED, - image: null, - title: 'Open Ended', - description: 'Question with a descriptive, open text response', - }, - { - key: PollKind.YES_NO, - image: null, - title: 'Yes / No', - description: 'A simple question with a binary Yes or No response', - }, -]; - -export default function SelectNewPollTypeFormView({ - setType, - onClose, -}: { - setType: React.Dispatch>; - onClose: () => void; -}) { - return ( - <> - - - - - - {newPollTypeConfig.map((item: newPollType) => ( - { - setType(item.key); - }}> - - - - {item.title} - {item.description} - - - - ))} - - - - ); -} - -export const style = StyleSheet.create({ - section: { - display: 'flex', - flexDirection: 'row', - gap: 20, - justifyContent: 'space-between', - }, - card: { - flexDirection: 'column', - gap: 12, - width: 140, - outlineStyle: 'none', - }, - cardImage: { - height: 90, - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - gap: 8, - borderRadius: 8, - borderWidth: 1, - borderColor: $config.CARD_LAYER_3_COLOR, - backgroundColor: $config.CARD_LAYER_4_COLOR, - }, - cardContent: { - display: 'flex', - flexDirection: 'column', - gap: 4, - }, - cardContentTitle: { - color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, - fontSize: ThemeConfig.FontSize.small, - fontFamily: ThemeConfig.FontFamily.sansPro, - lineHeight: 16, - fontWeight: '400', - }, - cardContentDesc: { - color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.low, - fontSize: ThemeConfig.FontSize.tiny, - fontFamily: ThemeConfig.FontFamily.sansPro, - lineHeight: 16, - fontWeight: '400', - }, -}); diff --git a/template/src/components/polling/components/form/form-config.ts b/template/src/components/polling/components/form/form-config.ts deleted file mode 100644 index d80240727..000000000 --- a/template/src/components/polling/components/form/form-config.ts +++ /dev/null @@ -1,118 +0,0 @@ -import {nanoid} from 'nanoid'; -import { - PollKind, - PollItem, - PollAccess, - PollStatus, -} from '../../context/poll-context'; - -const POLL_DURATION = 600; // takes seconds - -const getPollExpiresAtTime = (interval: number): number => { - const t = new Date(); - const expiresAT = t.setSeconds(t.getSeconds() + interval); - return expiresAT; -}; - -const initPollForm = (kind: PollKind): PollItem => { - if (kind === PollKind.OPEN_ENDED) { - return { - id: nanoid(4), - type: PollKind.OPEN_ENDED, - access: PollAccess.PUBLIC, - status: PollStatus.LATER, - question: '', - answers: null, - options: null, - multiple_response: false, - share: false, - duration: false, - expiresAt: null, - createdBy: -1, - }; - } - if (kind === PollKind.MCQ) { - return { - id: nanoid(4), - type: PollKind.MCQ, - access: PollAccess.PUBLIC, - status: PollStatus.LATER, - question: '', - answers: null, - options: [ - { - text: '', - value: '', - votes: [], - percent: '', - }, - { - text: '', - value: '', - votes: [], - percent: '', - }, - { - text: '', - value: '', - votes: [], - percent: '', - }, - ], - multiple_response: true, - share: false, - duration: false, - expiresAt: null, - createdBy: -1, - }; - } - if (kind === PollKind.YES_NO) { - return { - id: nanoid(4), - type: PollKind.YES_NO, - access: PollAccess.PUBLIC, - status: PollStatus.LATER, - question: '', - answers: null, - options: [ - { - text: 'YES', - value: 'yes', - votes: [], - percent: '', - }, - { - text: 'No', - value: 'no', - votes: [], - percent: '', - }, - ], - multiple_response: false, - share: false, - duration: false, - expiresAt: null, - createdBy: -1, - }; - } -}; - -const getAttributeLengthInKb = (attribute: string): string => { - const b = attribute.length * 2; - const kb = (b / 1024).toFixed(2); - return kb; -}; - -const isAttributeLengthValid = (attribute: string) => { - if (getAttributeLengthInKb(attribute) > '8') { - return false; - } - return true; -}; - -export { - getPollExpiresAtTime, - initPollForm, - isAttributeLengthValid, - POLL_DURATION, -}; diff --git a/template/src/components/polling/components/form/poll-response-forms.tsx b/template/src/components/polling/components/form/poll-response-forms.tsx deleted file mode 100644 index 0eb94a4f8..000000000 --- a/template/src/components/polling/components/form/poll-response-forms.tsx +++ /dev/null @@ -1,397 +0,0 @@ -import {Text, View, StyleSheet, TextInput} from 'react-native'; -import React, {useState} from 'react'; -import {BaseModalContent, BaseModalActions} from '../../ui/BaseModal'; -import ThemeConfig from '../../../../theme'; -import PrimaryButton from '../../../../atoms/PrimaryButton'; -import {PollItem} from '../../context/poll-context'; -import PollTimer from '../PollTimer'; -import {videoRoomUserFallbackText} from '../../../../language/default-labels/videoCallScreenLabels'; -import {useContent} from 'customization-api'; -import {UidType} from '../../../../../agora-rn-uikit/src'; -import {useString} from '../../../../utils/useString'; -import UserAvatar from '../../../../atoms/UserAvatar'; -import ImageIcon from '../../../../atoms/ImageIcon'; -import Checkbox from '../../../../atoms/Checkbox'; -import BaseRadioButton from '../../ui/BaseRadioButton'; -import Spacer from '../../../../atoms/Spacer'; - -interface Props { - pollItem: PollItem; -} - -function PollResponseFormModalTitle({pollItem}: Props) { - const remoteUserDefaultLabel = useString(videoRoomUserFallbackText)(); - const {defaultContent} = useContent(); - const getPollCreaterName = (uid: UidType) => { - return defaultContent[uid]?.name || remoteUserDefaultLabel; - }; - - return ( - - - - - - - {getPollCreaterName(pollItem.createdBy)} - - {pollItem.type} - - - ); -} - -function PollResponseFormComplete() { - return ( - - - - - - - Thank you for your response - - - - ); -} - -interface PollResponseFormProps { - pollItem: PollItem; - onComplete: (responses: string | string[]) => void; -} - -function PollResponseQuestionForm({ - pollItem, - onComplete, -}: PollResponseFormProps) { - const [answer, setAnswer] = useState(''); - const [isFormFreezed, setFreezeForm] = useState(false); - - return ( - <> - - {pollItem.duration ? ( - - ) : ( - <> - )} - {pollItem.question} - - - - - - - { - if (!answer || answer.trim() === '') { - return; - } - onComplete(answer); - }} - text="Submit" - /> - - - - ); -} - -function PollResponseMCQForm({pollItem, onComplete}: PollResponseFormProps) { - const [isFormFreezed, setFreezeForm] = useState(false); - const [selectedOption, setSelectedOption] = useState(null); - const [selectedOptions, setSelectedOptions] = useState([]); - - const handleCheckboxToggle = (value: string) => { - setSelectedOptions(prevSelectedOptions => { - if (prevSelectedOptions.includes(value)) { - return prevSelectedOptions.filter(option => option !== value); - } else { - return [...prevSelectedOptions, value]; - } - }); - }; - - const handleRadioSelect = (option: string) => { - setSelectedOption(option); - }; - - const handleSubmit = () => { - if (selectedOptions.length === 0 && !selectedOption) { - return; - } - if (pollItem.multiple_response) { - onComplete(selectedOptions); - } else { - onComplete(selectedOption); - } - }; - - return ( - - - - - {pollItem.question} - - - - {pollItem.multiple_response - ? pollItem.options.map((option, index) => ( - - handleCheckboxToggle(option.value)} - /> - - )) - : pollItem.options.map((option, index) => ( - - - - ))} - - - - - - - ); -} - -export { - PollResponseQuestionForm, - PollResponseMCQForm, - PollResponseFormModalTitle, - PollResponseFormComplete, -}; - -export const style = StyleSheet.create({ - titleCard: { - display: 'flex', - flexDirection: 'row', - gap: 12, - }, - title: { - display: 'flex', - flexDirection: 'column', - gap: 2, - }, - titleAvatar: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - }, - titleAvatarContainer: { - width: 36, - height: 36, - borderRadius: 18, - backgroundColor: $config.VIDEO_AUDIO_TILE_AVATAR_COLOR, - }, - titleAvatarContainerText: { - fontSize: ThemeConfig.FontSize.small, - lineHeight: 16, - fontWeight: '600', - color: $config.VIDEO_AUDIO_TILE_COLOR, - }, - titleText: { - color: $config.FONT_COLOR, - fontSize: ThemeConfig.FontSize.normal, - fontWeight: '700', - lineHeight: 20, - }, - titleSubtext: { - color: $config.FONT_COLOR, - fontSize: ThemeConfig.FontSize.tiny, - fontWeight: '400', - lineHeight: 16, - }, - heading4: { - color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, - fontSize: ThemeConfig.FontSize.medium, - fontFamily: ThemeConfig.FontFamily.sansPro, - lineHeight: 24, - fontWeight: '600', - }, - pFormTextarea: { - color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, - fontSize: ThemeConfig.FontSize.small, - fontFamily: ThemeConfig.FontFamily.sansPro, - lineHeight: 16, - fontWeight: '400', - borderRadius: 8, - borderWidth: 1, - borderColor: $config.INPUT_FIELD_BORDER_COLOR, - backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, - height: 110, - outlineStyle: 'none', - padding: 16, - }, - responseActions: { - flex: 1, - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - }, - btnContainer: { - minWidth: 150, - height: 36, - borderRadius: 4, - }, - btnText: { - color: $config.FONT_COLOR, - fontSize: ThemeConfig.FontSize.small, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontWeight: '600', - textTransform: 'capitalize', - }, - optionsSection: { - backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, - borderRadius: 9, - marginBottom: 32, - display: 'flex', - flexDirection: 'column', - gap: 4, - paddingVertical: 8, - }, - optionCard: { - display: 'flex', - flexDirection: 'row', - paddingHorizontal: 16, - paddingVertical: 8, - alignItems: 'center', - }, - optionCardText: { - color: $config.FONT_COLOR, - fontSize: ThemeConfig.FontSize.normal, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontWeight: '400', - lineHeight: 24, - }, - // pFormOptionText: { - // color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, - // fontSize: ThemeConfig.FontSize.small, - // fontFamily: ThemeConfig.FontFamily.sansPro, - // lineHeight: 16, - // fontWeight: '400', - // }, - // pFormOptionPrefix: { - // color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.low, - // paddingRight: 4, - // }, - // pFormOptionLink: { - // fontWeight: '400', - // lineHeight: 24, - // }, - // pFormOptions: { - // paddingVertical: 8, - // gap: 8, - // }, - pFormInput: { - flex: 1, - color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, - fontSize: ThemeConfig.FontSize.small, - fontFamily: ThemeConfig.FontFamily.sansPro, - lineHeight: 16, - fontWeight: '400', - outlineStyle: 'none', - backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, - borderRadius: 9, - paddingVertical: 12, - }, - centerAlign: { - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - gap: 12, - }, - mediumHeight: { - height: 272, - }, - // pFormOptionCard: { - // display: 'flex', - // paddingHorizontal: 16, - // flexDirection: 'row', - // justifyContent: 'flex-start', - // alignItems: 'center', - // alignSelf: 'stretch', - // gap: 8, - // backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, - // borderRadius: 9, - // }, - // verticalPadding: { - // paddingVertical: 12, - // }, - // pFormCheckboxContainer: { - // paddingHorizontal: 16, - // paddingVertical: 8, - // }, - // previewActions: { - // flex: 1, - // display: 'flex', - // flexDirection: 'row', - // alignItems: 'center', - // justifyContent: 'flex-end', - // }, - // btnContainer: { - // minWidth: 150, - // height: 36, - // borderRadius: 4, - // }, - // btnText: { - // color: $config.FONT_COLOR, - // fontSize: ThemeConfig.FontSize.small, - // fontFamily: ThemeConfig.FontFamily.sansPro, - // fontWeight: '600', - // textTransform: 'capitalize', - // }, -}); diff --git a/template/src/components/polling/components/modals/PollFormWizardModal.tsx b/template/src/components/polling/components/modals/PollFormWizardModal.tsx deleted file mode 100644 index 1a2ffee14..000000000 --- a/template/src/components/polling/components/modals/PollFormWizardModal.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import React, {useEffect, useState} from 'react'; -import {BaseModal} from '../../ui/BaseModal'; -import SelectNewPollTypeFormView from '../form/SelectNewPollTypeFormView'; -import DraftPollFormView from '../form/DraftPollFormView'; -import PreviewPollFormView from '../form/PreviewPollFormView'; -import { - PollItem, - PollKind, - PollStatus, - PollFormErrors, -} from '../../context/poll-context'; -import {useLocalUid} from '../../../../../agora-rn-uikit/src'; -import {usePoll} from '../../context/poll-context'; -import {initPollForm} from '../form/form-config'; -import {filterObject} from '../../../../utils'; - -type FormWizardStep = 'SELECT' | 'DRAFT' | 'PREVIEW'; - -export default function PollFormWizardModal() { - const {polls, savePoll, sendPoll, closeCurrentModal} = usePoll(); - const [step, setStep] = useState('SELECT'); - const [type, setType] = useState(null); - const [form, setForm] = useState(null); - const [formErrors, setFormErrors] = useState(null); - - const localUid = useLocalUid(); - - useEffect(() => { - if (!type) { - return; - } - setForm(initPollForm(type)); - setStep('DRAFT'); - }, [type]); - - const onSave = (launch?: boolean) => { - if (launch) { - // check if there is an already launched poll - const isAnyPollActive = Object.keys( - filterObject(polls, ([_, v]) => v.status === PollStatus.ACTIVE), - ); - if (isAnyPollActive.length > 0) { - console.error( - 'Cannot publish poll now as there is already one poll active', - ); - return; - } - } - const payload = { - ...form, - status: launch ? PollStatus.ACTIVE : PollStatus.LATER, - createdBy: localUid, - }; - savePoll(payload); - if (launch) { - sendPoll(payload); - } - }; - - const onEdit = () => { - setStep('DRAFT'); - }; - - const onPreview = () => { - if (validateForm()) { - setStep('PREVIEW'); - } - }; - - const validateForm = () => { - setFormErrors(null); - if (form.question.trim() === '') { - setFormErrors({ - ...formErrors, - question: {message: 'Cannot be blank'}, - }); - return false; - } - if ( - form.type === PollKind.MCQ && - form.options && - form.options.length === 0 - ) { - setFormErrors({ - ...formErrors, - options: {message: 'Cannot be empty'}, - }); - return false; - } - return true; - }; - - const onClose = () => { - setFormErrors(null); - setForm(null); - setType(null); - closeCurrentModal(); - }; - - function renderSwitch() { - switch (step) { - case 'SELECT': - return ( - - ); - case 'DRAFT': - return ( - - ); - case 'PREVIEW': - return ( - - ); - default: - return <>; - } - } - - return ( - - {renderSwitch()} - - ); -} diff --git a/template/src/components/polling/components/modals/PollResponseFormModal.tsx b/template/src/components/polling/components/modals/PollResponseFormModal.tsx deleted file mode 100644 index c9188288d..000000000 --- a/template/src/components/polling/components/modals/PollResponseFormModal.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, {useState} from 'react'; -import {BaseModal, BaseModalTitle} from '../../ui/BaseModal'; -import { - PollResponseFormComplete, - PollResponseFormModalTitle, - PollResponseQuestionForm, - PollResponseMCQForm, -} from '../form/poll-response-forms'; -import {PollKind, usePoll} from '../../context/poll-context'; - -export default function PollResponseFormModal() { - const {polls, launchPollId, sendResponseToPoll, goToShareResponseModal} = - usePoll(); - const [hasResponded, setHasResponded] = useState(false); - - const pollItem = polls[launchPollId]; - - const onFormComplete = (responses: string | string[]) => { - sendResponseToPoll(pollItem, responses); - if (pollItem.share) { - goToShareResponseModal(); - } else { - setHasResponded(true); - } - }; - - function renderForm(type: PollKind) { - switch (type) { - case PollKind.OPEN_ENDED: - return ( - - ); - case PollKind.MCQ: - return ( - - ); - - default: - return <>; - } - } - - return ( - - - - - {hasResponded ? : renderForm(pollItem.type)} - - ); -} diff --git a/template/src/components/polling/components/modals/SharePollModal.tsx b/template/src/components/polling/components/modals/SharePollModal.tsx deleted file mode 100644 index 8dfe4e823..000000000 --- a/template/src/components/polling/components/modals/SharePollModal.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import {Text, StyleSheet, View} from 'react-native'; -import React from 'react'; -import {BaseModal, BaseModalTitle, BaseModalContent} from '../../ui/BaseModal'; -import ThemeConfig from '../../../../theme'; -import UserAvatar from '../../../../atoms/UserAvatar'; - -export default function SharePollModal() { - return ( - - - - - - - - Elanor Pena - 2:30 pm MCQ - - - - - - How was today's session? - - - - Great - Your Response - - 75% (15) - - - - - - - - - Okay - Your Response - - 75% (15) - - - - - - - - - Could be better - Your Response - - 75% (15) - - - - - - - - - - - ); -} - -export const style = StyleSheet.create({ - shareBox: { - width: 550, - }, - titleCard: { - display: 'flex', - flexDirection: 'row', - gap: 12, - }, - title: { - display: 'flex', - flexDirection: 'column', - gap: 2, - }, - titleAvatar: { - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - }, - titleAvatarContainer: { - width: 36, - height: 36, - borderRadius: 18, - backgroundColor: $config.VIDEO_AUDIO_TILE_AVATAR_COLOR, - }, - titleAvatarContainerText: { - fontSize: ThemeConfig.FontSize.small, - lineHeight: 16, - fontWeight: '600', - color: $config.VIDEO_AUDIO_TILE_COLOR, - }, - titleText: { - color: $config.FONT_COLOR, - fontSize: ThemeConfig.FontSize.normal, - fontWeight: '700', - lineHeight: 20, - }, - titleSubtext: { - color: $config.FONT_COLOR, - fontSize: ThemeConfig.FontSize.tiny, - fontWeight: '400', - lineHeight: 16, - }, - questionText: { - color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, - fontSize: ThemeConfig.FontSize.medium, - fontFamily: ThemeConfig.FontFamily.sansPro, - lineHeight: 24, - fontWeight: '600', - }, - responseSection: { - backgroundColor: $config.INPUT_FIELD_BACKGROUND_COLOR, - borderRadius: 9, - paddingTop: 8, - paddingHorizontal: 12, - paddingBottom: 32, - display: 'flex', - flexDirection: 'column', - gap: 4, - marginVertical: 20, - }, - responseCard: { - display: 'flex', - flexDirection: 'column', - gap: 4, - }, - responseCardBody: { - display: 'flex', - flexDirection: 'row', - paddingHorizontal: 16, - paddingVertical: 8, - alignItems: 'center', - }, - responseText: { - color: $config.FONT_COLOR, - fontSize: ThemeConfig.FontSize.normal, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontWeight: '400', - lineHeight: 24, - }, - yourResponseText: { - color: $config.SEMANTIC_SUCCESS, - fontSize: ThemeConfig.FontSize.tiny, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontWeight: '600', - lineHeight: 12, - paddingLeft: 16, - }, - pushRight: { - marginLeft: 'auto', - }, - progressBar: { - height: 4, - borderRadius: 8, - backgroundColor: $config.CARD_LAYER_3_COLOR, - width: '100%', - }, - progressBarFill: { - borderRadius: 8, - backgroundColor: $config.PRIMARY_ACTION_BRAND_COLOR, - }, -}); diff --git a/template/src/components/polling/context/poll-context.tsx b/template/src/components/polling/context/poll-context.tsx deleted file mode 100644 index a9dcd9959..000000000 --- a/template/src/components/polling/context/poll-context.tsx +++ /dev/null @@ -1,349 +0,0 @@ -import React, {createContext, useReducer, Dispatch, useState} from 'react'; -import {usePollEvents} from './poll-events'; -import {useLocalUid} from '../../../../agora-rn-uikit'; -import {useLiveStreamDataContext} from '../../../components/contexts/LiveStreamDataContext'; -import { - getPollExpiresAtTime, - POLL_DURATION, -} from '../components/form/form-config'; - -enum PollAccess { - PUBLIC = 'PUBLIC', -} - -enum PollStatus { - ACTIVE = 'ACTIVE', - FINISHED = 'FINISHED', - LATER = 'LATER', -} - -enum PollKind { - OPEN_ENDED = 'OPEN_ENDED', - MCQ = 'MCQ', - YES_NO = 'YES_NO', -} - -enum PollModalState { - DRAFT_POLL = 'DRAFT_POLL', - RESPOND_TO_POLL = 'RESPOND_TO_POLL', - SHARE_POLL_RESULTS = 'SHARE_POLL_RESULTS', -} - -interface PollItemOptionItem { - text: string; - value: string; - votes: Array<{uid: number; access: PollAccess; timestamp: number}> | []; - percent: string; -} -interface PollItem { - id: string; - type: PollKind; - access: PollAccess; // remove it as poll are not private or public but the response will be public or private - status: PollStatus; - question: string; - answers: Array<{ - uid: number; - response: string; - timestamp: number; - }> | null; - options: Array | null; - multiple_response: boolean; - share: boolean; - duration: boolean; - expiresAt: number; - createdBy: number; -} - -type Poll = Record; - -interface PollFormErrors { - question?: { - message: string; - }; - options?: { - message: string; - }; -} - -enum PollActionKind { - ADD_POLL_ITEM = 'ADD_POLL_ITEM', - UPDATE_POLL_ITEM_RESPONSES = 'UPDATE_POLL_ITEM_RESPONSES', - LAUNCH_POLL_ITEM = 'LAUNCH_POLL_ITEM', - SUBMIT_POLL_OPEN_ENDED_RESPONSE = 'SUBMIT_POLL_OPEN_ENDED_RESPONSE', - START_POLL_TIMER = 'START_POLL_TIMER', -} - -type PollAction = - | { - type: PollActionKind.ADD_POLL_ITEM; - payload: {item: PollItem}; - } - | { - type: PollActionKind.START_POLL_TIMER; - payload: {item: PollItem}; - } - | { - type: PollActionKind.UPDATE_POLL_ITEM_RESPONSES; - payload: { - id: string; - type: PollKind; - responses: string | string[]; - uid: number; - timestamp: number; - }; - }; - -function addVote( - responses: string[], - options: PollItemOptionItem[], - uid: number, - timestamp: number, -): PollItemOptionItem[] { - return options.map((option: PollItemOptionItem) => { - // Count how many times the value appears in the strings array - const exists = responses.includes(option.value); - if (exists) { - // Creating a new object explicitly - const newOption: PollItemOptionItem = { - ...option, - ...option, - votes: [ - ...option.votes, - { - uid, - access: PollAccess.PUBLIC, - timestamp, - }, - ], - }; - return newOption; - } - // If no matches, return the option as is - return option; - }); -} - -function calculatePercentage( - options: PollItemOptionItem[], -): PollItemOptionItem[] { - const totalVotes = options.reduce( - (total, item) => total + item.votes.length, - 0, - ); - if (totalVotes === 0) { - // As none of the users have voted, there is no need to calulate the percentage, - // we can return the options as it is - return options; - } - return options.map((option: PollItemOptionItem) => { - let percentage = 0; - if (option.votes.length > 0) { - percentage = (option.votes.length / totalVotes) * 100; - } - // Creating a new object explicitly - const newOption: PollItemOptionItem = { - ...option, - percent: percentage.toFixed(2), - }; - return newOption; - }) as PollItemOptionItem[]; -} -function pollReducer(state: Poll, action: PollAction): Poll { - switch (action.type) { - case PollActionKind.ADD_POLL_ITEM: { - const pollId = action.payload.item.id; - return { - ...state, - [pollId]: {...action.payload.item}, - }; - } - case PollActionKind.UPDATE_POLL_ITEM_RESPONSES: - { - const {id: pollId, uid, responses, type, timestamp} = action.payload; - if (type === PollKind.OPEN_ENDED && typeof responses === 'string') { - return { - ...state, - [pollId]: { - ...state[pollId], - answers: [ - ...state[pollId].answers, - { - uid, - response: responses, - timestamp, - }, - ], - }, - }; - } - if (type === PollKind.MCQ && Array.isArray(responses)) { - const newCopyOptions = state[pollId].options.map(item => ({...item})); - const withVotesOptions = addVote( - responses, - newCopyOptions, - uid, - timestamp, - ); - const withPercentOptions = calculatePercentage(withVotesOptions); - return { - ...state, - [pollId]: { - ...state[pollId], - options: [...withPercentOptions], - }, - }; - } - } - break; - default: { - return state; - } - } -} - -interface PollContextValue { - polls: Poll; - currentModal: PollModalState; - dispatch: Dispatch; - startPollForm: () => void; - savePoll: (item: PollItem) => void; - sendPoll: (item: PollItem) => void; - onPollReceived: (item: PollItem, launchId: string) => void; - onPollResponseReceived: ( - id: string, - type: PollKind, - responses: string | string[], - sender: number, - ts: number, - ) => void; - launchPollId: string; - sendResponseToPoll: (item: PollItem, responses: string | string[]) => void; - goToShareResponseModal: () => void; - closeCurrentModal: () => void; -} - -const PollContext = createContext(null); -PollContext.displayName = 'PollContext'; - -function PollProvider({children}: {children: React.ReactNode}) { - const [polls, dispatch] = useReducer(pollReducer, {}); - const [currentModal, setCurrentModal] = useState(null); - const [launchPollId, setLaunchPollId] = useState(null); - const localUid = useLocalUid(); - const {audienceUids} = useLiveStreamDataContext(); - - const {sendPollEvt, sendResponseToPollEvt} = usePollEvents(); - - const startPollForm = () => { - setCurrentModal(PollModalState.DRAFT_POLL); - }; - - const savePoll = (item: PollItem) => { - addPollItem(item); - setCurrentModal(null); - }; - - const sendPoll = (item: PollItem) => { - if (item.status === PollStatus.ACTIVE) { - item.expiresAt = getPollExpiresAtTime(POLL_DURATION); - sendPollEvt(item); - setCurrentModal(null); - } else { - console.error('Poll: Cannot send poll as the status is not active'); - } - }; - - const onPollReceived = (item: PollItem, launchId: string) => { - addPollItem(item); - if (audienceUids.includes(localUid)) { - setLaunchPollId(launchId); - setCurrentModal(PollModalState.RESPOND_TO_POLL); - } - }; - - const sendResponseToPoll = (item: PollItem, responses: string | string[]) => { - if ( - (item.type === PollKind.OPEN_ENDED && typeof responses === 'string') || - (item.type === PollKind.MCQ && Array.isArray(responses)) - ) { - sendResponseToPollEvt(item, responses); - } else { - throw new Error( - 'sendResponseToPoll received incorrect type response. Unable to send poll response', - ); - } - }; - - const onPollResponseReceived = ( - id: string, - type: PollKind, - responses: string | string[], - sender: number, - ts: number, - ) => { - dispatch({ - type: PollActionKind.UPDATE_POLL_ITEM_RESPONSES, - payload: { - id, - type, - responses, - uid: sender, - timestamp: ts, - }, - }); - }; - - const addPollItem = (item: PollItem) => { - dispatch({ - type: PollActionKind.ADD_POLL_ITEM, - payload: { - item: {...item}, - }, - }); - }; - - const goToShareResponseModal = () => { - setCurrentModal(PollModalState.SHARE_POLL_RESULTS); - }; - - const closeCurrentModal = () => { - setCurrentModal(null); - }; - - const value = { - polls, - dispatch, - startPollForm, - savePoll, - sendPoll, - onPollReceived, - onPollResponseReceived, - currentModal, - launchPollId, - sendResponseToPoll, - goToShareResponseModal, - closeCurrentModal, - }; - - return {children}; -} - -function usePoll() { - const context = React.useContext(PollContext); - if (!context) { - throw new Error('usePoll must be used within a PollProvider'); - } - return context; -} - -export { - PollProvider, - usePoll, - PollActionKind, - PollKind, - PollStatus, - PollAccess, - PollModalState, -}; - -export type {PollItem, PollFormErrors, PollItemOptionItem}; diff --git a/template/src/components/polling/context/poll-events.tsx b/template/src/components/polling/context/poll-events.tsx deleted file mode 100644 index b12aff7e9..000000000 --- a/template/src/components/polling/context/poll-events.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import React, {createContext, useContext, useEffect} from 'react'; -import events, {PersistanceLevel} from '../../../rtm-events-api'; -import {PollItem, usePoll} from './poll-context'; - -enum PollEventNames { - polls = 'POLLS', - pollResponse = 'POLL_RESPONSE', -} -enum PollEventActions { - sendPoll = 'SEND_POLL', - sendResponseToPoll = 'SEND_RESONSE_TO_POLL', -} - -type sendResponseToPollEvtFunction = ( - poll: PollItem, - responses: string | string[], -) => void; -interface PollEventsContextValue { - sendPollEvt: (poll: PollItem) => void; - sendResponseToPollEvt: sendResponseToPollEvtFunction; -} - -const PollEventsContext = createContext(null); -PollEventsContext.displayName = 'PollEventsContext'; - -// Event Dispatcher -function PollEventsProvider({children}: {children?: React.ReactNode}) { - const sendPollEvt = async (poll: PollItem) => { - events.send( - PollEventNames.polls, - JSON.stringify({ - action: PollEventActions.sendPoll, - item: {...poll}, - activePollId: poll.id, - }), - PersistanceLevel.Channel, - ); - }; - const sendResponseToPollEvt: sendResponseToPollEvtFunction = ( - item, - responses, - ) => { - events.send( - PollEventNames.pollResponse, - JSON.stringify({ - type: item.type, - id: item.id, - responses, - }), - PersistanceLevel.None, - ); - }; - - const value = { - sendPollEvt, - sendResponseToPollEvt, - }; - - return ( - - {children} - - ); -} - -function usePollEvents() { - const context = useContext(PollEventsContext); - if (!context) { - throw new Error('usePollEvents must be used within PollEventsProvider.'); - } - return context; -} - -// Event Subscriber -const PollEventsSubscriberContext = createContext(null); -PollEventsSubscriberContext.displayName = 'PollEventsContext'; - -function PollEventsSubscriber({children}: {children?: React.ReactNode}) { - const {savePoll, onPollReceived, onPollResponseReceived} = usePoll(); - - useEffect(() => { - events.on(PollEventNames.polls, args => { - // const {payload, sender, ts} = args; - const {payload} = args; - const data = JSON.parse(payload); - const {action, item, activePollId} = data; - console.log('supriya poll received', data); - switch (action) { - case PollEventActions.sendPoll: - onPollReceived(item, activePollId); - break; - - default: - break; - } - }); - events.on(PollEventNames.pollResponse, args => { - const {payload, sender, ts} = args; - const data = JSON.parse(payload); - console.log('supriya poll response received', data); - const {type, id, responses} = data; - onPollResponseReceived(id, type, responses, sender, ts); - }); - - return () => { - events.off(PollEventNames.polls); - events.off(PollEventNames.pollResponse); - }; - }, [onPollReceived, onPollResponseReceived, savePoll]); - - return ( - - {children} - - ); -} - -export {usePollEvents, PollEventsProvider, PollEventsSubscriber}; diff --git a/template/src/components/polling/context/poll-form-context.tsx b/template/src/components/polling/context/poll-form-context.tsx deleted file mode 100644 index 24174d5bb..000000000 --- a/template/src/components/polling/context/poll-form-context.tsx +++ /dev/null @@ -1,339 +0,0 @@ -import React, {Dispatch, createContext, useContext, useReducer} from 'react'; - -enum PollAccess { - PUBLIC = 'PUBLIC', -} - -enum PollStatus { - ACTIVE = 'PUBLIC', - FINISHED = 'FINISHED', - LATER = 'LATER', -} - -enum PollKind { - OPEN_ENDED = 'OPEN_ENDED', - MCQ = 'MCQ', - YES_NO = 'YES_NO', -} - -interface PollItem { - type: PollKind; - access: PollAccess; - status: PollStatus; - title: string; - question: string; - options: Array<{ - text: string; - value: string; - votes: []; - }> | null; - multiple: boolean; - share: boolean; - duration: boolean; - timer: number; - createdBy: number; -} - -enum PollFormActionKind { - START_POLL = 'START_POLL', - SELECT_POLL = 'SELECT_POLL', - UPDATE_FORM_FIELD = 'UPDATE_FORM_FIELD', - UPDATE_FORM_OPTION = 'UPDATE_FORM_OPTION', - ADD_FORM_OPTION = 'ADD_FORM_OPTION', - DELETE_FORM_OPTION = 'DELETE_FORM_OPTION', - PREVIEW_FORM = 'PREVIEW_FORM', - UPDATE_FORM = 'UPDATE_FORM', - SAVE_FORM = 'SAVE_FORM', - POLL_FORM_CLOSE = 'POLL_FORM_CLOSE', -} - -type PollFormAction = - | { - type: PollFormActionKind.START_POLL; - } - | { - type: PollFormActionKind.SELECT_POLL; - payload: { - pollType: PollKind; - }; - } - | { - type: PollFormActionKind.UPDATE_FORM_FIELD; - payload: { - field: string; - value: string | boolean; - }; - } - | { - type: PollFormActionKind.ADD_FORM_OPTION; - } - | { - type: PollFormActionKind.UPDATE_FORM_OPTION; - payload: { - index: number; - value: string; - }; - } - | { - type: PollFormActionKind.DELETE_FORM_OPTION; - payload: { - index: number; - }; - } - | { - type: PollFormActionKind.PREVIEW_FORM; - } - | { - type: PollFormActionKind.SAVE_FORM; - payload: { - launch: boolean; - createdBy: number; - }; - } - | { - type: PollFormActionKind.UPDATE_FORM; - } - | { - type: PollFormActionKind.POLL_FORM_CLOSE; - }; - -const initPollForm = (kind: PollKind): PollItem => { - if (kind === PollKind.OPEN_ENDED) { - return { - type: PollKind.OPEN_ENDED, - access: PollAccess.PUBLIC, - status: PollStatus.LATER, - title: 'Open Ended Poll', - question: '', - options: null, - multiple: false, - share: false, - duration: false, - timer: -1, - createdBy: -1, - }; - } - if (kind === PollKind.MCQ) { - return { - type: PollKind.MCQ, - access: PollAccess.PUBLIC, - status: PollStatus.LATER, - title: 'Multiple Choice Question', - question: '', - options: [ - { - text: '', - value: '', - votes: [], - }, - { - text: '', - value: '', - votes: [], - }, - { - text: '', - value: '', - votes: [], - }, - ], - multiple: true, - share: false, - duration: false, - timer: -1, - createdBy: -1, - }; - } - if (kind === PollKind.YES_NO) { - return { - type: PollKind.YES_NO, - access: PollAccess.PUBLIC, - status: PollStatus.LATER, - title: 'Yes/No', - question: '', - options: [ - { - text: 'YES', - value: 'yes', - votes: [], - }, - { - text: 'No', - value: 'no', - votes: [], - }, - ], - multiple: false, - share: false, - duration: false, - timer: -1, - createdBy: -1, - }; - } -}; - -const getPollTimer = (isDurationEnabled: boolean) => { - if (isDurationEnabled) { - return 10000; - } - return -1; -}; - -interface PollFormState { - form: PollItem; - currentStep: 'START_POLL' | 'SELECT_POLL' | 'CREATE_POLL' | 'PREVIEW_POLL'; -} - -function pollFormReducer( - state: PollFormState, - action: PollFormAction, -): PollFormState { - switch (action.type) { - case PollFormActionKind.START_POLL: { - return { - ...state, - form: null, - currentStep: 'SELECT_POLL', - }; - } - case PollFormActionKind.SELECT_POLL: { - return { - ...state, - currentStep: 'CREATE_POLL', - form: initPollForm(action.payload.pollType), - }; - } - case PollFormActionKind.UPDATE_FORM_FIELD: { - return { - ...state, - form: { - ...state.form, - [action.payload.field]: action.payload.value, - ...(action.payload.field === 'duration' && { - timer: getPollTimer(action.payload.value as boolean), - }), - }, - }; - } - case PollFormActionKind.ADD_FORM_OPTION: { - return { - ...state, - form: { - ...state.form, - options: [ - ...state.form.options, - { - text: '', - value: '', - votes: [], - }, - ], - }, - }; - } - case PollFormActionKind.UPDATE_FORM_OPTION: { - return { - ...state, - form: { - ...state.form, - options: state.form.options.map((option, i) => { - if (i === action.payload.index) { - const value = action.payload.value - .replace(/\s+/g, '-') - .toLowerCase(); - return { - ...option, - text: action.payload.value, - value, - votes: [], - }; - } - return option; - }), - }, - }; - } - case PollFormActionKind.DELETE_FORM_OPTION: { - return { - ...state, - form: { - ...state.form, - options: state.form.options.filter( - (option, i) => i !== action.payload.index, - ), - }, - }; - } - case PollFormActionKind.PREVIEW_FORM: { - return { - ...state, - currentStep: 'PREVIEW_POLL', - }; - } - case PollFormActionKind.UPDATE_FORM: { - return { - ...state, - currentStep: 'CREATE_POLL', - }; - } - // case PollFormActionKind.SAVE_FORM: { - // return { - // ...state, - // form: { - // ...state.form, - // status: action.payload.launch ? PollStatus.ACTIVE : PollStatus.LATER, - // }, - // currentStep: null, - // }; - // } - case PollFormActionKind.POLL_FORM_CLOSE: { - return { - form: null, - currentStep: null, - }; - } - default: { - return state; - } - } -} - -interface PollFormContextValue { - state: PollFormState; - dispatch: Dispatch; -} - -const PollFormContext = createContext(null); -PollFormContext.displayName = 'PollFormContext'; - -function PollFormProvider({children}: {children?: React.ReactNode}) { - const [state, dispatch] = useReducer(pollFormReducer, { - form: null, - currentStep: 'SELECT_POLL', - }); - - const value = {state, dispatch}; - - return ( - - {children} - - ); -} - -function usePollForm() { - const context = useContext(PollFormContext); - if (!context) { - throw new Error('usePollForm must be used within PollFormProvider '); - } - return context; -} - -export { - PollFormProvider, - usePollForm, - PollFormActionKind, - PollKind, - PollStatus, -}; -export type {PollItem}; diff --git a/template/src/components/polling/hook/useCountdownTimer.tsx b/template/src/components/polling/hook/useCountdownTimer.tsx deleted file mode 100644 index 238f3adbe..000000000 --- a/template/src/components/polling/hook/useCountdownTimer.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import {useEffect, useState, useRef} from 'react'; - -const useCountdown = (targetDate: number) => { - const countDownDate = new Date(targetDate).getTime(); - const intervalRef = useRef(null); // Add a ref to store the interval id - - const [countDown, setCountDown] = useState( - countDownDate - new Date().getTime(), - ); - - useEffect(() => { - intervalRef.current = setInterval(() => { - setCountDown(_ => { - const newCountDown = countDownDate - new Date().getTime(); - if (newCountDown <= 0) { - clearInterval(intervalRef.current!); - return 0; - } - return newCountDown; - }); - }, 1000); - - return () => { - if (intervalRef.current) { - clearInterval(intervalRef.current); - } - }; - }, [countDownDate]); - - return getReturnValues(countDown); -}; - -const getReturnValues = countDown => { - // calculate time left - const days = Math.floor(countDown / (1000 * 60 * 60 * 24)); - const hours = Math.floor( - (countDown % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60), - ); - const minutes = Math.floor((countDown % (1000 * 60 * 60)) / (1000 * 60)); - const seconds = Math.floor((countDown % (1000 * 60)) / 1000); - - return [days, hours, minutes, seconds]; -}; - -export {useCountdown}; diff --git a/template/src/components/polling/ui/BaseModal.tsx b/template/src/components/polling/ui/BaseModal.tsx deleted file mode 100644 index 2c0ddc9ba..000000000 --- a/template/src/components/polling/ui/BaseModal.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import {Modal, View, StyleSheet, Text} from 'react-native'; -import React, {ReactNode} from 'react'; -import ThemeConfig from '../../../theme'; -import hexadecimalTransparency from '../../../utils/hexadecimalTransparency'; -import IconButton from '../../../atoms/IconButton'; -import {isMobileUA} from '../../../utils/common'; - -interface TitleProps { - title?: string; - children?: ReactNode | ReactNode[]; -} - -function BaseModalTitle({title, children}: TitleProps) { - return ( - - {title && ( - - {title} - - )} - {children} - - ); -} - -interface ContentProps { - children: ReactNode; -} - -function BaseModalContent({children}: ContentProps) { - return {children}; -} - -interface ActionProps { - children: ReactNode; -} -function BaseModalActions({children}: ActionProps) { - return {children}; -} - -type BaseModalProps = { - visible?: boolean; - children: ReactNode; - width?: number; -}; - -const BaseModal = ({ - children, - visible = false, - width = 650, -}: BaseModalProps) => { - return ( - - - - {children} - - - - ); -}; - -type BaseModalCloseIconProps = { - onClose: () => void; -}; - -const BaseModalCloseIcon = ({onClose}: BaseModalCloseIconProps) => { - return ( - - - - ); -}; -export { - BaseModal, - BaseModalTitle, - BaseModalContent, - BaseModalActions, - BaseModalCloseIcon, -}; - -const style = StyleSheet.create({ - baseModalBackDrop: { - flex: 1, - position: 'relative', - justifyContent: 'center', - alignItems: 'center', - padding: 20, - backgroundColor: - $config.HARD_CODED_BLACK_COLOR + hexadecimalTransparency['60%'], - }, - baseModal: { - backgroundColor: $config.CARD_LAYER_1_COLOR, - borderWidth: 1, - borderColor: $config.CARD_LAYER_3_COLOR, - borderRadius: ThemeConfig.BorderRadius.large, - shadowColor: $config.HARD_CODED_BLACK_COLOR, - shadowOffset: { - width: 0, - height: 2, - }, - shadowOpacity: 0.1, - shadowRadius: 4, - elevation: 5, - maxWidth: '90%', - maxHeight: 800, - overflow: 'scroll', - }, - scrollView: { - flex: 1, - }, - header: { - display: 'flex', - paddingHorizontal: 32, - paddingVertical: 20, - alignItems: 'center', - gap: 20, - minHeight: 72, - justifyContent: 'space-between', - flexDirection: 'row', - borderBottomWidth: 1, - borderColor: $config.CARD_LAYER_3_COLOR, - }, - title: { - color: $config.FONT_COLOR + ThemeConfig.EmphasisPlus.high, - fontSize: ThemeConfig.FontSize.xLarge, - fontFamily: ThemeConfig.FontFamily.sansPro, - lineHeight: 32, - fontWeight: '600', - letterSpacing: -0.48, - }, - content: { - padding: 32, - gap: 20, - display: 'flex', - flexDirection: 'column', - // minWidth: 620, - }, - actions: { - height: 72, - paddingHorizontal: 32, - paddingVertical: 12, - display: 'flex', - gap: 16, - backgroundColor: $config.CARD_LAYER_2_COLOR, - }, -}); diff --git a/template/src/components/polling/ui/BaseRadioButton.tsx b/template/src/components/polling/ui/BaseRadioButton.tsx deleted file mode 100644 index eac0edb61..000000000 --- a/template/src/components/polling/ui/BaseRadioButton.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { - TouchableOpacity, - View, - StyleSheet, - Text, - StyleProp, - TextStyle, -} from 'react-native'; -import React from 'react'; -import ThemeConfig from '../../../theme'; - -interface Props { - option: { - label: string; - value: string; - }; - checked: boolean; - onChange: (option: string) => void; - labelStyle?: StyleProp; - disabled?: boolean; -} -export default function BaseRadioButton(props: Props) { - const {option, checked, onChange, disabled, labelStyle = {}} = props; - return ( - - !disabled && onChange(option.value)}> - - {option.label} - - - ); -} - -const style = StyleSheet.create({ - optionsContainer: { - flexDirection: 'row', - alignItems: 'center', - marginBottom: 10, - }, - disabledContainer: { - opacity: 0.5, - }, - radioCircle: { - height: 22, - width: 22, - borderRadius: 11, - borderWidth: 2, - borderColor: $config.PRIMARY_ACTION_BRAND_COLOR, - alignItems: 'center', - justifyContent: 'center', - }, - disabledCircle: { - // borderColor: $config.FONT_COLOR, - }, - radioFilled: { - height: 14, - width: 14, - borderRadius: 7, - backgroundColor: $config.PRIMARY_ACTION_BRAND_COLOR, - }, - optionText: { - color: $config.FONT_COLOR, - fontSize: ThemeConfig.FontSize.normal, - fontFamily: ThemeConfig.FontFamily.sansPro, - fontWeight: '400', - lineHeight: 24, - marginLeft: 10, - }, -}); From b15014de03229b0d2b300699d3a17f75fa1ca680 Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Tue, 13 Aug 2024 19:27:49 +0530 Subject: [PATCH 030/174] add imports in customization api --- template/customization-api/app-state.ts | 2 ++ template/customization-api/atoms.ts | 7 +++++++ template/customization-api/index.ts | 1 + template/customization-api/temp.ts | 5 +++++ template/customization-api/types.ts | 2 ++ template/customization-api/utils.ts | 1 + 6 files changed, 18 insertions(+) create mode 100644 template/customization-api/atoms.ts diff --git a/template/customization-api/app-state.ts b/template/customization-api/app-state.ts index 087b9960b..31e60e640 100644 --- a/template/customization-api/app-state.ts +++ b/template/customization-api/app-state.ts @@ -60,3 +60,5 @@ export { export type {ChatUIControlsInterface} from '../src/components/chat-ui/useChatUIControls'; export {useVirtualBackground} from '../src/app-state/useVirtualBackground'; export {useBeautyEffects} from '../src/app-state/useBeautyEffects'; + +export {useLiveStreamDataContext} from '../src/components/contexts/LiveStreamDataContext'; diff --git a/template/customization-api/atoms.ts b/template/customization-api/atoms.ts new file mode 100644 index 000000000..8585c46e8 --- /dev/null +++ b/template/customization-api/atoms.ts @@ -0,0 +1,7 @@ +export {default as UserAvatar} from '../src/atoms/UserAvatar'; +export {default as ImageIcon} from '../src/atoms/ImageIcon'; +export {default as Checkbox} from '../src/atoms/Checkbox'; +export {default as Spacer} from '../src/atoms/Spacer'; +export {default as LinkButton} from '../src/atoms/LinkButton'; +export {default as PrimaryButton} from '../src/atoms/PrimaryButton'; +export {default as TertiaryButton} from '../src/atoms/TertiaryButton'; diff --git a/template/customization-api/index.ts b/template/customization-api/index.ts index b45dd0b7b..541460b4a 100644 --- a/template/customization-api/index.ts +++ b/template/customization-api/index.ts @@ -25,6 +25,7 @@ export * from './sub-components'; export * from './typeDefinition'; export * from './utils'; export * from './types'; +export * from './atoms'; //TODO: hari remove later - used for simple-practice demo export * from './temp'; diff --git a/template/customization-api/temp.ts b/template/customization-api/temp.ts index e54462700..4d497621f 100644 --- a/template/customization-api/temp.ts +++ b/template/customization-api/temp.ts @@ -25,7 +25,12 @@ import useRemoteMute, {MUTE_REMOTE_TYPE} from '../src/utils/useRemoteMute'; import getCustomRoute from '../src/utils/getCustomRoute'; import TertiaryButton from '../src/atoms/TertiaryButton'; import useEndCall from '../src/utils/useEndCall'; +import ThemeConfig from '../src/theme'; +import hexadecimalTransparency from '../src/utils/hexadecimalTransparency'; + export { + hexadecimalTransparency, + ThemeConfig, VideoRenderer, DispatchContext, IconButton, diff --git a/template/customization-api/types.ts b/template/customization-api/types.ts index c658053b0..f88369f7a 100644 --- a/template/customization-api/types.ts +++ b/template/customization-api/types.ts @@ -8,6 +8,7 @@ export type { ContentStateInterface, ExtenedContentInterface, UidType, + useLocalUid, } from '../agora-rn-uikit'; export { I18nDeviceStatus, @@ -23,6 +24,7 @@ export type { publicChatToastSubHeadingDataInterface, privateChatToastHeadingDataInterface, publicAndPrivateChatToastSubHeadingDataInterface, + videoRoomUserFallbackText, } from '../src/language/default-labels/videoCallScreenLabels'; export type {TextDataInterface} from '../src/language/default-labels'; export { diff --git a/template/customization-api/utils.ts b/template/customization-api/utils.ts index 24f5caaca..6ec15b6ca 100644 --- a/template/customization-api/utils.ts +++ b/template/customization-api/utils.ts @@ -33,6 +33,7 @@ export {default as isMobileOrTablet} from '../src/utils/isMobileOrTablet'; export {useLocalUid} from '../agora-rn-uikit'; export {default as useLocalAudio} from '../src/utils/useLocalAudio'; export {default as useLocalVideo} from '../src/utils/useLocalVideo'; +export {useString} from '../src/utils/useString'; export type {LanguageType} from '../src/subComponents/caption/utils'; export {default as useSpeechToText} from '../src/utils/useSpeechToText'; export {isMobileUA} from '../src/utils/common'; From 9c1168f71974ba1c8e04a254c562c8565249525a Mon Sep 17 00:00:00 2001 From: Supriya Adep Date: Tue, 13 Aug 2024 19:28:56 +0530 Subject: [PATCH 031/174] add polling and wip customization index file --- template/customization/PollDemo.tsx | 6 + template/customization/index.tsx | 88 ++++ template/customization/package.json | 3 + .../customization/polling/components/Poll.tsx | 40 ++ .../polling/components/PollCard.tsx | 188 +++++++++ .../polling/components/PollSidebar.tsx | 112 +++++ .../polling/components/PollTimer.tsx | 53 +++ .../components/form/DraftPollFormView.tsx | 394 +++++++++++++++++ .../components/form/PreviewPollFormView.tsx | 153 +++++++ .../form/SelectNewPollTypeFormView.tsx | 119 ++++++ .../polling/components/form/form-config.ts | 118 ++++++ .../components/form/poll-response-forms.tsx | 399 ++++++++++++++++++ .../components/modals/PollFormWizardModal.tsx | 134 ++++++ .../modals/PollResponseFormModal.tsx | 57 +++ .../components/modals/SharePollModal.tsx | 196 +++++++++ .../polling/context/poll-context.tsx | 349 +++++++++++++++ .../polling/context/poll-events.tsx | 118 ++++++ .../polling/hook/useCountdownTimer.tsx | 45 ++ .../customization/polling/ui/BaseModal.tsx | 160 +++++++ .../polling/ui/BaseRadioButton.tsx | 78 ++++ 20 files changed, 2810 insertions(+) create mode 100644 template/customization/PollDemo.tsx create mode 100644 template/customization/index.tsx create mode 100644 template/customization/package.json create mode 100644 template/customization/polling/components/Poll.tsx create mode 100644 template/customization/polling/components/PollCard.tsx create mode 100644 template/customization/polling/components/PollSidebar.tsx create mode 100644 template/customization/polling/components/PollTimer.tsx create mode 100644 template/customization/polling/components/form/DraftPollFormView.tsx create mode 100644 template/customization/polling/components/form/PreviewPollFormView.tsx create mode 100644 template/customization/polling/components/form/SelectNewPollTypeFormView.tsx create mode 100644 template/customization/polling/components/form/form-config.ts create mode 100644 template/customization/polling/components/form/poll-response-forms.tsx create mode 100644 template/customization/polling/components/modals/PollFormWizardModal.tsx create mode 100644 template/customization/polling/components/modals/PollResponseFormModal.tsx create mode 100644 template/customization/polling/components/modals/SharePollModal.tsx create mode 100644 template/customization/polling/context/poll-context.tsx create mode 100644 template/customization/polling/context/poll-events.tsx create mode 100644 template/customization/polling/hook/useCountdownTimer.tsx create mode 100644 template/customization/polling/ui/BaseModal.tsx create mode 100644 template/customization/polling/ui/BaseRadioButton.tsx diff --git a/template/customization/PollDemo.tsx b/template/customization/PollDemo.tsx new file mode 100644 index 000000000..2c6bc7b62 --- /dev/null +++ b/template/customization/PollDemo.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import {View} from 'react-native'; + +export default function PollDemo() { + return Hi; +} diff --git a/template/customization/index.tsx b/template/customization/index.tsx new file mode 100644 index 000000000..76bbc9cc4 --- /dev/null +++ b/template/customization/index.tsx @@ -0,0 +1,88 @@ +import React from 'react'; +import {View, Text, Button} from 'react-native'; +import { + customize, + ToolbarPreset, + useSidePanel, + ToolbarItem, +} from 'customization-api'; +import PollDemo from './PollDemo'; + +const TestButton1 = () => { + const {setSidePanel} = useSidePanel(); + return ( + +