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