Introduction
Like its’ Microsoft counterpart Excel, Google Sheets has got a lot of powerful features we can use to improve our productivity. There are quite a number of indie startups whose goals are to help people master the use of Google sheets in powering web apps or even simple data trackers.
So you can imagine my excitement when I discovered Sheet.Best – a website that turns your spreadsheets into Rest APIs. I thought it would be fun trying it out with a quite simple HR tool for gathering basic employee information.
Here’s the result:
You can also follow closely with the git repo, which you can find here:
Creating A React App and Installing Tailwind CSS
As we will be using Tailwind CSS – A utility-first CSS framework that helps developers rapidly build web assets, I thought it would be helpful to follow the official Tailwind CSS steps to create a react app with Tailwind CSS, which you can find here:
First, you set up your project with :
npx create-react-app my-project
cd my-project
And then, follow the remaining steps, which you will find in the short guide.
Following the recommended steps takes about 5 minutes.
Upon setting up your starter app, your project directory will look like this.
We will also be cleaning up our App.js file like below:
function App() {
return (
<div className="App">
</div>
);
}
export default App;
Building Our Components
For our HR page, there are two major components we will need to build.
- A form with input details
- A Popup on successful submitting the form
Creating A Form
Our form will have input details such as First Name, Last Name, Job Title, Department, email address, and Feedback section.
- i. Create a new folder under src called components
- ii. Create 2 filles called Form.js and PopUp.js i.e src > components > Form.js and src > components > PopUp.js
The beauty about Tailwind CSS is the existence of free, open-source reusable components which you can integrate into your project. You can find some on the project’s official page.
So please feel free to tweak the form component as much as you want. Here’s mine.
import React, { useState } from "react";
import img from "../images/pablo-coming-soon-dark-mono.png"
import logo from "../images/logo.png"
export default function Form() {
return (
<section className="mt-28">
<div className="flex flex-wrap">
<div className="hidden lg:block relative w-full lg:w-1/2 bg-green-600">
<div className="absolute bottom-0 inset-x-0 mx-auto mb-12 max-w-xl text-center" style={{zIndex: 10}}>
<img className="lg:max-w-xl mx-auto" src={img} alt="" />
<h2 className="mb-2 text-2xl text-white font-bold">A Simple HR Tool</h2>
<div className="max-w-lg mx-auto">
<p className="mb-6 text-gray-50 leading-loose">Collect details from your employees.</p>
</div>
</div>
</div>
<div className="lg:hidden bg-green-600 w-full">
<div className="relative w-full">
<img className="relative mx-auto max-w-sm mt-4 mb-4 block" src={img} alt="" />
</div>
<div className="py-10 px-3 text-center" style={{zIndex: 10}}>
<h2 className="mb-2 text-2xl text-white font-bold">A Simple HR Tool</h2>
<p className="mb-6 text-gray-50 leading-loose">Collect details from your employees.</p>
<a className="py-2 px-6 rounded-t-xl rounded-l-xl bg-white hover:bg-gray-500 text-gray-900 hover:text-white transition duration-200 font-bold" href="./">Get Started</a>
</div>
</div>
<div className="pt-6 lg:pt-16 pb-6 w-full lg:w-1/2">
<div className="max-w-md mx-auto">
<div className="mb-6 lg:mb-10 w-full px-3 flex items-center justify-between">
<a className="text-3xl font-bold leading-none" href="./">
<img className="h-12" src={logo} alt="" width="auto"/>
</a>
</div>
<div>
<div className="mb-6 px-3">
<h3 className="text-xl font-semibold">Please fill in the necessary details</h3>
</div>
<form onSubmit={submitForm} >
<div className="flex flex-wrap">
<div className="mb-3 w-full lg:w-1/2 px-2">
<input className="w-full p-4 text-xs bg-gray-50 outline-none rounded" type="text" placeholder="First Name"
name = "fname" value = {form.fname} />
</div>
<div className="mb-3 w-full lg:w-1/2 px-2">
<input className="w-full p-4 text-xs bg-gray-50 outline-none rounded" type="text" placeholder="Last Name"
name = "lname" value = {form.lname} />
</div>
</div>
<div className="flex flex-wrap">
<div className="mb-3 w-full lg:w-1/2 px-2">
<input className="w-full p-4 text-xs bg-gray-50 outline-none rounded" type="text" placeholder="Job Title"
name = "jtitle" value = {form.jtitle} />
</div>
<div className="mb-3 w-full lg:w-1/2 px-2">
<input className="w-full p-4 text-xs bg-gray-50 outline-none rounded" type="text" placeholder="Department"
name = "department" value = {form.department}
/>
</div>
</div>
<div className="mb-3 flex p-4 mx-2 bg-gray-50 rounded">
<input className="w-full text-xs bg-gray-50 outline-none" type="email" placeholder="name@email.com"
name = "email" value = {form.email} />
<svg className="h-6 w-6 ml-4 my-auto text-gray-300" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207"></path>
</svg>
</div>
<div className="mb-6 flex p-4 mx-2 bg-gray-50 rounded">
<textarea className="w-full text-xs bg-gray-50 outline-none resize border rounded-md" placeholder="Feedback"
name = "feedback" value = {form.feedback} ></textarea>
</div>
<div className="px-3 text-center">
<button className="mb-2 w-full py-4 bg-green-600 hover:bg-green-700 rounded text-sm font-bold text-gray-50 transition duration-200">Submit</button>
</div>
</form>
</div>
</div>
</div>
</div>
</section>
)
}
Creating a popup component
import React, { useState } from "react";
import logo from "../images/logo.png"
export default function Popup() {
return (
<>
<div className="fixed z-10 inset-0 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div className="flex items-end justify-center min-h-screen pt-4 lg:ml-96 px-4 pb-20 text-center sm:block sm:p-0">
<div className="fixed inset-0 bg-gray-500 bg-opacity-100 transition-opacity" aria-hidden="true"></div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
<div className="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div className="">
<img className=" mr-auto mt-3 mb-4 " src={logo} alt="" width="150" />
</div>
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-title">
Thank You!
</h3>
<div className="mt-2">
<p className="text-sm text-gray-500">
Thanks for your service. You're a valued employee!!! We will get in touch soon if necessary.
</p>
</div>
</div>
</div>
</div>
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<div className="actions">
<button
className="button w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"
>
Close
</button>
</div>
</div>
</div>
</div>
</div>
</>
)
}
Adding Functionality
After completing the UI of our form and popup components, it’s time to add some functionalities that will help us track the input data.
First,
import Popup from "./PopUp"
We then create a useState react hook that will help us keep track of the form input details.
const [ form, setForm ] = useState({
fname: '',
lname: '',
jtitle: '',
department: '',
feedback: '',
email: '',
pop: null
})
We then add a updateForm function and submitForm function
const updateForm = e => {
setForm({
...form,
[e.target.name]: e.target.value
})
}
const submitForm = e => {
e.preventDefault();
console.log(form)
setForm({ pop: true, fname : '', lname: '', jtitle: '', department: '', feedback: '', email: '' })
}
This will update our input variables’ state and reset them back to empty upon submitting the form.
We will also be adding a ternary operator to display our popup
{ form.pop ? ( <Popup /> ) : null }
We can now test our form and see the resulting details on the console.log
Posting The Form Data to Google Sheets
Since we aim to gather the form inputs in a Google spreadsheet, we have to find a way to make our application talk to our sheet. Luckily, this is where https://sheet.best/ comes in.
1. Create and Name A blank spreadsheet. Do make sure to name the columns exactly as they appear in our app.
2. Click the share Button setting the permissions as shown below.
Copy the link and head over to sheet.best where we will be using their free trial package.
3. Add a connection
Fill in the appropriate details and also the link you copied
Click on the newly created connection details and copy the connection URL. We’re almost done.
Connecting React App to API
With the connection URL, we can now connect our form to the created sheet.
First, install
npm i axios
Then, import Axios in our App.js
import axios from "axios"
Next, connect to our endpoint URL in our form submission submitForm function.
axios.post('https://sheet.best/api/sheets/a2cb5e9d-58e6-4a02-xxx-xxxxxxxxx', form)
.then( response => {
console.log(response)
})
.catch((error) => {
console.log(error);
});
You can find the complete code here for your perusal.
Now, run your react app, fill in the form and see the details captured in your Google spreadsheet successfully.
Deploying Our App
Now that we’re done with our application, it’s time to deploy it so we can have a shareable link.
For this, we will be using Vercel.
With vercel, deploying our React app is as simple as connecting our app git repository to the Vercel deploy page here.
In the end, we will get a shareable link like this. You can even set up a custom domain if you want.