DEV Community

kris
kris

Posted on • Originally published at Medium on

Building Appointment Scheduler App in React and NodeJS

We will be creating an appointment scheduler that enables users to schedule appointments by selecting date and time and providing their user information(name, email, phone numbers). A confirmation is sent to the user’s phone via text to confirm the appointment.

Upgrade your JavaScript skills.

Learn React.js in just a couple of afternoons.

A premium step-by-step training course by Wes Bos to get you building real-world React.js + Firebase apps and website components. START LEARNING NOW

A premium training course to strengthen your core JavaScript skills

and master all that ES6 has to offer. Join 22,120 in Learning Now →

See preview below:

React-App

We are going to concentrate on the following:

Backend (Part 1)

  1. Creating a template Express Web app
  2. MongoDB database configuration
  3. Creating a Nexmo account
  4. Creating routes, model, and controllers for Express web app
  5. Testing Express Web App with Postman https://medium.com/media/18214d8333adbb5668f31791a3202b60/href

Frontend (Part 2)

  1. Creating a template React App
  2. Creating Views for React
  3. App Functionality in Depth.

See here for source code

Backend with Nodejs

For the Backend we are going to use the following Frameworks:

Express is a fast, flexible modular Node.js web application framework that provides a robust set of features for web and mobile applications.

Nexmo helps growing startups and agile enterprises enhance their customer experience — and realize new business outcomes at scale. Nexmo has an SMS service which we will use to send messages to the users.

MongoDB is a free document-oriented database program. it’s regarded as a NoSQL database program, MongoDB uses JSON-like documents with schemas.

Mongoose models MongoDB objects for node.js, for this demo it will enable us to model MongoDB objects for our express app.

NPM and Node Install

The installation of Node and npm is required before anything reasonable can be achieved. Installing Node automatically installs npm as well. Node can be installed from the installation instructions (basically download and install) from the website. Skip this step if you already have the installation both packages.)

After download, check if node and npm were installed via command line(CLI):

node -v

npm -v

Creating a template Express Web App

Create a folder named “appointment-scheduler-web-react-app”

From your Command Line access your “ appointment-scheduler-web-react-app” folder, proceed to the api folder like this:

cd appointment-scheduler-web-react-app

install the express CLI (Command Line Interface) tools globally:

npm install express-generator -g

To create a new express project:

express --view=ejs api

The generator creates a new express app called api. The — view flag tells the generator to use ejs template engine for the view files.

Access your folder:

cd api

Folder structure should look like this:

+---bin

+---public

+---routes

+---views

+---app.js

+---package.json

Next, install the following dependencies in your new api folder via Command line(CLI):

npm install

npm install mongoose

Mongoose will be used to create MongoDB objects and connect them to the MongoDB database.

Your app.js file should look like this:

apps

Creating a Nexmo Account

We are going to use Nexmo to send a text message to user’s mobile phone number after scheduling the appointment.

We are going to create a Nexmo account, signup here

if you get error on nexmo I’ve recive comment from Nexmo crew

To install Nexmo via CLI:

npm install nexmo

After account setup make sure you get the following from the console dashboard

1.API Key

  1. API Secret

Copy them to somewhere safe, we will need it later to create the nexmo object in file, controllers/appointments.js.

Creating a MongoDB Database

MongoDB is an open-source cross-platform document-oriented database program. Classified as a NoSQL database program, MongoDB uses JSON-like documents with schemas.

MongoDB instance can be downloaded and installed in your machine, see link for more, but for this tutorial, we are interested in the cloud option where we can connect our express app to the database via the Mongoose connection string.

Now let’s create a MongoDB database via MongoLab, Mongolab is free tier and easy to set up.

To start:

Create a MongoLab account

Create a new database as shown in the image below:

sandox-option

Click on the SandBox option above to create a free tier

Next, for the AWS Region below choose US East (Virginia) (us-east -1) option:

sandbox2

Below, type “appointments” as database name

sandbox3

Click continue and you are done. Congrats you have completed the database creation process. below is a preview of your database.You should see your database listed on the MongoDB deployments:

sandbox6

Creating routes, models, and controllers for the Express Web App

