├── .eslintrc.js ├── .expo └── settings.json ├── .github └── pull_request_template.md ├── .gitignore ├── .prettierrc ├── App.tsx ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app.json ├── assets ├── favicon.png ├── icon.png └── splash.png ├── babel.config.js ├── components ├── Feed.tsx ├── JuniorDash.tsx ├── JuniorFeed.tsx ├── JuniorProfile.tsx ├── JuniorSignup.tsx ├── JuniorTickets.tsx ├── LoginPage.tsx ├── MapView.tsx ├── NewTicket.tsx ├── NewTicketDesc.tsx ├── SeniorDash.tsx ├── SeniorSignup.tsx ├── SeniorTicket.tsx ├── Splash.tsx ├── StartingPage.tsx └── Ticket.tsx ├── package-lock.json ├── package.json ├── server ├── index.ts ├── routes │ └── loginRouter.ts └── schema │ ├── model.ts │ ├── schema.ts │ └── types.ts ├── tsconfig.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2020: true, 5 | }, 6 | extends: [ 7 | 'plugin:react/recommended', 8 | 'prettier', 9 | 'prettier/flowtype', // if you are using flow 10 | 'prettier/react', 11 | ], 12 | parser: 'babel-eslint', 13 | parserOptions: { 14 | ecmaFeatures: { 15 | jsx: true, 16 | }, 17 | ecmaVersion: 11, 18 | sourceType: 'module', 19 | }, 20 | plugins: ['flowtype', 'react', 'jsx-a11y', 'prettier'], 21 | rules: { 22 | 'prettier/prettier': ['error'], 23 | 'react/prop-types': 'off', 24 | }, 25 | settings: { 26 | react: { 27 | version: 'detect', 28 | }, 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /.expo/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "hostType": "lan", 3 | "lanType": "ip", 4 | "dev": true, 5 | "minify": false, 6 | "urlRandomness": null, 7 | "https": false 8 | } 9 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Types of changes 2 | 3 | - [ ] Bugfix (change which fixes an issue) 4 | - [ ] New feature (change which adds functionality) 5 | - [ ] Refactor (change which changes the codebase without affecting its external behavior) 6 | - [ ] Non-breaking change (fix or feature that would causes existing functionality to work as expected) 7 | - [ ] Breaking change (fix or feature that would cause existing functionality to __not__ work as expected) 8 | ## Purpose 9 | 10 | ## Approach 11 | 12 | ## Resources 13 | 14 | ## Screenshot(s) 15 | 16 | 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .expo 4 | .env 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "bracketSpacing": false, 6 | "jsxBracketSameLine": true, 7 | "endOfLine": "auto", 8 | "overrides": [ 9 | { 10 | "files": ["*.js", "*.jsx", "*.ts", "*.tsx"], 11 | "options": { 12 | "parser": "flow" 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState, createContext} from 'react'; 2 | import {NavigationContainer, StackActions} from '@react-navigation/native'; 3 | import {createStackNavigator} from '@react-navigation/stack'; 4 | import {ApolloClient, InMemoryCache, ApolloProvider} from '@apollo/client'; 5 | 6 | import Splash from './components/Splash'; 7 | import StartingPage from './components/StartingPage'; 8 | import SeniorSignup from './components/SeniorSignup'; 9 | import SeniorDash from './components/SeniorDash'; 10 | import JuniorSignup from './components/JuniorSignup'; 11 | import JuniorDash from './components/JuniorDash'; 12 | import LoginPage from './components/LoginPage'; 13 | import NewTicket from './components/NewTicket'; 14 | import NewTicketDesc from './components/NewTicketDesc'; 15 | import Map from './components/MapView'; 16 | 17 | const client = new ApolloClient({ 18 | uri: 'http://localhost:3000/graphql', 19 | cache: new InMemoryCache(), 20 | }); 21 | 22 | const Stack = createStackNavigator(); 23 | // export const AuthContext = createContext({}); 24 | export default function App() { 25 | // global state for whoever's ID 26 | const [authID, setAuthID] = useState(null); 27 | return ( 28 | 29 | 30 | {/* */} 31 | 32 | 33 | 34 | {/* */} 35 | 36 | {props => ( 37 | 38 | )} 39 | 40 | 41 | 42 | {props => ( 43 | 44 | )} 45 | 46 | 47 | {props => ( 48 | 49 | )} 50 | 51 | 52 | 53 | 54 | 55 | 56 | {/* */} 57 | 58 | 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Generation Bridge 2 | 3 | We want to make contributing to this project as easy as possible. 4 | 5 | ## Pull Requests 6 | 7 | Please feel free to submit any PR's. The team will happily review them. 8 | 9 | 1. Fork the repo and create your branch from `master`. 10 | 2. If you've added code that should be tested, please add tests. 11 | 3. Ensure the test suite passes. 12 | 4. Make sure your code lints and is formatted with `prettier`. 13 | 14 | ## Issues 15 | 16 | We use GitHub issues to track public bugs. Please ensure your description is clear and has sufficient instructions to be able to reproduce the issue. 17 | 18 | ## License 19 | 20 | By contributing to Generation Bridge, you agree that your contributions will be licensed under the LICENSE file in the root directory of this source tree. 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 squirtlesquad123 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Generation Bridge (Gen-B) 2 | 3 | An open source, social welfare mobile application for volunteers and elderly-in-need 4 | 5 | ## About 6 | 7 | Generation Bridge (Gen-B) is a social welfare mobile application built with a motive to connect able-bodied, younger volunteers with the elderly-in-need in times of COVID-19. 8 | 9 | ## Features 10 | 11 | - Signup/Login as a volunteer or elderly-in-need 12 | 13 | - As a volunteer, users are able to claim 'tickets' that are submitted by the elderly. Users are also able to view the general location of the submitted tickets. 14 | 15 | - Volunteers can see their list of claimed tickets. 16 | 17 | - As an elderly, users are able to submit 'tickets' that indicate they need help with categories such as errands, conversation, and etc. 18 | 19 | - Elderly can check their tickets based on open/close status. 20 | 21 | ## Contributions 22 | 23 | Development of Generation Bridge is open on GitHub, and the team will appreciate any feedback, contributions, bugfixes, and improvements. 24 | 25 | We want to make contributing to this project as easy as possible. 26 | 27 | [Contribution Guidelines](CONTRIBUTING.md) 28 | 29 | ## Try It Out! 30 | 31 | 1. Fork this repo and install dependencies. 32 | 33 | `npm install` 34 | 35 | 2. Follow this [Expo Guide](https://docs.expo.io/guides/) to connect with your phone using expo client or run the iOS/Android simuator. 36 | 37 | 3) Run the app. 38 | 39 | `npm start` 40 | 41 | ### License 42 | 43 | Generation Bridge is [MIT Licensed](LICENSE) 44 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "youngandold", 4 | "slug": "youngandold", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "splash": { 9 | "image": "./assets/splash.png", 10 | "resizeMode": "contain", 11 | "backgroundColor": "#ffffff" 12 | }, 13 | "updates": { 14 | "fallbackToCacheTimeout": 0 15 | }, 16 | "assetBundlePatterns": [ 17 | "**/*" 18 | ], 19 | "ios": { 20 | "supportsTablet": true 21 | }, 22 | "web": { 23 | "favicon": "./assets/favicon.png" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Generation-Bridge/Gen-B/ebc6af5eb03d65d667e67d66c4a8857ef3f44c0d/assets/favicon.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Generation-Bridge/Gen-B/ebc6af5eb03d65d667e67d66c4a8857ef3f44c0d/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Generation-Bridge/Gen-B/ebc6af5eb03d65d667e67d66c4a8857ef3f44c0d/assets/splash.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /components/Feed.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react' 2 | import { StyleSheet, Text, View, RefreshControl } from 'react-native' 3 | import SeniorTicket from './SeniorTicket' 4 | import { useQuery, gql } from '@apollo/client'; 5 | import { ScrollView } from 'react-native-gesture-handler'; 6 | 7 | 8 | const GET_TEST = gql` 9 | query { 10 | tasks { 11 | id 12 | type 13 | description 14 | deadline 15 | completed 16 | } 17 | }` 18 | const Feed = () => { 19 | // useEffect(() => { 20 | // console.log('useeffect') 21 | // }, []) 22 | const [refreshing, useRefresh] = useState('false'); 23 | const onRefresh = () => { 24 | console.log('refreshing'); 25 | } 26 | 27 | //wrapper for useQuery 28 | 29 | 30 | 31 | const { loading, error, data } = useQuery(GET_TEST); 32 | if (loading) return Loading...; 33 | if (error) { 34 | console.log('error', error) 35 | return Error :( ; 36 | } 37 | let tickets; 38 | try { 39 | tickets = data.tasks.map( task => ).reverse(); 40 | } catch (e) { 41 | console.log('error in gql feed', e); 42 | } 43 | // console.log('tickets', tickets) 44 | return ( 45 | } 52 | > 53 | {tickets} 54 | 55 | ) 56 | } 57 | 58 | export default Feed 59 | 60 | const styles = StyleSheet.create({ 61 | container: { 62 | width: '100%', 63 | } 64 | }) 65 | 66 | -------------------------------------------------------------------------------- /components/JuniorDash.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet} from 'react-native'; 3 | import {FontAwesome} from '@expo/vector-icons'; 4 | import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'; 5 | 6 | import JuniorFeed from './JuniorFeed'; 7 | import JuniorTickets from './JuniorTickets'; 8 | import JuniorProfile from './JuniorProfile'; 9 | 10 | const Tab = createBottomTabNavigator(); 11 | 12 | export default function JuniorDash({authID}) { 13 | console.log('authID in dash after signing up', authID); 14 | return ( 15 | ({ 17 | tabBarIcon: ({focused, color, size}) => { 18 | let iconName; 19 | 20 | if (route.name === 'Feed') { 21 | iconName = 'feed'; 22 | color = focused ? 'red' : 'black'; 23 | } else if (route.name === 'JuniorTickets') { 24 | iconName = 'ticket'; 25 | color = focused ? 'red' : 'black'; 26 | } else if (route.name === 'JuniorProfile') { 27 | iconName = 'user'; 28 | color = focused ? 'red' : 'black'; 29 | } 30 | 31 | // You can return any component that you like here! 32 | return ; 33 | }, 34 | })} 35 | tabBarOptions={{ 36 | activeTintColor: 'tomato', 37 | inactiveTintColor: 'gray', 38 | showLabel: false, 39 | }}> 40 | 41 | {props => } 42 | 43 | 44 | {props => } 45 | 46 | 47 | 48 | ); 49 | } 50 | 51 | /* 52 | {props => ( 53 | 54 | )} 55 | 56 | */ 57 | const styles = StyleSheet.create({ 58 | container: { 59 | flex: 1, 60 | alignItems: 'center', 61 | }, 62 | submit: { 63 | width: '80%', 64 | backgroundColor: 'lightgreen', 65 | }, 66 | tabs: { 67 | flex: 1, 68 | alignSelf: 'flex-end', 69 | width: '40%', 70 | backgroundColor: 'red', 71 | height: 20, 72 | }, 73 | buttonView: { 74 | flexDirection: 'row', 75 | justifyContent: 'space-around', 76 | }, 77 | }); 78 | -------------------------------------------------------------------------------- /components/JuniorFeed.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, Text, ScrollView} from 'react-native'; 3 | import Ticket from './Ticket'; 4 | import {useQuery, gql, useApolloClient} from '@apollo/client'; 5 | 6 | const GET_TASKS = gql` 7 | query { 8 | tasks { 9 | id 10 | seniorname 11 | description 12 | type 13 | } 14 | } 15 | `; 16 | 17 | const JuniorFeed = ({authID}) => { 18 | console.log('authID in JuniorFeed', authID); 19 | const {loading, error, data} = useQuery(GET_TASKS); 20 | if (loading) return Loading...; 21 | if (error) { 22 | console.log('error', error); 23 | return Error :( ; 24 | } 25 | // console.log('data', data); 26 | 27 | // const tickets = data.helpers[0].map( task => ) 28 | const tickets = data.tasks.map(task => ( 29 | 30 | )); 31 | // console.log('tickets', tickets) 32 | return ( 33 | 34 | CLAIM YOUR TICKETS 35 | {tickets} 36 | {/* 37 | 38 | */} 39 | 40 | ); 41 | }; 42 | 43 | export default JuniorFeed; 44 | 45 | const styles = StyleSheet.create({ 46 | container: { 47 | width: '100%', 48 | }, 49 | text: { 50 | marginVertical: 10, 51 | fontSize: 25, 52 | alignSelf: 'center', 53 | }, 54 | }); 55 | -------------------------------------------------------------------------------- /components/JuniorProfile.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, Text, View} from 'react-native'; 3 | 4 | const JuniorProfile = () => { 5 | return ( 6 | 7 | Profile Page! 8 | 9 | ); 10 | }; 11 | 12 | export default JuniorProfile; 13 | 14 | const styles = StyleSheet.create({}); 15 | -------------------------------------------------------------------------------- /components/JuniorSignup.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import { 3 | StyleSheet, 4 | Text, 5 | View, 6 | TouchableWithoutFeedback, 7 | TextInput, 8 | Keyboard, 9 | TouchableOpacity, 10 | } from 'react-native'; 11 | import {gql, useMutation} from '@apollo/client'; 12 | 13 | interface signupState { 14 | name: string; 15 | email: string; 16 | phoneNumber: string; 17 | password: string; 18 | zipCode: any; 19 | } 20 | // TODO: change occupation to zip code when Andy modifies DB 21 | const ADD_HELPER = gql` 22 | mutation addHelper( 23 | $name: String! 24 | $email: String! 25 | $phone: String! 26 | $password: String! 27 | $zipCode: Int 28 | ) { 29 | addHelper( 30 | name: $name 31 | email: $email 32 | phone: $phone 33 | password: $password 34 | zipcode: $zipCode 35 | ) { 36 | id 37 | } 38 | } 39 | `; 40 | 41 | const JuniorSignup = ({navigation, authID, setAuthID}) => { 42 | // const {setAuthID} = useContext(AuthContext); 43 | const initialState: signupState = { 44 | name: '', 45 | email: '', 46 | phoneNumber: '', 47 | password: '', 48 | zipCode: '', 49 | }; 50 | // console.log('authID in signup', authID); 51 | 52 | // state for the forms 53 | const [form, setForm] = useState(initialState); 54 | 55 | // mutation hook to send form data to backend 56 | const [addHelper, {data, error}] = useMutation(ADD_HELPER); 57 | 58 | const handleSubmit = async () => { 59 | const {name, email, phoneNumber, password, zipCode} = form; 60 | const numberedZip = Number(zipCode); 61 | // TODO : need some sort of validation for the forms before we send to DB 62 | try { 63 | const {data} = await addHelper({ 64 | variables: { 65 | name, 66 | email, 67 | phone: phoneNumber, 68 | password, 69 | zipCode: numberedZip, 70 | }, 71 | }); 72 | console.log('data', data.addHelper.id); 73 | const uID = Number(data.addHelper.id); 74 | setAuthID(uID); 75 | setForm(initialState); 76 | navigation.navigate('JuniorDash'); 77 | } catch (error) { 78 | console.log('error', error); 79 | } 80 | 81 | // TODO: once id returns, need to set it to global auth state (maybe use onCompleted for useMutation?) 82 | }; 83 | return ( 84 | 85 | 86 | Name 87 | setForm({...form, name: text})} 91 | value={form.name} 92 | /> 93 | Email 94 | setForm({...form, email: text})} 98 | value={form.email} 99 | /> 100 | Phone Number 101 | setForm({...form, phoneNumber: text})} 106 | value={form.phoneNumber} 107 | /> 108 | Zip Code 109 | setForm({...form, zipCode: text})} 114 | value={form.zipCode} 115 | /> 116 | Password 117 | setForm({...form, password: text})} 122 | value={form.password} 123 | /> 124 | 125 | 126 | Submit 127 | 128 | 129 | 130 | ); 131 | }; 132 | 133 | export default JuniorSignup; 134 | 135 | const styles = StyleSheet.create({ 136 | container: { 137 | flex: 1, 138 | backgroundColor: '#fff', 139 | alignItems: 'center', 140 | justifyContent: 'flex-start', 141 | }, 142 | 143 | inputFields: { 144 | width: '70%', 145 | borderColor: 'lightblue', 146 | borderStyle: 'solid', 147 | borderWidth: 1, 148 | fontSize: 25, 149 | marginBottom: 10, 150 | }, 151 | labels: { 152 | fontSize: 25, 153 | marginBottom: 5, 154 | }, 155 | 156 | button: { 157 | backgroundColor: 'lightblue', 158 | borderColor: 'lightblue', 159 | borderStyle: 'solid', 160 | borderWidth: 1, 161 | padding: 10, 162 | marginTop: 10, 163 | }, 164 | 165 | buttonText: { 166 | fontSize: 20, 167 | }, 168 | }); 169 | -------------------------------------------------------------------------------- /components/JuniorTickets.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, Text, View, TouchableOpacity} from 'react-native'; 3 | import {gql, useQuery} from '@apollo/client'; 4 | 5 | import Ticket from './Ticket'; 6 | // TODO: get ID of whoever is logged in so that we can query the tasks assigned to specific id 7 | 8 | const GET_TASK_FOR_HELPER = gql` 9 | query Helper($id: Int!) { 10 | helper(id: $id) { 11 | tasks { 12 | seniorname 13 | description 14 | type 15 | } 16 | } 17 | } 18 | `; 19 | 20 | const JuniorTickets = ({authID}) => { 21 | console.log('authID in junior ticketss', authID); 22 | const {loading, error, data} = useQuery(GET_TASK_FOR_HELPER, { 23 | // userID hardcoded to 34, we need to figure out why dynamic authID isn't working 24 | variables: {id: 34}, 25 | }); 26 | if (error) return `Error! ${error.message}`; 27 | 28 | console.log('data from query', data); 29 | 30 | const tickets = data.helper.tasks.map(task => ( 31 | 32 | )); 33 | 34 | return {tickets}; 35 | }; 36 | 37 | export default JuniorTickets; 38 | 39 | const styles = StyleSheet.create({ 40 | container: { 41 | width: '100%', 42 | }, 43 | text: { 44 | marginVertical: 10, 45 | fontSize: 25, 46 | alignSelf: 'center', 47 | }, 48 | }); 49 | -------------------------------------------------------------------------------- /components/LoginPage.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import { 3 | StyleSheet, 4 | Text, 5 | View, 6 | Keyboard, 7 | TouchableOpacity, 8 | TouchableWithoutFeedback, 9 | } from 'react-native'; 10 | import {TextInput} from 'react-native-gesture-handler'; 11 | 12 | interface loginState { 13 | emailOrPhone: string; 14 | password: string; 15 | } 16 | 17 | const LoginPage: React.FC = () => { 18 | // initial state for login form 19 | const initialState: loginState = { 20 | emailOrPhone: '', 21 | password: '', 22 | }; 23 | 24 | // state for login form 25 | const [loginForm, setLoginForm] = useState(initialState); 26 | 27 | // handle submit for button 28 | const handleSubmit = (): void => {}; 29 | return ( 30 | 31 | 32 | Log In 33 | Email or Phone Number 34 | 38 | setLoginForm({...loginForm, emailOrPhone: text}) 39 | } 40 | value={loginForm.emailOrPhone} 41 | /> 42 | Password 43 | setLoginForm({...loginForm, password: text})} 47 | value={loginForm.password} 48 | /> 49 | 50 | 51 | Submit 52 | 53 | 54 | 55 | ); 56 | }; 57 | 58 | export default LoginPage; 59 | 60 | const styles = StyleSheet.create({ 61 | container: { 62 | flex: 1, 63 | backgroundColor: '#fff', 64 | alignItems: 'center', 65 | justifyContent: 'flex-start', 66 | }, 67 | 68 | inputFields: { 69 | width: '70%', 70 | borderColor: 'lightblue', 71 | borderStyle: 'solid', 72 | borderWidth: 1, 73 | fontSize: 25, 74 | marginBottom: 10, 75 | }, 76 | 77 | labels: { 78 | fontSize: 25, 79 | marginBottom: 5, 80 | }, 81 | 82 | button: { 83 | backgroundColor: 'lightblue', 84 | borderColor: 'lightblue', 85 | borderStyle: 'solid', 86 | borderWidth: 1, 87 | padding: 10, 88 | marginTop: 10, 89 | }, 90 | 91 | buttonText: { 92 | fontSize: 20, 93 | }, 94 | 95 | Text: { 96 | fontSize: 40, 97 | marginTop: 40, 98 | marginBottom: 30, 99 | }, 100 | }); 101 | -------------------------------------------------------------------------------- /components/MapView.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import MapView from 'react-native-maps'; 3 | import {StyleSheet, Text, View, Dimensions} from 'react-native'; 4 | 5 | function Map() { 6 | const availableLocations = [ 7 | { 8 | latitude: 37.78825, 9 | longitude: -122.4324, 10 | latitudeDelta: 0.0922, 11 | longitudeDelta: 0.0421, 12 | }, 13 | { 14 | latitude: 34.04544, 15 | longitude: -118.445848, 16 | latitudeDelta: 0.0922, 17 | longitudeDelta: 0.0421, 18 | }, 19 | { 20 | latitude: 34.136254, 21 | longitude: -118.026447, 22 | latitudeDelta: 0.0922, 23 | longitudeDelta: 0.0421, 24 | }, 25 | { 26 | latitude: 40.682294, 27 | longitude: -73.978018, 28 | latitudeDelta: 0.0922, 29 | longitudeDelta: 0.0421, 30 | }, 31 | { 32 | latitude: 42.35052, 33 | longitude: -71.058802, 34 | latitudeDelta: 0.0922, 35 | longitudeDelta: 0.0421, 36 | }, 37 | { 38 | latitude: 47.610903, 39 | longitude: -122.336229, 40 | latitudeDelta: 0.0922, 41 | longitudeDelta: 0.0421, 42 | }, 43 | { 44 | latitude: 34.419007, 45 | longitude: -119.709215, 46 | latitudeDelta: 0.0922, 47 | longitudeDelta: 0.0421, 48 | }, 49 | ]; 50 | 51 | return ( 52 | 53 | 61 | 62 | ); 63 | } 64 | 65 | const styles = StyleSheet.create({ 66 | container: { 67 | flex: 1, 68 | backgroundColor: '#fff', 69 | alignItems: 'center', 70 | justifyContent: 'center', 71 | }, 72 | mapStyle: { 73 | width: Dimensions.get('window').width, 74 | height: Dimensions.get('window').height, 75 | }, 76 | }); 77 | 78 | export default Map; 79 | -------------------------------------------------------------------------------- /components/NewTicket.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { StyleSheet, Text, View, TouchableOpacity, Picker } from 'react-native' 3 | 4 | const NewTicket = ({navigation}) => { 5 | return ( 6 | 7 | What do you need help with? 8 | navigation.navigate('NewTicketDesc', {typeid: 100})} 11 | > 12 | Errands 13 | 14 | navigation.navigate('NewTicketDesc', {typeid: 200})} 17 | > 18 | Conversation 19 | 20 | navigation.navigate('NewTicketDesc', {typeid: 300})} 23 | > 24 | Household Chores 25 | 26 | 27 | ) 28 | } 29 | 30 | export default NewTicket 31 | 32 | const styles = StyleSheet.create({ 33 | container: { 34 | flex: 1, 35 | alignItems: 'center', 36 | justifyContent: 'center' 37 | }, 38 | choices: { 39 | width: '82%', 40 | backgroundColor: 'dodgerblue', 41 | borderStyle: 'solid', 42 | padding: 10, 43 | borderRadius: 10, 44 | borderWidth: 5, 45 | borderColor: 'black', 46 | display: 'flex', 47 | alignItems: 'center', 48 | margin: 10 49 | }, 50 | choiceText: { 51 | fontSize: 30, 52 | }, 53 | }) 54 | -------------------------------------------------------------------------------- /components/NewTicketDesc.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { StyleSheet, Text, View, TextInput, TouchableOpacity} from 'react-native' 3 | import { gql, useMutation } from '@apollo/client' 4 | 5 | 6 | const ADD_TASK = gql` 7 | mutation addTask( 8 | $id: Int! 9 | $typeid: Int 10 | $description: String 11 | ) { 12 | addTask( 13 | senior: $id 14 | typeid: $typeid 15 | description: $description 16 | ) { 17 | id 18 | } 19 | } 20 | ` 21 | 22 | const NewTicketDesc = ({navigation, route}) => { 23 | const [value, onChangeText] = React.useState(''); 24 | const [addTask, { data, error }] = useMutation(ADD_TASK); 25 | 26 | const handleSubmit = async () => { 27 | try { 28 | const mutation = await addTask({ 29 | variables: { 30 | id: 3, 31 | typeid: route.params.typeid, 32 | description: value 33 | }, 34 | }); 35 | navigation.navigate('SeniorDash', {fetch: true}); 36 | } catch (e) { 37 | console.log('error in addTask', e) 38 | } 39 | 40 | } 41 | console.log('params', route.params) 42 | return ( 43 | 44 | Please describe your request. 45 | onChangeText(text)} 50 | value={value} 51 | /> 52 | 56 | Submit 57 | 58 | 59 | ) 60 | } 61 | 62 | export default NewTicketDesc 63 | 64 | const styles = StyleSheet.create({ 65 | container: { 66 | flex: 1, 67 | alignItems: 'center' 68 | }, 69 | text: { 70 | fontSize: 27 71 | }, 72 | textBox: { 73 | height: '45%', 74 | width: '82%', 75 | fontSize: 30, 76 | borderWidth: 1, 77 | borderColor: 'black', 78 | borderRadius: 10, 79 | borderWidth: 5, 80 | justifyContent: 'flex-start' 81 | }, 82 | submit: { 83 | width: '82%', 84 | backgroundColor: 'lightgreen', 85 | borderStyle: 'solid', 86 | padding: 10, 87 | borderRadius: 10, 88 | borderWidth: 5, 89 | borderColor: 'black', 90 | display: 'flex', 91 | alignItems: 'center', 92 | }, 93 | }) 94 | -------------------------------------------------------------------------------- /components/SeniorDash.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, Text, View, TouchableOpacity} from 'react-native'; 3 | import Feed from './Feed' 4 | 5 | export default function SeniorDash({navigation, route}) { 6 | console.log('navigation', navigation) 7 | console.log('route', route) 8 | return ( 9 | 10 | navigation.navigate('NewTicket')} 13 | > 14 | New Ticket 15 | 16 | 19 | 22 | Open 23 | 24 | 27 | Closed 30 | 31 | 32 | 33 | 34 | ); 35 | } 36 | 37 | const styles = StyleSheet.create({ 38 | container: { 39 | flex: 1, 40 | alignItems: 'center', 41 | }, 42 | submit: { 43 | width: '82%', 44 | backgroundColor: 'lightgreen', 45 | borderStyle: 'solid', 46 | padding: 10, 47 | borderRadius: 10, 48 | borderWidth: 5, 49 | borderColor: 'black', 50 | display: 'flex', 51 | alignItems: 'center', 52 | }, 53 | submitText: { 54 | fontSize: 30, 55 | }, 56 | buttonView: { 57 | flexDirection: 'row', 58 | justifyContent: 'space-around', 59 | }, 60 | tabOpen: { 61 | margin: 5, 62 | width: '40%', 63 | backgroundColor: 'dodgerblue', 64 | borderStyle: 'solid', 65 | padding: 10, 66 | borderRadius: 10, 67 | borderWidth: 5, 68 | borderColor: 'black', 69 | alignItems: 'center' 70 | }, 71 | tabClosed: { 72 | margin: 5, 73 | width: '40%', 74 | backgroundColor: 'tomato', 75 | borderStyle: 'solid', 76 | padding: 10, 77 | borderRadius: 10, 78 | borderWidth: 5, 79 | borderColor: 'black', 80 | alignItems: 'center' 81 | }, 82 | tabsText: { 83 | fontSize: 25 84 | }, 85 | }) 86 | -------------------------------------------------------------------------------- /components/SeniorSignup.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import {useQuery, gql, useMutation} from '@apollo/client'; 3 | import { 4 | StyleSheet, 5 | Text, 6 | View, 7 | TouchableOpacity, 8 | TouchableWithoutFeedback, 9 | Keyboard, 10 | } from 'react-native'; 11 | import {TextInput} from 'react-native-gesture-handler'; 12 | 13 | interface signUpState { 14 | firstName: string; 15 | lastName: string; 16 | email: string; 17 | phoneNumber: string; 18 | zipCode: string; 19 | password: string; 20 | } 21 | 22 | const ADD_SENIOR = gql` 23 | mutation addSenior( 24 | $name: String! 25 | $phone: String! 26 | $password: String! 27 | $zipCode: Int! 28 | $email: String 29 | ) { 30 | addSenior( 31 | name: $name 32 | phone: $phone 33 | password: $password 34 | zipcode: $zipCode 35 | email: $email 36 | ) { 37 | id 38 | } 39 | } 40 | `; 41 | 42 | 43 | const SeniorSignup: React.FC = ({navigation}) => { 44 | // inital state for the forms 45 | const [addSenior, {data, error}] = useMutation(ADD_SENIOR); 46 | 47 | 48 | const initialState: signUpState = { 49 | firstName: '', 50 | lastName: '', 51 | email: '', 52 | phoneNumber: '', 53 | zipCode: '', 54 | password: '', 55 | }; 56 | 57 | // if (loading) return 'Loading...'; 58 | // if (error) return `Error! ${error.message}`; 59 | 60 | // state for the forms 61 | const [form, setForm] = useState(initialState); 62 | // console.log('state change', form); 63 | 64 | // handle submit when submit button is clicked 65 | const handleSubmit = async () => { 66 | console.log('form', form) 67 | const {firstName, lastName, phoneNumber, password, zipCode, email} = form; 68 | const numberedZip = Number(zipCode); 69 | const name = `${firstName} ${lastName}` 70 | // TODO : need some sort of validation for the forms before we send to DB 71 | try { 72 | const {data} = await addSenior({ 73 | variables: { 74 | name, 75 | phone: phoneNumber, 76 | email, 77 | password, 78 | zipCode: numberedZip, 79 | }, 80 | }); 81 | setForm(initialState); 82 | navigation.navigate('SeniorDash'); 83 | } catch (error) { 84 | console.log('error', error); 85 | } 86 | }; 87 | 88 | return ( 89 | 90 | 91 | Sign Up 92 | First Name 93 | setForm({...form, firstName: text})} 97 | value={form.firstName} 98 | /> 99 | Last Name 100 | setForm({...form, lastName: text})} 104 | value={form.lastName} 105 | /> 106 | Email 107 | setForm({...form, email: text})} 111 | value={form.email} 112 | /> 113 | Phone Number 114 | setForm({...form, phoneNumber: text})} 119 | value={form.phoneNumber} 120 | /> 121 | Zip Code 122 | setForm({...form, zipCode: text})} 127 | value={form.zipCode} 128 | /> 129 | Password 130 | setForm({...form, password: text})} 135 | value={form.password} 136 | /> 137 | 140 | Submit 141 | 142 | 143 | 144 | ); 145 | }; 146 | 147 | export default SeniorSignup; 148 | 149 | const styles = StyleSheet.create({ 150 | container: { 151 | flex: 1, 152 | backgroundColor: '#fff', 153 | alignItems: 'center', 154 | justifyContent: 'flex-start', 155 | }, 156 | 157 | inputFields: { 158 | width: '70%', 159 | borderColor: 'lightblue', 160 | borderStyle: 'solid', 161 | borderWidth: 1, 162 | fontSize: 25, 163 | marginBottom: 10, 164 | }, 165 | labels: { 166 | fontSize: 25, 167 | marginBottom: 5, 168 | }, 169 | 170 | button: { 171 | backgroundColor: 'lightblue', 172 | borderColor: 'lightblue', 173 | borderStyle: 'solid', 174 | borderWidth: 1, 175 | padding: 10, 176 | marginTop: 10, 177 | }, 178 | 179 | buttonText: { 180 | fontSize: 20, 181 | }, 182 | 183 | Text: { 184 | fontSize: 40, 185 | marginVertical: 20, 186 | }, 187 | }); 188 | -------------------------------------------------------------------------------- /components/SeniorTicket.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Text, View, StyleSheet } from 'react-native' 3 | 4 | 5 | const SeniorTicket = ({task}) => { 6 | return ( 7 | 8 | Type: {task.type} 9 | Description: {task.description} 10 | 11 | ) 12 | } 13 | 14 | const styles = StyleSheet.create({ 15 | container: { 16 | flexDirection: 'column', 17 | alignItems: 'flex-start', 18 | justifyContent: 'center', 19 | backgroundColor: 'lightblue', 20 | margin: 5, 21 | borderStyle: 'solid', 22 | borderRadius: 10, 23 | borderWidth: 5, 24 | borderColor: 'black', 25 | display: 'flex', 26 | }, 27 | text: { 28 | fontSize: 25, 29 | fontWeight: 'bold' 30 | } 31 | }) 32 | 33 | export default SeniorTicket -------------------------------------------------------------------------------- /components/Splash.tsx: -------------------------------------------------------------------------------- 1 | import {StatusBar} from 'expo-status-bar'; 2 | import React from 'react'; 3 | import {StyleSheet, Text, View, TouchableOpacity} from 'react-native'; 4 | 5 | const Splash = ({navigation}) => { 6 | return ( 7 | 8 | navigation.navigate('StartingPage')}> 11 | Get Started 12 | 13 | navigation.navigate('LoginPage')}> 16 | Log In 17 | 18 | navigation.navigate('Map')}> 21 | See Location 22 | 23 | 24 | 25 | ); 26 | }; 27 | 28 | const styles = StyleSheet.create({ 29 | container: { 30 | flex: 1, 31 | backgroundColor: '#fff', 32 | alignItems: 'center', 33 | justifyContent: 'center', 34 | }, 35 | 36 | button: { 37 | borderStyle: 'solid', 38 | padding: 10, 39 | backgroundColor: 'lightblue', 40 | borderRadius: 10, 41 | borderWidth: 5, 42 | borderColor: 'black', 43 | }, 44 | 45 | buttonText: { 46 | fontSize: 30, 47 | }, 48 | }); 49 | 50 | export default Splash; 51 | -------------------------------------------------------------------------------- /components/StartingPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {StyleSheet, Text, View} from 'react-native'; 3 | import {TouchableOpacity} from 'react-native-gesture-handler'; 4 | 5 | const StartingPage = ({navigation}) => { 6 | return ( 7 | 8 | Are you over 65 years old? 9 | navigation.navigate('SeniorSignup')}> 12 | Yes 13 | 14 | navigation.navigate('JuniorSignup')}> 16 | No 17 | 18 | 19 | ); 20 | }; 21 | 22 | const styles = StyleSheet.create({ 23 | container: { 24 | flex: 1, 25 | backgroundColor: '#fff', 26 | alignItems: 'center', 27 | justifyContent: 'center', 28 | }, 29 | 30 | text: { 31 | fontSize: 20, 32 | paddingBottom: 15, 33 | }, 34 | 35 | button: { 36 | borderStyle: 'solid', 37 | padding: 10, 38 | backgroundColor: 'lightblue', 39 | borderRadius: 10, 40 | borderWidth: 5, 41 | borderColor: 'black', 42 | marginBottom: 10, 43 | paddingHorizontal: 20, 44 | }, 45 | 46 | buttonText: { 47 | fontSize: 15, 48 | }, 49 | }); 50 | 51 | export default StartingPage; 52 | -------------------------------------------------------------------------------- /components/Ticket.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import {Text, View, StyleSheet, TouchableOpacity} from 'react-native'; 3 | import {gql, useMutation} from '@apollo/client'; 4 | 5 | const ADD_HELPER_TO_TASK = gql` 6 | mutation addHelperToTask($helperid: Int!, $taskid: Int!) { 7 | addHelperToTask(helperid: $helperid, taskid: $taskid) { 8 | id 9 | } 10 | } 11 | `; 12 | 13 | const Ticket = props => { 14 | const [claimed, setClaimed] = useState(false); 15 | const [addHelperToTask, {data, error}] = useMutation(ADD_HELPER_TO_TASK); 16 | 17 | // destucturing the props 18 | const {task, authID, navigation} = props; 19 | const {seniorname, type, description, id} = task; 20 | // handle claim submit button 21 | const handleClaim = async () => { 22 | // id is taskID and authID is userID 23 | console.log('authID in Ticket', authID); 24 | console.log('taskID in Ticket', id); 25 | 26 | try { 27 | const {data} = await addHelperToTask({ 28 | variables: { 29 | helperid: authID, 30 | taskid: id, 31 | }, 32 | }); 33 | setClaimed(true); 34 | } catch (error) { 35 | console.log('error', error); 36 | } 37 | }; 38 | 39 | return ( 40 | 41 | Who: {seniorname} 42 | Type: {type} 43 | Description: {description} 44 | 45 | 46 | Claim 47 | 48 | navigation.navigate('Map')}> 51 | See Location 52 | 53 | 54 | ); 55 | }; 56 | 57 | const styles = StyleSheet.create({ 58 | container: { 59 | display: 'flex', 60 | flexDirection: 'column', 61 | alignItems: 'flex-start', 62 | justifyContent: 'center', 63 | width: '96%', 64 | backgroundColor: 'lightblue', 65 | margin: 5, 66 | }, 67 | 68 | text: { 69 | fontSize: 25, 70 | }, 71 | 72 | button: { 73 | alignSelf: 'center', 74 | backgroundColor: 'dodgerblue', 75 | borderColor: 'black', 76 | borderWidth: 1, 77 | marginVertical: 10, 78 | paddingVertical: 5, 79 | paddingHorizontal: 20, 80 | }, 81 | 82 | buttonText: { 83 | fontSize: 20, 84 | color: 'white', 85 | }, 86 | }); 87 | 88 | export default Ticket; 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "scripts": { 4 | "start": "expo start", 5 | "android": "expo start --android", 6 | "ios": "expo start --ios", 7 | "web": "expo start --web", 8 | "eject": "expo eject", 9 | "server": "nodemon ./server/index.ts" 10 | }, 11 | "dependencies": { 12 | "@apollo/client": "^3.1.5", 13 | "@react-native-community/masked-view": "0.1.10", 14 | "@react-navigation/bottom-tabs": "^5.8.0", 15 | "@react-navigation/native": "^5.7.3", 16 | "@react-navigation/stack": "^5.9.0", 17 | "bcrypt": "^5.0.0", 18 | "dotenv": "^8.2.0", 19 | "expo": "~38.0.8", 20 | "expo-status-bar": "^1.0.2", 21 | "express": "^4.17.1", 22 | "express-graphql": "^0.11.0", 23 | "graphql": "^15.3.0", 24 | "pg": "^8.3.3", 25 | "react": "~16.11.0", 26 | "react-dom": "~16.11.0", 27 | "react-native": "https://github.com/expo/react-native/archive/sdk-38.0.2.tar.gz", 28 | "react-native-gesture-handler": "~1.6.0", 29 | "react-native-maps": "^0.27.1", 30 | "react-native-reanimated": "~1.9.0", 31 | "react-native-safe-area-context": "~3.0.7", 32 | "react-native-screens": "~2.9.0", 33 | "react-native-web": "~0.11.7" 34 | }, 35 | "devDependencies": { 36 | "@babel/core": "^7.8.6", 37 | "@types/node": "^14.6.4", 38 | "@types/react": "~16.9.41", 39 | "@types/react-native": "~0.62.13", 40 | "nodemon": "^2.0.4", 41 | "ts-node": "^9.0.0", 42 | "typescript": "~3.9.5" 43 | }, 44 | "private": true 45 | } 46 | -------------------------------------------------------------------------------- /server/index.ts: -------------------------------------------------------------------------------- 1 | export {}; //used to bypass "cannot redeclare block-scoped variable" error 2 | const express = require('express'); 3 | const app = express(); 4 | const graphqlHTTP = require('express-graphql').graphqlHTTP; 5 | const {schema} = require('./schema/schema'); 6 | const loginRouter = require('./routes/loginRouter'); 7 | 8 | const PORT = process.env.port || 3000; 9 | 10 | app.use(express.json()); 11 | app.use('/graphql', graphqlHTTP({graphiql: true, schema})); 12 | app.use('/login', loginRouter); 13 | 14 | /* ----------------Error Handling---------------- */ 15 | app.use((err: any, req: any, res: any, next: any) => { 16 | const defaultErr = { 17 | log: 'Express error handler caught unknown middleware error', 18 | status: 400, 19 | message: {err: 'An error occurred'}, 20 | }; 21 | const errorObj = {...defaultErr, err}; 22 | console.log(errorObj.log); 23 | return res.status(errorObj.status).json(errorObj.message); 24 | }); 25 | 26 | app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); 27 | -------------------------------------------------------------------------------- /server/routes/loginRouter.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | const bcrypt = require('bcrypt'); 3 | const express = require('express'); 4 | const router = express.Router(); 5 | const model = require('../schema/model'); 6 | 7 | //Verification Middleware 8 | const verifyHelper = async (req: any, res: any, next: any) => { 9 | const {phone, password} = req.body; 10 | const queryText = 'SELECT * FROM helpers WHERE phone=$1'; 11 | const queryResult = await model.query(queryText, [phone]); 12 | const [parsedResult] = queryResult.rows; 13 | if (parsedResult === undefined) { 14 | res.locals.isVerified = false; 15 | return next(); 16 | } 17 | 18 | bcrypt.compare( 19 | password, 20 | parsedResult.password, 21 | (err: any, isMatch: boolean) => { 22 | if (err) return next(err); 23 | if (isMatch) { 24 | res.locals.isVerified = true; 25 | res.locals.id = parsedResult.id; 26 | } else { 27 | res.locals.isVerified = false; 28 | } 29 | return next(); 30 | }, 31 | ); 32 | }; 33 | 34 | const verifySenior = async (req: any, res: any, next: any) => { 35 | const {phone, password} = req.body; 36 | const queryText = 'SELECT * FROM seniors WHERE phone=$1'; 37 | const queryResult = await model.query(queryText, [phone]); 38 | const [parsedResult] = queryResult.rows; 39 | if (parsedResult === undefined) { 40 | res.locals.isVerified = false; 41 | return next(); 42 | } 43 | 44 | bcrypt.compare( 45 | password, 46 | parsedResult.password, 47 | (err: any, isMatch: boolean) => { 48 | if (err) return next(err); 49 | if (isMatch) { 50 | res.locals.isVerified = true; 51 | res.locals.id = parsedResult.id; 52 | } else { 53 | res.locals.isVerified = false; 54 | } 55 | return next(); 56 | }, 57 | ); 58 | }; 59 | 60 | //Router 61 | router.post('/helper', verifyHelper, (req: any, res: any) => { 62 | res.status(200).json(res.locals); 63 | }); 64 | 65 | router.post('/senior', verifySenior, (req: any, res: any) => { 66 | res.status(200).json(res.locals); 67 | }); 68 | 69 | module.exports = router; 70 | -------------------------------------------------------------------------------- /server/schema/model.ts: -------------------------------------------------------------------------------- 1 | const {Pool} = require('pg'); 2 | require('dotenv').config(); 3 | 4 | const PG_URI = process.env.elephantURI; 5 | 6 | const pool = new Pool({ 7 | connectionString: PG_URI, 8 | ssl: { 9 | rejectUnauthorized: false, 10 | }, 11 | }); 12 | 13 | module.exports = { 14 | query: (text: string, params: any, callback: Function) => { 15 | console.log('executed query', text); 16 | return pool.query(text, params, callback); 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /server/schema/schema.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | const { 3 | GraphQLSchema, 4 | GraphQLObjectType, 5 | GraphQLString, 6 | GraphQLList, 7 | GraphQLInt, 8 | GraphQLNonNull, 9 | } = require('graphql'); 10 | const {TaskType, HelperType, SeniorType} = require('./types'); 11 | const model = require('./model'); 12 | const bcrypt = require('bcrypt'); 13 | 14 | const RootQueryType = new GraphQLObjectType({ 15 | name: 'Query', 16 | description: 'Root Query', 17 | fields: () => ({ 18 | helper: { 19 | type: HelperType, 20 | description: 'Returns designated helper based on ID', 21 | args: { 22 | id: {type: GraphQLInt}, 23 | }, 24 | resolve: (parent: any, {id}: any) => { 25 | const queryText = `SELECT * FROM helpers WHERE id=$1`; 26 | return model 27 | .query(queryText, [id]) 28 | .then((data: any) => data.rows[0]) 29 | .catch((err: any) => console.log(err)); 30 | }, 31 | }, 32 | helpers: { 33 | type: GraphQLList(HelperType), 34 | description: 'List of all helpers', 35 | resolve: () => 36 | model 37 | .query(`SELECT * FROM helpers`) 38 | .then((data: any) => data.rows) 39 | .catch((err: any) => console.log(err)), 40 | }, 41 | senior: { 42 | type: SeniorType, 43 | description: 'Returns senior data based on ID', 44 | args: { 45 | id: {type: GraphQLInt}, 46 | }, 47 | resolve: (parent: any, {id}: any) => 48 | model 49 | .query(`SELECT * FROM seniors WHERE id=$1`, [id]) 50 | .then((data: any) => data.rows[0]) 51 | .catch((err: any) => console.log(err)), 52 | }, 53 | seniors: { 54 | type: GraphQLList(SeniorType), 55 | description: 'List of senior users', 56 | resolve: () => 57 | model 58 | .query(`SELECT * FROM seniors`) 59 | .then((data: any) => data.rows) 60 | .catch((err: any) => console.log(err)), 61 | }, 62 | tasks: { 63 | type: GraphQLList(TaskType), 64 | description: 'List of all tasks', 65 | resolve: () => 66 | model 67 | .query( 68 | `SELECT id, type as typeid, senior as seniorid, description, deadline, completed FROM tasks`, 69 | ) 70 | .then((data: any) => data.rows) 71 | .catch((err: any) => console.log(err)), 72 | }, 73 | }), 74 | }); 75 | 76 | const RootMutationType = new GraphQLObjectType({ 77 | name: 'Mutation', 78 | description: 'Root Mutation', 79 | fields: () => ({ 80 | addHelper: { 81 | type: HelperType, 82 | description: 'Add a helper', 83 | args: { 84 | name: {type: GraphQLNonNull(GraphQLString)}, 85 | phone: {type: GraphQLNonNull(GraphQLString)}, 86 | email: {type: GraphQLNonNull(GraphQLString)}, 87 | password: {type: GraphQLNonNull(GraphQLString)}, 88 | zipcode: {type: GraphQLInt}, 89 | }, 90 | resolve: async (parent: any, args: any) => { 91 | const {name, phone, email, password, zipcode} = args; 92 | const hashedPass = await bcrypt.hash(password, 10); 93 | const queryText = `INSERT INTO helpers (name, phone, email, password, zipcode) 94 | VALUES ($1, $2, $3, $4, $5) 95 | RETURNING *`; 96 | return model 97 | .query(queryText, [name, phone, email, hashedPass, zipcode]) 98 | .then((data: any) => data.rows[0]) 99 | .catch((err: any) => console.log(err)); 100 | }, 101 | }, 102 | addSenior: { 103 | type: SeniorType, 104 | description: 'Add a senior', 105 | args: { 106 | name: {type: GraphQLNonNull(GraphQLString)}, 107 | phone: {type: GraphQLNonNull(GraphQLString)}, 108 | password: {type: GraphQLNonNull(GraphQLString)}, 109 | zipcode: {type: GraphQLNonNull(GraphQLInt)}, 110 | email: {type: GraphQLString}, 111 | }, 112 | resolve: async (parent: any, args: any) => { 113 | const {name, phone, email, password, zipcode} = args; 114 | const hashedPass = await bcrypt.hash(password, 10); 115 | const queryText = `INSERT INTO seniors (name, phone, password, zipcode, email) 116 | VALUES ($1, $2, $3, $4, $5) 117 | RETURNING *`; 118 | return model 119 | .query(queryText, [name, phone, hashedPass, zipcode, email]) 120 | .then((data: any) => data.rows[0]) 121 | .catch((err: any) => console.log(err)); 122 | }, 123 | }, 124 | addTask: { 125 | type: SeniorType, 126 | description: 'Add a task', 127 | args: { 128 | senior: {type: GraphQLNonNull(GraphQLInt)}, 129 | typeid: {type: GraphQLInt}, 130 | description: {type: GraphQLString}, 131 | deadline: {type: GraphQLString}, 132 | }, 133 | resolve: (parent: any, args: any) => { 134 | const {senior, typeid, description, deadline} = args; 135 | const queryText = `INSERT INTO tasks (senior, type, description, deadline) 136 | VALUES ($1, $2, $3, $4) 137 | RETURNING *`; 138 | return model 139 | .query(queryText, [senior, typeid, description, deadline]) 140 | .then((data: any) => data.rows[0]) 141 | .catch((err: any) => console.log(err)); 142 | }, 143 | }, 144 | addHelperToTask: { 145 | type: TaskType, 146 | description: 'Add Helper to a Task', 147 | args: { 148 | helperid: {type: GraphQLNonNull(GraphQLInt)}, 149 | taskid: {type: GraphQLNonNull(GraphQLInt)}, 150 | }, 151 | resolve: (parent: any, args: any) => { 152 | const {helperid, taskid} = args; 153 | const queryText = `INSERT INTO helpertask (helperid, taskid) 154 | VALUES ($1, $2) 155 | RETURNING *`; 156 | return model 157 | .query(queryText, [helperid, taskid]) 158 | .then((data: any) => data.rows[0]) 159 | .catch((err: any) => console.log(err)); 160 | }, 161 | }, 162 | }), 163 | }); 164 | 165 | const schema = new GraphQLSchema({ 166 | query: RootQueryType, 167 | mutation: RootMutationType, 168 | }); 169 | 170 | module.exports = {schema}; 171 | -------------------------------------------------------------------------------- /server/schema/types.ts: -------------------------------------------------------------------------------- 1 | const { 2 | GraphQLSchema, 3 | GraphQLObjectType, 4 | GraphQLString, 5 | GraphQLList, 6 | GraphQLInt, 7 | GraphQLNonNull, 8 | GraphQLBoolean, 9 | } = require('graphql'); 10 | const model = require('./model'); 11 | 12 | const TaskType = new GraphQLObjectType({ 13 | name: 'Task', 14 | description: 'Specifies particular tasks', 15 | fields: { 16 | id: {type: GraphQLNonNull(GraphQLInt)}, 17 | seniorid: {type: GraphQLNonNull(GraphQLInt)}, 18 | seniorname: { 19 | type: GraphQLString, 20 | resolve: (task: any) => { 21 | const queryText = 'SELECT name FROM seniors WHERE id=$1'; 22 | return model 23 | .query(queryText, [task.seniorid]) 24 | .then((data: any) => data.rows[0].name) 25 | .catch((err: any) => console.log(err)); 26 | }, 27 | }, 28 | helpers: { 29 | type: GraphQLList(GraphQLString), 30 | resolve: (task: any) => { 31 | const queryText = `SELECT name 32 | FROM helpertask 33 | JOIN helpers 34 | ON helpertask.helperid = helpers.id 35 | WHERE taskid=$1`; 36 | return model 37 | .query(queryText, [task.id]) 38 | .then((data: any) => data.rows.map(({name}: any) => name)) 39 | .catch((err: any) => console.log(err)); 40 | }, 41 | }, 42 | typeid: {type: GraphQLInt}, 43 | type: { 44 | type: GraphQLString, 45 | resolve: (task: any) => { 46 | const queryText = 'SELECT type FROM tasktypes WHERE id=$1'; 47 | return model 48 | .query(queryText, [task.typeid]) 49 | .then((data: any) => data.rows[0].type) 50 | .catch((err: any) => console.log(err)); 51 | }, 52 | }, 53 | description: {type: GraphQLString}, 54 | deadline: {type: GraphQLString}, 55 | completed: {type: GraphQLBoolean}, 56 | }, 57 | }); 58 | 59 | const HelperType = new GraphQLObjectType({ 60 | name: 'Helpers', 61 | description: 'Specifies particular helpers', 62 | fields: { 63 | id: {type: GraphQLNonNull(GraphQLInt)}, 64 | name: {type: GraphQLNonNull(GraphQLString)}, 65 | phone: {type: GraphQLNonNull(GraphQLString)}, 66 | email: {type: GraphQLNonNull(GraphQLString)}, 67 | password: {type: GraphQLNonNull(GraphQLString)}, 68 | zipcode: {type: GraphQLInt}, 69 | tasks: { 70 | type: GraphQLList(TaskType), 71 | resolve: (helper: any) => { 72 | const queryText = `SELECT helpertask.taskid as id, tasks.type as typeid, tasks.description, seniors.id as seniorid, seniors.name as seniorName, deadline, completed FROM helpertask 73 | JOIN tasks ON helpertask.taskid = tasks.id 74 | JOIN seniors ON seniors.id= tasks.senior 75 | WHERE helperid=$1`; 76 | return model 77 | .query(queryText, [helper.id]) 78 | .then((data: any) => { 79 | return data.rows; 80 | }) 81 | .catch((err: any) => console.log(err)); 82 | }, 83 | }, 84 | }, 85 | }); 86 | 87 | const SeniorType = new GraphQLObjectType({ 88 | name: 'Seniors', 89 | description: 'Specifies particular seniors', 90 | fields: { 91 | id: {type: GraphQLNonNull(GraphQLInt)}, 92 | name: {type: GraphQLNonNull(GraphQLString)}, 93 | phone: {type: GraphQLNonNull(GraphQLString)}, 94 | password: {type: GraphQLNonNull(GraphQLString)}, 95 | email: {type: GraphQLString}, 96 | zipcode: {type: GraphQLInt}, 97 | tasks: { 98 | type: GraphQLList(TaskType), 99 | resolve: (senior: any) => { 100 | const queryText = `SELECT helpertask.taskid as id, tasks.type as typeid, tasks.description, seniors.id as seniorid, seniors.name as seniorName, deadline, completed FROM helpertask 101 | JOIN tasks ON helpertask.taskid = tasks.id 102 | JOIN seniors ON seniors.id = tasks.senior 103 | WHERE seniors.id=$1`; 104 | return model 105 | .query(queryText, [senior.id]) 106 | .then((data: any) => { 107 | return data.rows; 108 | }) 109 | .catch((err: any) => console.log(err)); 110 | }, 111 | }, 112 | }, 113 | }); 114 | 115 | module.exports = {TaskType, HelperType, SeniorType}; 116 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "jsx": "react-native", 5 | "lib": ["ES6"], 6 | "moduleResolution": "node", 7 | "noEmit": true, 8 | "skipLibCheck": true, 9 | "resolveJsonModule": true, 10 | "module": "es2015", 11 | "strict": true 12 | } 13 | } 14 | --------------------------------------------------------------------------------