diff --git a/components/CreateAnnouncementButton.js b/components/CreateAnnouncementButton.js new file mode 100644 index 00000000..62eff8d8 --- /dev/null +++ b/components/CreateAnnouncementButton.js @@ -0,0 +1,41 @@ +import React from 'react'; +import { TouchableOpacity, Platform } from 'react-native'; +import { withNavigation } from 'react-navigation'; +import { connect } from 'react-redux'; +import { Ionicons } from '@expo/vector-icons'; + +import { userHasAnyOfGroups } from '../utils/User'; +import { getPlatformSpecificIconName } from '../utils/Icons'; + +class CreateAnnouncementButton extends React.Component { + + render() { + if (!this.props.isAdmin) { + return null; + } + + return ( + + + + ); + } + + _createAnnouncementButtonPress = () => { + this.props.navigation.navigate('CreateAnnouncement'); + } + +} + +function mapStateToProps(state) { + const { auth } = state; + return { + isAdmin: auth.user !== null && userHasAnyOfGroups(auth.user, 'admin'), + }; +} + +export default withNavigation(connect(mapStateToProps)(CreateAnnouncementButton)); \ No newline at end of file diff --git a/config/config.js b/config/config.js index dfd7bac3..9d3568ca 100644 --- a/config/config.js +++ b/config/config.js @@ -26,6 +26,10 @@ export default { // Size of the icons on the tab bar TAB_NAVIGATOR_ICON_SIZE: 20, + // Window of time (in hours) around the start and end + // of hacking where announcements can be sent + ANNOUNCEMENT_TIME_WINDOW_PADDING: 10, + // A list of colors for the app. It would be nice // to be able to dynamically fill the list of colors, // but I guess we have to define them at compile diff --git a/package.json b/package.json index af749d31..144ff8f3 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,12 @@ }, "dependencies": { "expo": "^32.0.0", + "formik": "^1.5.7", + "moment": "^2.24.0", "react": "16.5.0", "react-clock": "^2.3.0", "react-native": "https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz", + "react-native-datepicker": "^1.7.2", "react-native-events-calendar": "github:joshjhargreaves/react-native-event-calendar#3f49b15439bc4ae28f16fbc4d53391fce955c199", "react-native-paper": "^2.15.2", "react-native-qrcode-svg": "^5.1.2", @@ -19,7 +22,8 @@ "react-navigation-material-bottom-tabs": "^1.0.0", "react-redux": "^6.0.1", "redux": "^4.0.1", - "redux-thunk": "^2.3.0" + "redux-thunk": "^2.3.0", + "yup": "^0.27.0" }, "devDependencies": { "babel-preset-expo": "^5.0.0" diff --git a/screens/Announcements.js b/screens/Announcements.js index 699c8050..04e36f1e 100644 --- a/screens/Announcements.js +++ b/screens/Announcements.js @@ -1,12 +1,13 @@ import React from 'react'; -import { View, Text, Header, Platform } from 'react-native'; -import { SafeAreaView, createStackNavigator } from 'react-navigation'; +import { View, Text } from 'react-native'; +import { createStackNavigator } from 'react-navigation'; import { FlatList } from 'react-native'; -import Ionicons from '@expo/vector-icons/Ionicons'; import { connect } from 'react-redux'; import Announcement from '../components/Announcement'; import { fetchAnnouncements } from '../actions/Announcements'; +import CreateAnnouncementButton from '../components/CreateAnnouncementButton'; +import CreateAnnouncementScreen from './CreateAnnouncement'; class AnnouncementsScreen extends React.Component { @@ -44,7 +45,13 @@ export default stackNavigator = createStackNavigator({ screen: connect(mapStateToProps)(AnnouncementsScreen), navigationOptions: { title: 'Announcements', - headerRight: (), + headerRight: , } }, + CreateAnnouncement: { + screen: CreateAnnouncementScreen, + navigationOptions: { + title: 'Create Announcement', + } + } }); \ No newline at end of file diff --git a/screens/CreateAnnouncement.js b/screens/CreateAnnouncement.js new file mode 100644 index 00000000..76de4990 --- /dev/null +++ b/screens/CreateAnnouncement.js @@ -0,0 +1,212 @@ +import React from 'react'; +import { Alert, View, StyleSheet, Picker, TextInput, Text, ScrollView, SafeAreaView } from 'react-native'; +import { withNavigation } from 'react-navigation'; +import { connect } from 'react-redux'; +import { Formik } from 'formik'; +import * as yup from 'yup'; +import DatePicker from 'react-native-datepicker'; +import moment from 'moment'; + +import Config from '../config/config'; +import Endpoints from '../config/endpoints'; +import Button from '../components/Button'; + + +class CreateAnnouncementScreen extends React.Component { + + state = { + isPostingAnnouncement: false, + }; + + render() { + // Create a window of Config.ANNOUNCEMENT_TIME_WINDOW_PADDING hours + // on each side of hacking around which announcements can be created. + const datePickerMinDate = new Date(this.props.hackingStartTime.getTime() - 1000 * 60 * 60 * Config.ANNOUNCEMENT_TIME_WINDOW_PADDING); + const datePickerMaxDate = new Date(this.props.hackingEndTime.getTime() + 1000 * 60 * 60 * Config.ANNOUNCEMENT_TIME_WINDOW_PADDING); + + // Get the initial time for the date picker. If during the window + // set it to now, if before the window set it to the start, + // and if after the window set it to the end. + const datePickerInitialDate = new Date(Math.min(Math.max(datePickerMinDate, Date.now()), datePickerMaxDate)); + + return ( + + this.postAnnouncement(values)} + validationSchema={yup.object().shape({ + title: yup + .string() + .required(), + body: yup + .string() + .required(), + })} + > + {({ values, handleChange, setFieldValue, errors, setFieldTouched, touched, isValid, handleSubmit }) => ( + + + + + + Title + setFieldTouched('title')} + autoCapitalize='words' + placeholder='Title' + fontSize={20} + style={styles.textInput} + /> + {touched.title && errors.title && + {errors.title} + } + + + Body + setFieldTouched('body')} + style={styles.textInput} + /> + {touched.body && errors.body && + {errors.body} + } + + + Category + setFieldValue('category', itemValue)} + > + + + + + + + + + Broadcast Time + + setFieldValue('broadcastTime', date)} + getDateStr={date => moment(date).format('MMM DD [at] h:mm a')} + /> + + + + +