We are now going to build express app features. Our express app is will enable the appointment scheduler app to interact with the MongoDB we created earlier.

The express app requires the following:

Models : To access the MongoDB database using mongoose, we need to define schemas and models. schemas and models convey to mongoose a simplified representation of the data structure comprising of fields and data types.

Routes : Express makes use of routes to handle request and responses from the client app (appointment scheduler in this case) and MongoDB.

Controllers : Controllers are actually callback functions. Moving forward appointmentController.all, slotController.all and appointmentController.create are callback functions which we are referring to as controllers.

First, we have to edit the app.js file to add our mongoose connection:

var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
const mongoose = require('mongoose');

var index = require('./routes/index');
const api = require('./routes/api/index');

var app = express();

mongoose.Promise = global.Promise;

//Adds connection to database using mongoose
//for \<dbuser\>:replace with your username, \<dbpassword\>: replace with your password.
//\<DATABASE\_URL\>: replace with database url, example:ds234562.mlab.com:17283
mongoose.connect('\<dbuser\>:\<dbpassword\>@\<DATABASE\_URL\>/appointments', {
 useMongoClient: true
});

//This enabled CORS, Cross-origin resource sharing (CORS) is a mechanism that allows restricted resources (e.g. fonts) 
//on a web page to be requested from another domain outside the domain from which the first resource was served

app.all('/\*', function(req, res, next) {
 // CORS headers
 res.header("Access-Control-Allow-Origin", "\*"); // restrict it to the required domain
 res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
 // Set custom headers for CORS
 res.header('Access-Control-Allow-Headers', 'Content-type,Accept,X-Access-Token,X-Key');
 if (req.method == 'OPTIONS') {
 res.status(200).end();
 } else {
 next();
 }
});
// view engine setup
app.set('views', path.join(\_\_dirname, 'views'));
app.set('view engine', 'ejs');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(\_\_dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(\_\_dirname, 'public')));

app.use('/', index);
app.use('/api', api);
//app.use('/users', users);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
 var err = new Error('Not Found');
 err.status = 404;
 next(err);
});

// error handler
app.use(function(err, req, res, next) {
 // set locals, only providing error in development
 res.locals.message = err.message;
 res.locals.error = req.app.get('env') === 'development' ? err : {};

// render the error page
 res.status(err.status || 500);
 res.render('error');
});

module.exports = app;

Create a models/index.js file in your base api folder:

const Schema = mongoose.Schema,
 model = mongoose.model.bind(mongoose),
 ObjectId = mongoose.Schema.Types.ObjectId;

const slotSchema = new Schema ({
 slot\_time: String,
 slot\_date: String,
 created\_at: Date
 });

const Slot = model('Slot', slotSchema);

const appointmentSchema = new Schema({
 id: ObjectId,
 name: String,
 email: String,
 phone: Number,
 slots:{type: ObjectId, ref: 'Slot'},
 created\_at: Date
});

const Appointment = model('Appointment', appointmentSchema);

Controllers for Express App

Create a controllers/appointments.js file in your base api folder

const { Appointment, Slot } = Model;
const Nexmo = require("nexmo");

