mediasfu-reactnative
Version:
MediaSFU Prebuilt React Native SDK
460 lines (454 loc) • 16.6 kB
JavaScript
import React, { useEffect, useState } from 'react';
import { Modal, View, Text, TextInput, ScrollView, Pressable, StyleSheet, Dimensions, } from 'react-native';
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5';
import RNPickerSelect from 'react-native-picker-select';
import { getModalPosition } from '../../methods/utils/getModalPosition';
/**
* PollModal component allows users to create, manage, and vote in polls within an event.
*
* @component
* @param {PollModalOptions} props - The properties for the PollModal component.
* @returns {JSX.Element} The rendered PollModal component.
*
* @example
* ```tsx
* import React, { useState } from 'react';
* import { PollModal } from 'mediasfu-reactnative';
*
* function App() {
* const [isPollModalVisible, setPollModalVisible] = useState(false);
*
* const handleCreatePoll = (options) => { };
* const handleEndPoll = (options) => { };
* const handleVotePoll = (options) => { };
*
* return (
* <PollModal
* isPollModalVisible={isPollModalVisible}
* onClose={() => setPollModalVisible(false)}
* position="topRight"
* member="john_doe"
* islevel="2"
* polls={[{ id: '1', question: 'Is React Native awesome?', options: ['Yes', 'No'], status: 'active', voters: {}, votes: [3, 1] }]}
* poll={{ id: '1', question: 'Is React Native awesome?', options: ['Yes', 'No'], status: 'active', voters: {}, votes: [3, 1] }}
* socket={socketInstance}
* roomName="MainRoom"
* handleCreatePoll={handleCreatePoll}
* handleEndPoll={handleEndPoll}
* handleVotePoll={handleVotePoll}
* showAlert={showAlertFunction}
* updateIsPollModalVisible={setPollModalVisible}
* />
* );
* }
*
* export default App;
* ```
*/
const PollModal = ({ isPollModalVisible, onClose, position = 'topRight', backgroundColor = '#f5f5f5', member, islevel, polls, poll, socket, roomName, showAlert, updateIsPollModalVisible, handleCreatePoll, handleEndPoll, handleVotePoll, }) => {
const [newPoll, setNewPoll] = useState({
question: '',
type: '',
options: [],
});
const screenWidth = Dimensions.get('window').width;
let modalWidth = 0.9 * screenWidth; // Adjusted for better mobile view
if (modalWidth > 350) {
modalWidth = 350;
}
/**
* Renders polls based on the user's level and poll status.
*/
const renderPolls = () => {
let activePollCount = 0;
polls.forEach((polled) => {
if (polled.status === 'active' && poll && polled.id === poll.id) {
activePollCount++;
}
});
if (islevel === '2' && activePollCount === 0) {
if (poll && poll.status === 'active') {
// Ideally, you should handle state immutably
// This is just a placeholder; consider using state management
poll.status = 'inactive';
}
}
};
useEffect(() => {
if (isPollModalVisible) {
renderPolls();
}
}, [isPollModalVisible, polls, poll]);
/**
* Calculates the percentage of votes for a given option.
* @param votes Array of vote counts.
* @param optionIndex Index of the option.
* @returns Percentage string.
*/
const calculatePercentage = (votes, optionIndex) => {
const totalVotes = votes.reduce((a, b) => a + b, 0);
return totalVotes > 0
? ((votes[optionIndex] / totalVotes) * 100).toFixed(2)
: '0.00';
};
/**
* Handles the change in poll type and initializes options accordingly.
* @param type Selected poll type.
*/
const handlePollTypeChange = (type) => {
let options = [];
switch (type) {
case 'trueFalse':
options = ['True', 'False'];
break;
case 'yesNo':
options = ['Yes', 'No'];
break;
case 'custom':
options = [];
break;
default:
options = [];
break;
}
setNewPoll(Object.assign(Object.assign({}, newPoll), { type, options }));
};
/**
* Renders poll options based on the selected poll type.
*/
const renderPollOptions = () => {
var _a, _b;
switch (newPoll === null || newPoll === void 0 ? void 0 : newPoll.type) {
case 'trueFalse':
case 'yesNo':
return (<View>
{newPoll.options.map((option, index) => (<View style={styles.formCheck} key={index}>
<View style={styles.radioButton}>
<View style={styles.radioButtonIcon}/>
</View>
<Text style={styles.formCheckLabel}>{option}</Text>
</View>))}
</View>);
case 'custom':
return (<>
{(_a = newPoll.options) === null || _a === void 0 ? void 0 : _a.map((option, index) => (<View style={styles.formGroup} key={index}>
<TextInput style={styles.formControl} placeholder={`Option ${index + 1}`} maxLength={50} value={option || ''} onChangeText={(text) => {
const newOptions = [...newPoll.options];
newOptions[index] = text;
setNewPoll(Object.assign(Object.assign({}, newPoll), { options: newOptions }));
}}/>
</View>))}
{[...Array(5 - (((_b = newPoll.options) === null || _b === void 0 ? void 0 : _b.length) || 0))].map((_, index) => {
var _a, _b;
return (<View style={styles.formGroup} key={(((_a = newPoll.options) === null || _a === void 0 ? void 0 : _a.length) || 0) + index}>
<TextInput style={styles.formControl} placeholder={`Option ${(((_b = newPoll.options) === null || _b === void 0 ? void 0 : _b.length) || 0) + index + 1}`} maxLength={50} value="" onChangeText={(text) => {
const newOptions = [...(newPoll.options || []), text];
setNewPoll(Object.assign(Object.assign({}, newPoll), { options: newOptions }));
}}/>
</View>);
})}
</>);
default:
return null;
}
};
/**
* Renders the options for the current active poll.
*/
const renderCurrentPollOptions = () => poll === null || poll === void 0 ? void 0 : poll.options.map((option, i) => (<Pressable key={i} style={styles.formCheck} onPress={() => handleVotePoll({
pollId: poll.id,
optionIndex: i,
socket,
showAlert,
member,
roomName,
updateIsPollModalVisible,
})}>
<View style={[
styles.radioButton,
poll.voters
&& poll.voters[member] === i
&& styles.radioButtonSelected,
]}>
{poll.voters && poll.voters[member] === i && (<View style={styles.radioButtonIcon}/>)}
</View>
<Text style={styles.formCheckLabel}>{option}</Text>
</Pressable>));
return (<Modal visible={isPollModalVisible} transparent animationType="slide" onRequestClose={onClose}>
<View style={[styles.modalContainer, getModalPosition({ position })]}>
<View style={[styles.modalContent, { backgroundColor, width: modalWidth }]}>
{/* Header */}
<View style={styles.header}>
<Text style={styles.headerText}>Polls</Text>
<Pressable onPress={onClose} style={styles.closeButton}>
<FontAwesome5 name="times" size={24} color="black"/>
</Pressable>
</View>
<View style={styles.separator}/>
<ScrollView>
{islevel === '2' && (<>
{/* Previous Polls */}
<View style={styles.section}>
<Text style={styles.sectionHeader}>Previous Polls</Text>
{polls.length === 0 && (<Text style={styles.noPollText}>No polls available</Text>)}
{polls.map((polled, index) => polled
&& (!poll
|| (poll
&& (poll.status !== 'active'
|| polled.id !== poll.id))) && (<View key={index} style={styles.poll}>
<Text style={styles.pollLabel}>Question:</Text>
<TextInput style={styles.textarea} multiline editable={false} value={polled.question}/>
<Text style={styles.pollLabel}>Options:</Text>
{polled.options.map((option, i) => (<Text key={i} style={styles.optionText}>
{`${option}: ${polled.votes[i]} votes (${calculatePercentage(polled.votes, i)}%)`}
</Text>))}
{polled.status === 'active' && (<Pressable style={[styles.button, styles.buttonDanger]} onPress={() => handleEndPoll({
pollId: polled.id,
socket,
showAlert,
roomName,
updateIsPollModalVisible,
})}>
<Text style={styles.buttonText}>End Poll</Text>
</Pressable>)}
</View>))}
</View>
<View style={styles.separator}/>
{/* Create New Poll */}
<View style={styles.section}>
<Text style={styles.sectionHeader}>Create a New Poll</Text>
<View style={styles.formGroup}>
<Text style={styles.formLabel}>Poll Question</Text>
<TextInput style={styles.textarea} multiline maxLength={300} value={newPoll.question} onChangeText={(text) => setNewPoll(Object.assign(Object.assign({}, newPoll), { question: text }))} placeholder="Enter your question here..." placeholderTextColor={'black'}/>
</View>
<View style={styles.formGroup}>
<Text style={styles.formLabel}>
Select Poll Answer Type
</Text>
<RNPickerSelect onValueChange={handlePollTypeChange} items={[
{ label: 'Choose...', value: '' },
{ label: 'True/False', value: 'trueFalse' },
{ label: 'Yes/No', value: 'yesNo' },
{ label: 'Custom', value: 'custom' },
]} placeholder={{}} style={pickerSelectStyles} value={newPoll.type} useNativeAndroidPickerStyle={false}/>
</View>
{renderPollOptions()}
<Pressable style={[styles.button, styles.buttonPrimary]} onPress={() => handleCreatePoll({
poll: newPoll,
socket,
roomName,
showAlert,
updateIsPollModalVisible,
})}>
<Text style={styles.buttonText}>Create Poll</Text>
</Pressable>
</View>
<View style={styles.separator}/>
</>)}
{/* Current Poll */}
<View style={styles.section}>
<Text style={styles.sectionHeader}>Current Poll</Text>
{poll && poll.status === 'active' ? (<View style={styles.poll}>
<Text style={styles.pollLabel}>Question:</Text>
<TextInput style={styles.textarea} multiline editable={false} value={poll.question}/>
<Text style={styles.pollLabel}>Options:</Text>
{renderCurrentPollOptions()}
{poll.status === 'active' && islevel === '2' && (<Pressable style={[styles.button, styles.buttonDanger]} onPress={() => handleEndPoll({
pollId: poll.id,
socket,
showAlert,
roomName,
updateIsPollModalVisible,
})}>
<Text style={styles.buttonText}>End Poll</Text>
</Pressable>)}
</View>) : (<Text style={styles.noPollText}>No active poll</Text>)}
</View>
</ScrollView>
</View>
</View>
</Modal>);
};
const styles = StyleSheet.create({
modalContainer: {
flex: 1,
borderRadius: 10,
padding: 10,
zIndex: 9,
elevation: 9,
},
modalContent: {
backgroundColor: '#ffffff',
borderRadius: 10,
padding: 15,
maxHeight: '70%',
maxWidth: '80%',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 4,
elevation: 9,
zIndex: 9,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 15,
},
headerText: {
fontSize: 24,
fontWeight: 'bold',
color: 'black',
},
closeButton: {
padding: 5,
},
separator: {
height: 1,
backgroundColor: '#000000',
marginVertical: 10,
},
section: {
marginBottom: 15,
},
sectionHeader: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 10,
color: 'black',
},
noPollText: {
fontSize: 16,
color: 'gray',
textAlign: 'center',
marginVertical: 10,
},
poll: {
marginBottom: 10,
},
pollLabel: {
fontWeight: 'bold',
fontSize: 16,
marginBottom: 5,
},
optionText: {
fontSize: 14,
marginLeft: 10,
marginBottom: 5,
color: 'black',
},
textarea: {
borderWidth: 1,
borderColor: 'gray',
borderRadius: 5,
padding: 10,
marginBottom: 10,
textAlignVertical: 'top',
fontSize: 16,
color: 'black',
},
formGroup: {
marginBottom: 15,
},
formControl: {
fontSize: 16,
paddingVertical: 8,
paddingHorizontal: 10,
borderWidth: 1,
borderColor: 'gray',
borderRadius: 5,
color: 'black',
},
formLabel: {
fontSize: 16,
marginBottom: 5,
color: 'black',
},
formCheck: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 10,
},
radioButton: {
height: 20,
width: 20,
borderRadius: 10,
borderWidth: 1,
borderColor: '#000',
alignItems: 'center',
justifyContent: 'center',
marginRight: 10,
},
radioButtonSelected: {
borderColor: '#000',
backgroundColor: '#000',
},
radioButtonIcon: {
height: 10,
width: 10,
borderRadius: 5,
backgroundColor: '#fff',
},
formCheckLabel: {
fontSize: 16,
color: 'black',
},
button: {
padding: 10,
borderRadius: 5,
alignItems: 'center',
justifyContent: 'center',
marginTop: 10,
},
buttonText: {
color: 'white',
fontSize: 16,
fontWeight: 'bold',
},
buttonPrimary: {
backgroundColor: '#000000',
},
buttonDanger: {
backgroundColor: '#dc3545',
},
});
/**
* Styles for the RNPickerSelect component.
*/
const pickerSelectStyles = StyleSheet.create({
inputIOS: {
fontSize: 16,
paddingVertical: 4,
paddingHorizontal: 10,
borderWidth: 1,
borderColor: 'gray',
borderRadius: 5,
color: 'black',
paddingRight: 30, // to ensure the text is never behind the icon
},
inputAndroid: {
fontSize: 16,
paddingHorizontal: 10,
paddingVertical: 4,
borderWidth: 0.5,
borderColor: 'gray',
borderRadius: 5,
color: 'black',
paddingRight: 30, // to ensure the text is never behind the icon
},
inputWeb: {
fontSize: 14,
paddingHorizontal: 10,
paddingVertical: 1,
borderWidth: 0.5,
borderColor: 'purple',
borderRadius: 8,
color: 'black',
paddingRight: 30, // To ensure the text is never behind the icon
backgroundColor: 'white',
marginBottom: 10,
},
});
export default PollModal;
//# sourceMappingURL=PollModal.js.map