├── .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 |
--------------------------------------------------------------------------------