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.
See preview below:
We are going to concentrate on the following:
Backend (Part 1)
- Creating a template Express Web app
- MongoDB database configuration
- Creating a Nexmo account
- Creating routes, model, and controllers for Express web app
- Testing Express Web App with Postman
Frontend (Part 2)
- Creating a template React App
- Creating Views for React
- 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:
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
2. 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:
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:
Below, type “appointments” as database name
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:
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 servedapp.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:
Now we are done with our express app creation. Let’s see if we can post and get values using our express app.
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:
Followed by, retrieving slots:
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/";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…