const appointmentController = {
 all(req, res) {
 // Returns all appointments
 Appointment.find({}).exec((err, appointments) =\> res.json(appointments));
 },
 create(req, res) {
 var requestBody = req.body;`

var newslot = new Slot({
 slot\_time: requestBody.slot\_time,
 slot\_date: requestBody.slot\_date,
 created\_at: Date.now()
 });
 newslot.save();
 // Creates a new record from a submitted form
 var newappointment = new Appointment({
 name: requestBody.name,
 email: requestBody.email,
 phone: requestBody.phone,
 slots: newslot.\_id
 });

const nexmo = new Nexmo({
 apiKey: "YOUR\_API\_KEY",
 apiSecret: "YOUR\_API\_SECRET"
 });

let msg =
 requestBody.name +
 " " +
 "this message is to confirm your appointment at" +
 " " +
 requestBody.appointment;

// and saves the record to
 // the data base
 newappointment.save((err, saved) =\> {
 // Returns the saved appointment
 // after a successful save
 Appointment.find({ \_id: saved.\_id })
 .populate("slots")
 .exec((err, appointment) =\> res.json(appointment));

const from = VIRTUAL\_NUMBER;
 const to = RECIPIENT\_NUMBER;

nexmo.message.sendSms(from, to, msg, (err, responseData) =\> {
 if (err) {
 console.log(err);
 } else {
 console.dir(responseData);
 }
 });
 });
 }
};

module.exports = appointmentController;

Create a controllers/slot.js file in your base api folder

const {Appointment, Slot} = Model;

const slotController = {
 all (req, res) {
 // Returns all Slots
 Slot.find({})
 .exec((err, slots) =\> res.json(slots))
 }
};

module.exports = slotController;

Routing for Express App

Edit the routes/index.js file to look like this:

var router = express.Router();

/\* GET home page. \*/
router.get('/', function(req, res, next) {
 res.render('index', { title: 'Express App running' });
});

module.exports = router;

The express router is responsible for navigation within the app, from above it gets and displays the express app default page located in path views/index .ejs.

Let’s continue by creating our own route. Create a routes/api/index.js

const router = express.Router();

const appointmentController = require('../../controllers/appointments')
const slotController = require('../../controllers/slot')

router.get('/appointments', appointmentController.all);
router.get('/retrieveSlots', slotController.all);
router.post('/appointmentCreate', appointmentController.create);

module.exports = router;

Both the express and react apps use the port 3000 in development mode. so we are a going to change the localhost port value to 8083

Go to bin/www.js in your express app

change port from 3000 to 8083

Stop express app with Ctrl C(for windows), Ctrl Z(for mac), rerun with:

npm start

Go to localhost:8083 using your browser:

express2

Now we are done with our express app creation. Let’s see if we can post and get values using our express app.

Upgrade your JavaScript skills.

Learn React.js in just a couple of afternoons.

A premium step-by-step training course by Wes Bos to get you building real-world React.js + Firebase apps and website components. START LEARNING NOW

Testing with Postman

Since we don’t have a form to send values, Postman is a utility tool for testing APIs is ideal to run tests, with Postman we can post and get values to and from the database using the routes we created earlier, let’s proceed:

You need to download and install Postman

Let’s start testing locally, with posting appointments:

postman5

Followed by, retrieving slots:

postman6

Front end with React App

Now access your command line Interface (CLI) and use the following command to create react app globally using npm:

For npm version 5.1 or earlier

npm install -g create-react-app

To create our appointment scheduler app, run a single command

npm install create-react-app appointment-scheduler

For npm version 5.2+ and higher

npx install -g create-react-app

To create our appointment scheduler app, run a single command

npx install create-react-app appointment-scheduler

A directory called appointment-scheduler is created at the current directory. Inside the folder you should see the following transitive dependencies

appointment-scheduler

├── README.md

├── node_modules

├── package.json

├── .gitignore

├── public

│ └── favicon.ico

│ └── index.html

│ └── manifest.json

└── src

└── App.css

└── App.js

└── App.test.js

└── index.css

└── index.js

└── logo.svg

└── registerServiceWorker.js

To start the project in development mode via CLI

npm start

Access your app directory using:

cd appointment-scheduler

Install the following dependencies:

npm install material-ui

npm install axios

Creating Views for Appointment Scheduler App

We will start by creating a components folder:

Create a folder with the name “components” in your “src” folder directory of your appointment-scheduler app. see below:

appointment-scheduler/src/components

Create AppointmentApp.js in the components folder with the following code:

import React, {
 Component
} from "react";
import logo from "./logo.svg";
class AppointmentApp extends Component {
 render() {
 return ( 
 \<div\>
 \< p\> ApointmentApp \</p\> 
 \</div\>
 );
 }
}
export default AppointmentApp;

update your src/App.js

import logo from "./logo.svg";
import AppointmentApp from "./components/AppointmentApp.js";
import MuiThemeProvider from "material-ui/styles/MuiThemeProvider";
import "./App.css";
class App extends Component {
 render() {
 return (
 \<div\>
 \<MuiThemeProvider\>
 \<AppointmentApp /\>
 \</MuiThemeProvider\>
 \</div\>
 );
 }
}
export default App;

Notice that we imported the AppointmentApp component and included a MuiThemeProvider component. The appointmentApp component is enclosed inside the MuiThemeProvider to inject material-ui theme into your application context. Following that, you can use any of the components in the material-ui documentation.

App Functionality In Depth

App state Description

The app loads data from an database via the express app

  • Our appointment scheduling process we’ll take place in three steps: selecting a date, selecting a time slot, and filling out personal information. We need to track which step the user is on, the date and time they’ve selected, their contact details, and we also need to validate their email address and phone number.
  • We’ll be loading scheduled appointments slots that we’d benefit from when the user is selecting available slot times. A slot time that has already been selected by a previous user cannot be selected again.
  • As our user proceeds through the scheduling steps, after selecting the date in step 1 the application will proceed to step 2 where the user will select either AM or PM which will display the available appointment times (slot times), when that is done the demo will proceed to step 3 where the user will enter their details and schedule the appointment.
  • A modal will display the appointment date and time for user confirmation.
  • Finally, a snackbar will display a message indicating appointment success or failure.

Method Description of AppointmentApp Component

Because the AppointmentApp component is over four hundred lines of code. we are going to divide and explain each chunk of code as we progress. See here for a complete version of source code.

Various components are imported from respective frameworks/libraries we installed. the state of various fields such as firstName, lastName are set using this.state.

For the componentWillMount lifecycle method below, previous scheduled appointments slots are retrieved from the database via the express app.

import React, { Component } from "react";
import AppBar from "material-ui/AppBar";
import RaisedButton from "material-ui/RaisedButton";
import FlatButton from "material-ui/FlatButton";
import moment from "moment";
import DatePicker from "material-ui/DatePicker";
import Dialog from "material-ui/Dialog";
import SelectField from "material-ui/SelectField";
import MenuItem from "material-ui/MenuItem";
import TextField from "material-ui/TextField";
import SnackBar from "material-ui/Snackbar";
import Card from "material-ui/Card";
import {
 Step,
 Stepper,
 StepLabel,
 StepContent
} from "material-ui/Stepper";
import { RadioButton, RadioButtonGroup } from "material-ui/RadioButton";
import axios from "axios";

const API\_BASE = "[http://localhost:8083/](http://localhost:8083/)";

class AppointmentApp extends Component {
 constructor(props, context) {
 super(props, context);

this.state = {
 firstName: "",
 lastName: "",
 email: "",
 schedule: [],
 confirmationModalOpen: false,
 appointmentDateSelected: false,
 appointmentMeridiem: 0,
 validEmail: true,
 validPhone: true,
 finished: false,
 smallScreen: window.innerWidth \< 768,
 stepIndex: 0
 };
 }
 componentWillMount() {
 axios.get(API\_BASE + `api/retrieveSlots`).then(response =\> {
 console.log("response via db: ", response.data);
 this.handleDBReponse(response.data);
 });
 }

The handleNext method moves the stepper to the next position using the stepIndex field.

The handlePrev method moves the stepper to the previous position using the stepIndex field.

handleNext = () =\> {
 const { stepIndex } = this.state;
 this.setState({
 stepIndex: stepIndex + 1,
 finished: stepIndex \>= 2
 });
 };

handlePrev = () =\> {
 const { stepIndex } = this.state;
 if (stepIndex \> 0) {
 this.setState({ stepIndex: stepIndex - 1 });
 }
 };

The validateEmail method checks the email parameter and returns true if the email value is valid or false if it is not.

The validatePhone method checks the phone parameter and returns true if phone value is valid or false if it is not.

validateEmail(email) {
 const regex = /^(([^\<\>()\[\]\.,;:\s@\"]+(\.[^\<\>()\[\]\.,;:\s@\"]+)\*)|(\".+\"))@(([^\<\>()[\]\.,;:\s@\"]+\.)+[^\<\>()[\]\.,;:\s@\"]{2,})$
 /i;
 return regex.test(email)
 ? this.setState({ email: email, validEmail: true })
 : this.setState({ validEmail: false });
 }
 validatePhone(phoneNumber) {
 const regex = /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/;
 return regex.test(phoneNumber)
 ? this.setState({ phone: phoneNumber, validPhone: true })
 : this.setState({ validPhone: false });
 }

The renderStepActions method renders the Next, finish and Back Buttons.

renderStepActions(step) {
 const { stepIndex } = this.state;

return (
 \<div style={{ margin: "12px 0" }}\>
 \<RaisedButton
 label={stepIndex === 2 ? "Finish" : "Next"}
 disableTouchRipple={true}
 disableFocusRipple={true}
 primary={true}
 onClick={this.handleNext}
 backgroundColor="#00C853 !important"
 style={{ marginRight: 12, backgroundColor: "#00C853" }}
 /\>
 {step \> 0 && (
 \<FlatButton
 label="Back"
 disabled={stepIndex === 0}
 disableTouchRipple={true}
 disableFocusRipple={true}
 onClick={this.handlePrev}
 /\>
 )}
 \</div\>
 );
}

The handleSetAppointmentDate method is used to set the state of the appointmentDate field.

The handleSetAppointmentSlot method is used to set the state of the appointmentSlot field.

The handleSetAppointmentMeridiem method is used to set the state of the appointmentMeridiem field.

handleSetAppointmentDate(date) {
 this.setState({ appointmentDate: date, confirmationTextVisible: true });
 }
handleSetAppointmentSlot(slot) {
 this.setState({ appointmentSlot: slot });
 }
handleSetAppointmentMeridiem(meridiem) {
 this.setState({ appointmentMeridiem: meridiem });
 }

The checkDisableDate method passes disabled dates to the date picker component.

The handleDBResponse method handles the appointment slot data from the database.

checkDisableDate(day) {
 const dateString = moment(day).format("YYYY-DD-MM");
 return (
 this.state.schedule[dateString] === true ||
 moment(day)
 .startOf("day")
 .diff(moment().startOf("day")) \< 0
 );
 }

handleDBReponse(response) {
 const appointments = response;
 const today = moment().startOf("day"); //start of today 12 am
 const initialSchedule = {};
 initialSchedule[today.format("YYYY-DD-MM")] = true;
 const schedule = !appointments.length
 ? initialSchedule
 : appointments.reduce((currentSchedule, appointment) =\> {
 const { slot\_date, slot\_time } = appointment;
 const dateString = moment(slot\_date, "YYYY-DD-MM").format(
 "YYYY-DD-MM"
 );
 !currentSchedule[slot\_date]
 ? (currentSchedule[dateString] = Array(8).fill(false))
 : null;
 Array.isArray(currentSchedule[dateString])
 ? (currentSchedule[dateString][slot\_time] = true)
 : null;
 return currentSchedule;
 }, initialSchedule);

for (let day in schedule) {
 let slots = schedule[day];
 slots.length
 ? slots.every(slot =\> slot === true) ? (schedule[day] = true) : null
 : null;
 }

this.setState({
 schedule: schedule
 });
 }

The renderAppointmentTimes method renders available time slots to user and disables the rest if any.

renderAppointmentTimes() {
 if (!this.state.isLoading) {
 const slots = [...Array(8).keys()];
 return slots.map(slot =\> {
 const appointmentDateString = moment(this.state.appointmentDate).format(
 "YYYY-DD-MM"
 );
 const time1 = moment()
 .hour(9)
 .minute(0)
 .add(slot, "hours");
 const time2 = moment()
 .hour(9)
 .minute(0)
 .add(slot + 1, "hours");
 const scheduleDisabled = this.state.schedule[appointmentDateString]
 ? this.state.schedule[
 moment(this.state.appointmentDate).format("YYYY-DD-MM")
 ][slot]
 : false;
 const meridiemDisabled = this.state.appointmentMeridiem
 ? time1.format("a") === "am"
 : time1.format("a") === "pm";
 return (
 \<RadioButton
 label={time1.format("h:mm a") + " - " + time2.format("h:mm a")}
 key={slot}
 value={slot}
 style={{
 marginBottom: 15,
 display: meridiemDisabled ? "none" : "inherit"
 }}
 disabled={scheduleDisabled || meridiemDisabled}
 /\>
 );
 });
 } else {
 return null;
 }
 }

The renderAppointmentConfirmation method display a modal with the user’s inputted information and asks th user to confirm, before saving to database.

renderAppointmentConfirmation() {
 const spanStyle = { color: "#00C853" };
 return (
 \<section\>
 \<p\>
 Name:{" "}
 \<span style={spanStyle}\>
 {this.state.firstName} {this.state.lastName}
 \</span\>
 \</p\>
 \<p\>
 Number: \<span style={spanStyle}\>{this.state.phone}\</span\>
 \</p\>
 \<p\>
 Email: \<span style={spanStyle}\>{this.state.email}\</span\>
 \</p\>
 \<p\>
 Appointment:{" "}
 \<span style={spanStyle}\>
 {moment(this.state.appointmentDate).format(
 "dddd[,] MMMM Do[,] YYYY"
 )}
 \</span\>{" "}
 at{" "}
 \<span style={spanStyle}\>
 {moment()
 .hour(9)
 .minute(0)
 .add(this.state.appointmentSlot, "hours")
 .format("h:mm a")}
 \</span\>
 \</p\>
 \</section\>
 );
}

The handleSubmit method is used to pass user data to the database via the express app. it will display a snackbar message if the data is successfully saved in the database or if it fails.

handleSubmit() {
 this.setState({ confirmationModalOpen: false });
 const newAppointment = {
 name: this.state.firstName + " " + this.state.lastName,
 email: this.state.email,
 phone: this.state.phone,
 slot\_date: moment(this.state.appointmentDate).format("YYYY-DD-MM"),
 slot\_time: this.state.appointmentSlot
 };
 axios
 .post(API\_BASE + "api/appointmentCreate", newAppointment)
 .then(response =\>
 this.setState({
 confirmationSnackbarMessage: "Appointment succesfully added!",
 confirmationSnackbarOpen: true,
 processed: true
 })
 )
 .catch(err =\> {
 console.log(err);
 return this.setState({
 confirmationSnackbarMessage: "Appointment failed to save.",
 confirmationSnackbarOpen: true
 });
 });
}

The render method renders all components to the virtual DOM(Document Object Model).

{
 const {
 finished,
 isLoading,
 smallScreen,
 stepIndex,
 confirmationModalOpen,
 confirmationSnackbarOpen,
 ...data
 } = this.state;
 const contactFormFilled =
 data.firstName &&
 data.lastName &&
 data.phone &&
 data.email &&
 data.validPhone &&
 data.validEmail;
 const DatePickerExampleSimple = () =\> (
 \<div\>
 \<DatePicker
 hintText="Select Date"
 mode={smallScreen ? "portrait" : "landscape"}
 onChange={(n, date) =\> this.handleSetAppointmentDate(date)}
 shouldDisableDate={day =\> this.checkDisableDate(day)}
 /\>
 \</div\>
 );
 const modalActions = [
 \<FlatButton
 label="Cancel"
 primary={false}
 onClick={() =\> this.setState({ confirmationModalOpen: false })}
 /\>,
 \<FlatButton
 label="Confirm"
 style={{ backgroundColor: "#00C853 !important" }}
 primary={true}
 onClick={() =\> this.handleSubmit()}
 /\>
 ];
 return (
 \<div\>
 \<AppBar
 title="Appointment Scheduler"
 iconClassNameRight="muidocs-icon-navigation-expand-more"
 /\>
 \<section
 style={{
 maxWidth: !smallScreen ? "80%" : "100%",
 margin: "auto",
 marginTop: !smallScreen ? 20 : 0
 }}
 \>
 \<Card
 style={{
 padding: "12px 12px 25px 12px",
 height: smallScreen ? "100vh" : null
 }}
 \>
 \<Stepper
 activeStep={stepIndex}
 orientation="vertical"
 linear={false}
 \>
 \<Step\>
 \<StepLabel\>
 Choose an available day for your appointment
 \</StepLabel\>
 \<StepContent\>
 {DatePickerExampleSimple()}
 {this.renderStepActions(0)}
 \</StepContent\>
 \</Step\>
 \<Step disabled={!data.appointmentDate}\>
 \<StepLabel\>
 Choose an available time for your appointment
 \</StepLabel\>
 \<StepContent\>
 \<SelectField
 floatingLabelText="AM/PM"
 value={data.appointmentMeridiem}
 onChange={(evt, key, payload) =\>
 this.handleSetAppointmentMeridiem(payload)
 }
 selectionRenderer={value =\> (value ? "PM" : "AM")}
 \>
 \<MenuItem value={0} primaryText="AM" /\>
 \<MenuItem value={1} primaryText="PM" /\>
 \</SelectField\>
 \<RadioButtonGroup
 style={{
 marginTop: 15,
 marginLeft: 15
 }}
 name="appointmentTimes"
 defaultSelected={data.appointmentSlot}
 onChange={(evt, val) =\> this.handleSetAppointmentSlot(val)}
 \>
 {this.renderAppointmentTimes()}
 \</RadioButtonGroup\>
 {this.renderStepActions(1)}
 \</StepContent\>
 \</Step\>
 \<Step\>
 \<StepLabel\>
 Share your contact information with us and we'll send you a
 reminder
 \</StepLabel\>
 \<StepContent\>
 \<p\>
 \<section\>
 \<TextField
 style={{ display: "block" }}
 name="first\_name"
 hintText="First Name"
 floatingLabelText="First Name"
 onChange={(evt, newValue) =\>
 this.setState({ firstName: newValue })
 }
 /\>
 \<TextField
 style={{ display: "block" }}
 name="last\_name"
 hintText="Last Name"
 floatingLabelText="Last Name"
 onChange={(evt, newValue) =\>
 this.setState({ lastName: newValue })
 }
 /\>
 \<TextField
 style={{ display: "block" }}
 name="email"
 hintText="youraddress@mail.com"
 floatingLabelText="Email"
 errorText={
 data.validEmail ? null : "Enter a valid email address"
 }
 onChange={(evt, newValue) =\>
 this.validateEmail(newValue)
 }
 /\>
 \<TextField
 style={{ display: "block" }}
 name="phone"
 hintText="+2348995989"
 floatingLabelText="Phone"
 errorText={
 data.validPhone ? null : "Enter a valid phone number"
 }
 onChange={(evt, newValue) =\>
 this.validatePhone(newValue)
 }
 /\>
 \<RaisedButton
 style={{ display: "block", backgroundColor: "#00C853" }}
 label={
 contactFormFilled
 ? "Schedule"
 : "Fill out your information to schedule"
 }
 labelPosition="before"
 primary={true}
 fullWidth={true}
 onClick={() =\>
 this.setState({
 confirmationModalOpen: !this.state
 .confirmationModalOpen
 })
 }
 disabled={!contactFormFilled || data.processed}
 style={{ marginTop: 20, maxWidth: 100 }}
 /\>
 \</section\>
 \</p\>
 {this.renderStepActions(2)}
 \</StepContent\>
 \</Step\>
 \</Stepper\>
 \</Card\>
 \<Dialog
 modal={true}
 open={confirmationModalOpen}
 actions={modalActions}
 title="Confirm your appointment"
 \>
 {this.renderAppointmentConfirmation()}
 \</Dialog\>
 \<SnackBar
 open={confirmationSnackbarOpen || isLoading}
 message={
 isLoading ? "Loading... " : data.confirmationSnackbarMessage || ""
 }
 autoHideDuration={10000}
 onRequestClose={() =\>
 this.setState({ confirmationSnackbarOpen: false })
 }
 /\>
 \</section\>
 \</div\>
 );
}

update your AppointmentApp.js with source code, run your app via Command line using:

npm start

Make sure both the express and appointment-scheduler apps are running, test to see if working properly.

Conclusion

Now we are done with our apps, congratulations!, I know it was challenging, but you have created two app demos. WELL DONE…

Featured React JS Courses

Upgrade your JavaScript skills.

Learn React.js in just a couple of afternoons.

A premium step-by-step training course by Wes Bos to get you building real-world React.js + Firebase apps and website components. START LEARNING NOW

Disclaimer

This post contains affiliate links to products. We may receive a commission for purchases made through these links.


Top comments (2)

Collapse
 
dance2die profile image
Sung M. Kim

Thanks you for the great articles, kris.

May I request to add a markdown syntax highlights for code snippets?
And code snippets start with \...

Collapse
 
rob88 profile image
Reben Faraj

Hi Everyone I would like to contribute in this project if you guys have started please let me know

Thank you #