├── .gitignore ├── README.md ├── app ├── ActionBar.js ├── EmployeeDetails.js ├── EmployeeDirectoryApp.js ├── EmployeeList.js ├── EmployeeListItem.js ├── SearchBar.js ├── assets │ ├── back.png │ ├── call.png │ ├── email.png │ └── sms.png └── services │ ├── employee-service-mock.js │ └── employee-service-rest.js ├── index.android.js └── index.ios.js /.gitignore: -------------------------------------------------------------------------------- 1 | __tests__ 2 | android 3 | ios 4 | .babelrc 5 | .buckconfig 6 | .flowconfig 7 | .gitattributes 8 | .watchmanconfig 9 | package.json 10 | 11 | # OSX 12 | # 13 | .DS_Store 14 | 15 | # Xcode 16 | # 17 | build/ 18 | *.pbxuser 19 | !default.pbxuser 20 | *.mode1v3 21 | !default.mode1v3 22 | *.mode2v3 23 | !default.mode2v3 24 | *.perspectivev3 25 | !default.perspectivev3 26 | xcuserdata 27 | *.xccheckout 28 | *.moved-aside 29 | DerivedData 30 | *.hmap 31 | *.ipa 32 | *.xcuserstate 33 | project.xcworkspace 34 | 35 | # Android/IntelliJ 36 | # 37 | build/ 38 | .idea 39 | .gradle 40 | local.properties 41 | *.iml 42 | 43 | # node.js 44 | # 45 | node_modules/ 46 | npm-debug.log 47 | 48 | # BUCK 49 | buck-out/ 50 | \.buckd/ 51 | android/app/libs 52 | *.keystore 53 | 54 | # fastlane 55 | # 56 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 57 | # screenshots whenever they are needed. 58 | # For more information about the recommended setup visit: 59 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 60 | 61 | fastlane/report.xml 62 | fastlane/Preview.html 63 | fastlane/screenshots 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Employee Directory: React Native Sample App 2 | 3 | See [this](http://coenraets.org/blog/2017/01/react-native-sample-app-tutorial/) blog post for more info. 4 | 5 | ## Installation Instructions 6 | 7 | 1. Follow the React Native getting started instructions to install React Native 8 | 9 | 1. Create a new React Native app named EmployeeDirectory: 10 | ``` 11 | react-native init EmployeeDirectory 12 | cd EmployeeDirectory 13 | react-native run-ios 14 | ``` 15 | 1. Clone the sample app repository: 16 | ``` 17 | git clone https://github.com/ccoenraets/employee-directory-react-native 18 | ``` 19 | 20 | 1. Copy the `app` folder from `employee-directory-react-native` into your `EmployeeDirectory` project folder 21 | 22 | 1. Open `index.ios.js`. Delete the existing content and replace it with: 23 | ``` 24 | import {AppRegistry} from 'react-native'; 25 | 26 | import EmployeeDirectoryApp from './app/EmployeeDirectoryApp'; 27 | 28 | AppRegistry.registerComponent('EmployeeDirectory', () => EmployeeDirectoryApp); 29 | ``` 30 | 31 | 1. Save index.ios.js 32 | 33 | 1. Hit Cmd + R to refresh the app in the emulator -------------------------------------------------------------------------------- /app/ActionBar.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {View, Text, Image, StyleSheet, TouchableOpacity, Linking} from 'react-native'; 3 | 4 | export default class ActionBar extends Component { 5 | 6 | callNumber() { 7 | this.openURL('tel:' + this.props.mobilePhone); 8 | } 9 | 10 | sendMessage() { 11 | this.openURL('sms:' + this.props.mobilePhone); 12 | } 13 | 14 | sendMail() { 15 | this.openURL('mailto:' + this.props.email); 16 | } 17 | 18 | openURL(url) { 19 | Linking.canOpenURL(url).then(supported => { 20 | if (!supported) { 21 | console.log('Can\'t handle url: ' + url); 22 | } else { 23 | return Linking.openURL(url); 24 | } 25 | }).catch(err => console.error('An error occurred', err)); 26 | } 27 | 28 | render() { 29 | return ( 30 | 31 | 32 | 33 | email 34 | 35 | 36 | 37 | call 38 | 39 | 40 | 41 | message 42 | 43 | 44 | ); 45 | } 46 | } 47 | 48 | const styles = StyleSheet.create({ 49 | container: { 50 | flexDirection: 'row', 51 | justifyContent: 'space-around', 52 | backgroundColor: '#FAFAFF', 53 | paddingVertical: 8 54 | }, 55 | action: { 56 | flex: 1, 57 | alignItems: 'center' 58 | }, 59 | actionText: { 60 | color: '#007AFF' 61 | }, 62 | icon: { 63 | height: 20, 64 | width: 20 65 | } 66 | }); -------------------------------------------------------------------------------- /app/EmployeeDetails.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { View, ListView, Text, Image, StyleSheet, TouchableOpacity } from 'react-native'; 3 | import ActionBar from './ActionBar'; 4 | import EmployeeListItem from './EmployeeListItem'; 5 | import * as employeeService from './services/employee-service-mock'; 6 | 7 | export default class EmployeeDetails extends Component { 8 | 9 | constructor(props) { 10 | super(props); 11 | this.state = {dataSource: new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2})}; 12 | employeeService.findById(this.props.data.id).then(employee => { 13 | this.setState({ 14 | employee: employee, 15 | dataSource: this.state.dataSource.cloneWithRows(employee.reports) 16 | }); 17 | }); 18 | } 19 | 20 | openManager() { 21 | this.props.navigator.push({name: 'details', data: this.state.employee.manager}); 22 | } 23 | 24 | render() { 25 | if (this.state && this.state.employee) { 26 | let employee = this.state.employee; 27 | let manager; 28 | if (employee.manager) { 29 | manager = 30 | 31 | {employee.manager.firstName} {employee.manager.lastName} 32 | {employee.manager.title} 33 | ; 34 | } 35 | let directReports; 36 | if (employee.reports && employee.reports.length > 0) { 37 | directReports = 38 | } 42 | renderSeparator={(sectionId, rowId) => } 43 | />; 44 | } else { 45 | directReports = No direct reports; 46 | } 47 | return ( 48 | 49 | 50 | {manager} 51 | 52 | {employee.firstName} {employee.lastName} 53 | {employee.title} 54 | 55 | 56 | {directReports} 57 | 58 | ); 59 | } else { 60 | return null; 61 | } 62 | } 63 | } 64 | 65 | const styles = StyleSheet.create({ 66 | container: { 67 | marginTop: 60, 68 | backgroundColor: '#FFFFFF', 69 | flex: 1 70 | }, 71 | header: { 72 | alignItems: 'center', 73 | backgroundColor: '#FAFAFF', 74 | paddingBottom: 4, 75 | borderBottomColor: '#F2F2F7', 76 | borderBottomWidth: StyleSheet.hairlineWidth 77 | }, 78 | manager: { 79 | paddingBottom: 10, 80 | alignItems: 'center' 81 | }, 82 | picture: { 83 | width: 80, 84 | height: 80, 85 | borderRadius: 40 86 | }, 87 | smallPicture: { 88 | width: 40, 89 | height: 40, 90 | borderRadius: 20 91 | }, 92 | mediumText: { 93 | fontSize: 16, 94 | }, 95 | bigText: { 96 | fontSize: 20 97 | }, 98 | separator: { 99 | height: StyleSheet.hairlineWidth, 100 | backgroundColor: '#AAAAAA', 101 | }, 102 | list: { 103 | flex: 1, 104 | }, 105 | emptyList: { 106 | flex: 1, 107 | justifyContent: 'center', 108 | alignItems: 'center' 109 | }, 110 | lightText: { 111 | color: '#C7C7CC' 112 | } 113 | }); -------------------------------------------------------------------------------- /app/EmployeeDirectoryApp.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Navigator, Text, TouchableOpacity, Image, StyleSheet} from 'react-native'; 3 | import EmployeeList from './EmployeeList'; 4 | import EmployeeDetails from './EmployeeDetails'; 5 | 6 | export default class EmployeeDirectoryApp extends Component { 7 | 8 | renderScene(route, navigator) { 9 | switch (route.name) { 10 | case 'employee-list': 11 | return 12 | case 'details': 13 | return 14 | } 15 | } 16 | 17 | render() { 18 | return ( 19 | { 26 | if (route.name === 'employee-list') { 27 | return null; 28 | } else { 29 | return ( 30 | navigator.pop()}> 31 | 32 | 33 | ); 34 | } 35 | }, 36 | RightButton: (route, navigator, index, navState) => { 37 | return null; 38 | }, 39 | Title: (route, navigator, index, navState) => { 40 | return ({route.title}); 41 | }, 42 | }} 43 | style={styles.navBar} 44 | /> 45 | } 46 | /> 47 | ) 48 | } 49 | } 50 | 51 | const styles = StyleSheet.create({ 52 | navBar: { 53 | backgroundColor: '#FAFAFF', 54 | height: 60, 55 | }, 56 | backButton: { 57 | marginTop: 8, 58 | marginLeft: 12, 59 | height: 24, 60 | width: 24 61 | }, 62 | title: { 63 | padding: 8, 64 | fontSize: 16, 65 | fontWeight: 'bold' 66 | } 67 | }); -------------------------------------------------------------------------------- /app/EmployeeList.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {View, ListView, StyleSheet} from 'react-native'; 3 | import SearchBar from './SearchBar'; 4 | import EmployeeListItem from './EmployeeListItem'; 5 | import * as employeeService from './services/employee-service-mock'; 6 | 7 | export default class EmployeeList extends Component { 8 | 9 | constructor(props) { 10 | super(props); 11 | this.state = {dataSource: new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2})}; 12 | employeeService.findAll().then(employees => { 13 | this.setState({ 14 | dataSource: this.state.dataSource.cloneWithRows(employees) 15 | }); 16 | }); 17 | } 18 | 19 | search(key) { 20 | employeeService.findByName(key).then(employees => { 21 | this.setState({ 22 | dataSource: this.state.dataSource.cloneWithRows(employees) 23 | }); 24 | }); 25 | } 26 | 27 | render() { 28 | return ( 29 | } 33 | renderSeparator={(sectionId, rowId) => } 34 | renderHeader={() => } 35 | /> 36 | ); 37 | } 38 | } 39 | 40 | const styles = StyleSheet.create({ 41 | container: { 42 | backgroundColor: '#FFFFFF', 43 | marginTop: 60 44 | }, 45 | separator: { 46 | height: StyleSheet.hairlineWidth, 47 | backgroundColor: '#AAAAAA', 48 | } 49 | }); -------------------------------------------------------------------------------- /app/EmployeeListItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { View, Text, Image, TouchableHighlight, StyleSheet } from 'react-native'; 3 | 4 | export default class EmployeeListItem extends Component { 5 | 6 | showDetails() { 7 | this.props.navigator.push({name: 'details', data: this.props.data}); 8 | } 9 | 10 | render() { 11 | return ( 12 | 13 | 14 | 15 | 16 | {this.props.data.firstName} {this.props.data.lastName} 17 | {this.props.data.title} 18 | 19 | 20 | 21 | ) 22 | } 23 | } 24 | 25 | const styles = StyleSheet.create({ 26 | container: { 27 | flex: 1, 28 | flexDirection: 'row', 29 | padding: 8 30 | }, 31 | picture: { 32 | width: 40, 33 | height: 40, 34 | borderRadius: 20, 35 | marginRight: 8 36 | }, 37 | title: { 38 | color: '#848484' 39 | } 40 | }); 41 | -------------------------------------------------------------------------------- /app/SearchBar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { View, TextInput, StyleSheet } from 'react-native'; 3 | 4 | export default class SearchBar extends Component { 5 | 6 | render() { 7 | return ( 8 | 9 | 14 | 15 | ) 16 | } 17 | } 18 | 19 | const styles = StyleSheet.create({ 20 | container: { 21 | flex: 1, 22 | padding: 8, 23 | flexDirection: 'row', 24 | alignItems: 'center', 25 | backgroundColor: '#C9C9CE', 26 | }, 27 | input: { 28 | height: 30, 29 | flex: 1, 30 | paddingHorizontal: 8, 31 | backgroundColor: '#FFFFFF', 32 | borderRadius: 4, 33 | }, 34 | }); 35 | 36 | -------------------------------------------------------------------------------- /app/assets/back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccoenraets/employee-directory-react-native/f164d2a629222daf619f8fe0f66b3b90e15a8a2f/app/assets/back.png -------------------------------------------------------------------------------- /app/assets/call.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccoenraets/employee-directory-react-native/f164d2a629222daf619f8fe0f66b3b90e15a8a2f/app/assets/call.png -------------------------------------------------------------------------------- /app/assets/email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccoenraets/employee-directory-react-native/f164d2a629222daf619f8fe0f66b3b90e15a8a2f/app/assets/email.png -------------------------------------------------------------------------------- /app/assets/sms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ccoenraets/employee-directory-react-native/f164d2a629222daf619f8fe0f66b3b90e15a8a2f/app/assets/sms.png -------------------------------------------------------------------------------- /app/services/employee-service-mock.js: -------------------------------------------------------------------------------- 1 | let employees = [ 2 | { 3 | id: 1, 4 | firstName: "Amy", 5 | lastName: "Taylor", 6 | title: "CEO", 7 | phone: "617-123-4567", 8 | mobilePhone: "617-987-6543", 9 | email: "amy@fakemail.com", 10 | picture: "https://s3-us-west-1.amazonaws.com/sfdc-demo/people/amy_taylor.jpg" 11 | }, 12 | { 13 | id: 2, 14 | firstName: "Anup", 15 | lastName: "Gupta", 16 | title: "VP of Engineering", 17 | managerId: 1, 18 | managerName: "Amy Taylor", 19 | phone: "617-123-4567", 20 | mobilePhone: "617-987-6543", 21 | email: "anup@fakemail.com", 22 | picture: "https://s3-us-west-1.amazonaws.com/sfdc-demo/people/anup_gupta.jpg" 23 | }, 24 | { 25 | id: 3, 26 | firstName: "Michael", 27 | lastName: "Jones", 28 | title: "VP of Marketing", 29 | managerId: 1, 30 | managerName: "Amy Taylor", 31 | phone: "617-123-4567", 32 | mobilePhone: "617-987-6543", 33 | email: "michael@fakemail.com", 34 | picture: "https://s3-us-west-1.amazonaws.com/sfdc-demo/people/michael_jones.jpg" 35 | }, 36 | { 37 | id: 4, 38 | firstName: "Caroline", 39 | lastName: "Kingsley", 40 | title: "VP of Sales", 41 | managerId: 1, 42 | managerName: "Amy Taylor", 43 | phone: "617-123-4567", 44 | mobilePhone: "617-987-6543", 45 | email: "caroline@fakemail.com", 46 | picture: "https://s3-us-west-1.amazonaws.com/sfdc-demo/people/caroline_kingsley.jpg" 47 | }, 48 | { 49 | id: 5, 50 | firstName: "James", 51 | lastName: "Kennedy", 52 | title: "Account Executive", 53 | managerId: 4, 54 | managerName: "Caroline Kingsley", 55 | phone: "617-123-4567", 56 | mobilePhone: "617-987-6543", 57 | email: "james@fakemail.com", 58 | picture: "https://s3-us-west-1.amazonaws.com/sfdc-demo/people/james_kennedy.jpg" 59 | }, 60 | { 61 | id: 6, 62 | firstName: "Jennifer", 63 | lastName: "Wu", 64 | title: "Account Executive", 65 | managerId: 4, 66 | managerName: "Caroline Kingsley", 67 | phone: "617-123-4567", 68 | mobilePhone: "617-987-6543", 69 | email: "jen@fakemail.com", 70 | picture: "https://s3-us-west-1.amazonaws.com/sfdc-demo/people/jennifer_wu.jpg" 71 | }, 72 | { 73 | id: 7, 74 | firstName: "Jonathan", 75 | lastName: "Bradley", 76 | title: "Account Executive", 77 | managerId: 4, 78 | managerName: "Caroline Kingsley", 79 | phone: "617-123-4567", 80 | mobilePhone: "617-987-6543", 81 | email: "jonathan@fakemail.com", 82 | picture: "https://s3-us-west-1.amazonaws.com/sfdc-demo/people/jonathan_bradley.jpg" 83 | }, 84 | { 85 | id: 8, 86 | firstName: "Kenneth", 87 | lastName: "Sato", 88 | title: "Account Executive", 89 | managerId: 4, 90 | managerName: "Caroline Kingsley", 91 | phone: "617-123-4567", 92 | mobilePhone: "617-987-6543", 93 | email: "kenneth@fakemail.com", 94 | picture: "https://s3-us-west-1.amazonaws.com/sfdc-demo/people/kenneth_sato.jpg" 95 | }, 96 | { 97 | id: 9, 98 | firstName: "Lisa", 99 | lastName: "Parker", 100 | title: "Software Architect", 101 | managerId: 2, 102 | managerName: "Anup Gupta", 103 | phone: "617-123-4567", 104 | mobilePhone: "617-987-6543", 105 | email: "lisa@fakemail.com", 106 | picture: "https://s3-us-west-1.amazonaws.com/sfdc-demo/people/lisa_parker.jpg" 107 | }, 108 | { 109 | id: 10, 110 | firstName: "Brad", 111 | lastName: "Moretti", 112 | title: "Software Architect", 113 | managerId: 2, 114 | managerName: "Anup Gupta", 115 | phone: "617-123-4567", 116 | mobilePhone: "617-987-6543", 117 | email: "brad@fakemail.com", 118 | picture: "https://s3-us-west-1.amazonaws.com/sfdc-demo/people/brad_moretti.jpg" 119 | }, 120 | { 121 | id: 11, 122 | firstName: "Michelle", 123 | lastName: "Lambert", 124 | title: "Software Architect", 125 | managerId: 2, 126 | managerName: "Anup Gupta", 127 | phone: "617-123-4567", 128 | mobilePhone: "617-987-6543", 129 | email: "michelle@fakemail.com", 130 | picture: "https://s3-us-west-1.amazonaws.com/sfdc-demo/people/michelle_lambert.jpg" 131 | }, 132 | { 133 | id: 12, 134 | firstName: "Miriam", 135 | lastName: "Aupont", 136 | title: "Marketing Manager", 137 | managerId: 3, 138 | managerName: "Michael Jones", 139 | phone: "617-123-4567", 140 | mobilePhone: "617-987-6543", 141 | email: "miriam@fakemail.com", 142 | picture: "https://s3-us-west-1.amazonaws.com/sfdc-demo/people/miriam_aupont.jpg" 143 | }, 144 | { 145 | id: 13, 146 | firstName: "Olivia", 147 | lastName: "Green", 148 | title: "Marketing Manager", 149 | managerId: 3, 150 | managerName: "Michael Jones", 151 | phone: "617-123-4567", 152 | mobilePhone: "617-987-6543", 153 | email: "olivia@fakemail.com", 154 | picture: "https://s3-us-west-1.amazonaws.com/sfdc-demo/people/olivia_green.jpg" 155 | }, 156 | { 157 | id: 14, 158 | firstName: "Robert", 159 | lastName: "Sullivan", 160 | title: "Marketing Manager", 161 | managerId: 3, 162 | managerName: "Michael Jones", 163 | phone: "617-123-4567", 164 | mobilePhone: "617-987-6543", 165 | email: "robert@fakemail.com", 166 | picture: "https://s3-us-west-1.amazonaws.com/sfdc-demo/people/robert_sullivan.jpg" 167 | }, 168 | { 169 | id: 15, 170 | firstName: "Tammy", 171 | lastName: "Robinson", 172 | title: "Software Architect", 173 | managerId: 2, 174 | managerName: "Anup Gupta", 175 | phone: "617-123-4567", 176 | mobilePhone: "617-987-6543", 177 | email: "tammy@fakemail.com", 178 | picture: "https://s3-us-west-1.amazonaws.com/sfdc-demo/people/tammy_robinson.jpg" 179 | }, 180 | { 181 | id: 16, 182 | firstName: "Victor", 183 | lastName: "Ochoa", 184 | title: "Account Executive", 185 | managerId: 4, 186 | managerName: "Caroline Kingsley", 187 | phone: "617-123-4567", 188 | mobilePhone: "617-987-6543", 189 | email: "victor@fakemail.com", 190 | picture: "https://s3-us-west-1.amazonaws.com/sfdc-demo/people/victor_ochoa.jpg" 191 | } 192 | ]; 193 | 194 | // Simulating async calls for plug-and-play replacement with REST services 195 | export let findAll = () => new Promise((resolve, reject) => { 196 | resolve(employees); 197 | }); 198 | 199 | export let findByName = (name) => new Promise((resolve, reject) => { 200 | let filtered = employees.filter(employee => (employee.firstName + ' ' + employee.lastName).toLowerCase().indexOf(name.toLowerCase()) > -1); 201 | resolve(filtered); 202 | }); 203 | 204 | export let findById = (id) => new Promise((resolve, reject) => { 205 | let employee = employees[id-1]; 206 | resolve({ 207 | firstName: employee.firstName, 208 | lastName: employee.lastName, 209 | title: employee.title, 210 | email: employee.email, 211 | mobilePhone: employee.mobilePhone, 212 | picture: employee.picture, 213 | manager: employees[employee.managerId - 1], 214 | reports: employees.filter(item => item.managerId === id) 215 | }); 216 | }); -------------------------------------------------------------------------------- /app/services/employee-service-rest.js: -------------------------------------------------------------------------------- 1 | const baseURL = 'https://employee-directory-services.herokuapp.com/employees'; 2 | 3 | export let findAll = () => fetch(baseURL) 4 | .then((response) => response.json()); 5 | 6 | export let findByName = (name) => fetch(`${baseURL}?name=${name}`) 7 | .then((response) => response.json()); 8 | 9 | export let findById = (id) => fetch(`${baseURL}/${id}`) 10 | .then((response) => response.json()); -------------------------------------------------------------------------------- /index.android.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | * @flow 5 | */ 6 | 7 | import React, { Component } from 'react'; 8 | import { 9 | AppRegistry, 10 | StyleSheet, 11 | Text, 12 | View 13 | } from 'react-native'; 14 | 15 | export default class EmployeeDirectory extends Component { 16 | render() { 17 | return ( 18 | 19 | 20 | Welcome to React Native! 21 | 22 | 23 | To get started, edit index.android.js 24 | 25 | 26 | Double tap R on your keyboard to reload,{'\n'} 27 | Shake or press menu button for dev menu 28 | 29 | 30 | ); 31 | } 32 | } 33 | 34 | const styles = StyleSheet.create({ 35 | container: { 36 | flex: 1, 37 | justifyContent: 'center', 38 | alignItems: 'center', 39 | backgroundColor: '#F5FCFF', 40 | }, 41 | welcome: { 42 | fontSize: 20, 43 | textAlign: 'center', 44 | margin: 10, 45 | }, 46 | instructions: { 47 | textAlign: 'center', 48 | color: '#333333', 49 | marginBottom: 5, 50 | }, 51 | }); 52 | 53 | AppRegistry.registerComponent('EmployeeDirectory', () => EmployeeDirectory); 54 | -------------------------------------------------------------------------------- /index.ios.js: -------------------------------------------------------------------------------- 1 | import {AppRegistry} from 'react-native'; 2 | 3 | import EmployeeDirectoryApp from './app/EmployeeDirectoryApp'; 4 | 5 | AppRegistry.registerComponent('EmployeeDirectory', () => EmployeeDirectoryApp); --------------------------------------------------------------------------------