{"id":31548,"date":"2023-10-23T07:00:00","date_gmt":"2023-10-23T14:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=31548"},"modified":"2025-11-26T21:47:55","modified_gmt":"2025-11-27T05:47:55","slug":"build-timeline-tracker-cloudinary-xata-next-js","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js","title":{"rendered":"Build a Timeline Tracker With Cloudinary, Xata, and Next.js"},"content":{"rendered":"\n<p>A timeline visually represents the work required to finish our project. It displays the dates that each activity was completed so we can monitor our progress. On the other hand, it may also convey what&#8217;s required to achieve deadlines, helping assignees and stakeholders manage their expectations and assign priorities to projects in the long term.<\/p>\n\n\n\n<p>In this blog post, we&#8217;ll build a timeline tracker with&nbsp;<a href=\"https:\/\/cloudinary.com\/?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\" target=\"_blank\" rel=\"noreferrer noopener\">Cloudinary<\/a>&nbsp;and&nbsp;<a href=\"https:\/\/xata.io\/?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\" target=\"_blank\" rel=\"noreferrer noopener\">Xata<\/a>&nbsp;in a Next.js application.<\/p>\n\n\n\n<p>It&#8217;s important to note that timeline trackers come in various forms and serve multiple purposes. Tools like Google Maps Timeline can track places visited, offering functionality for mileage logs, while apps like Timelines provide insights into time management through interactive timelines and charts. In Excel, timelines can be created with sub-events, offering a structured event-tracking approach. This post focuses on a custom-built solution, but understanding the broader landscape of timeline tracking tools, such as Toggl Plan, Timely, and Monday, can help tailor a solution to specific needs.<\/p>\n\n\n\n<p>Check out the live <a href=\"https:\/\/timeline-tracker-with-cloudinary-xata-and-next-js.vercel.app\/?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\">demo<\/a> and the <a href=\"https:\/\/github.com\/Olanetsoft\/Timeline-Tracker-with-Cloudinary-Xata-and-NextJs\" target=\"_blank\" rel=\"noreferrer noopener\">GitHub Repository<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What is Cloudinary?<\/h2>\n\n\n\n<p><a href=\"https:\/\/cloudinary.com\/documentation\/image_video_and_file_upload#upload_options_overview\">Cloudinary<\/a>&nbsp;provides a secure and comprehensive API for uploading media files fast and efficiently from the server side, the browser, or a mobile application. We can upload media assets using Cloudinary&#8217;s REST API or client libraries (SDKs) which makes integrating with websites and mobile apps more accessible.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-timeline-tracker-with-cloudinary-xata-and-nextjs-3p4k#what-is-xata\"><\/a>What is Xata?<\/h2>\n\n\n\n<p><a href=\"https:\/\/xata.io\/?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\">Xata<\/a>&nbsp;is a serverless data platform that enables us to manage, scale, prevent downtime, cache, and maintain our database to improve our development workflow. Additionally, it offers a relational database, an effective search engine, and much more.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-timeline-tracker-with-cloudinary-xata-and-nextjs-3p4k#prerequisites\"><\/a>Prerequisites<\/h2>\n\n\n\n<p>Before getting started with this tutorial, we should have the following:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Basic understanding of ES6 Javascript features<\/li>\n\n\n\n<li>NodeJs installed in our PC<\/li>\n\n\n\n<li>Knowledge of React and React hooks<\/li>\n\n\n\n<li>Yarn or NPM package manager<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-timeline-tracker-with-cloudinary-xata-and-nextjs-3p4k#project-setup-and-installation\"><\/a>Project Setup and Installation<\/h2>\n\n\n\n<p>To proceed, let us clone the starter project in our preferred directory with the git command below:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\">git <span class=\"hljs-keyword\">clone<\/span> https:<span class=\"hljs-comment\">\/\/github.com\/Olanetsoft\/Timeline-Tracker-with-Cloudinary-Xata-and-NextJs\/tree\/starter<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Run the following command to install all dependencies using the&nbsp;<code>yarn<\/code>&nbsp;package manager.<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-wrap-lines\">yarn &amp;&amp; yarn dev\n<\/code><\/span><\/pre>\n\n\n<p>Or Run the following command to install all dependencies using the&nbsp;<code>npm<\/code>&nbsp;package manager to start the project on&nbsp;<a href=\"http:\/\/localhost:3000\/\">http:\/\/localhost:3000<\/a>.<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs shcb-wrap-lines\">npm install &amp;&amp; npm run dev \n<\/code><\/span><\/pre>\n\n\n<p>We should have something similar to what we have below.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--A5j0c1yQ--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800\/https:\/\/paper-attachments.dropboxusercontent.com\/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669245529841_Screenshot%2B2022-11-24%2Bat%2B00.18.35.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764222234\/blog-Build_a_Timeline_Tracker_With_Cloudinary_Xata_and_Next.js-1.png\" alt=\"Timeline Tracker\"\/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-timeline-tracker-with-cloudinary-xata-and-nextjs-3p4k#creating-and-setting-up-xata\"><\/a>Creating and Setting Up Xata<\/h2>\n\n\n\n<p>In this section, we will set up a&nbsp;<a href=\"https:\/\/xata.io\/?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\">Xata<\/a>&nbsp;profile by&nbsp;<a href=\"https:\/\/app.xata.io\/signin?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\">signing up<\/a>&nbsp;for a new account or&nbsp;<a href=\"http:\/\/app.xata.io\/?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\">log<\/a>&nbsp;<a href=\"http:\/\/app.xata.io\/?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\">in<\/a>. We will get redirected to our dashboard after successful sign-up, as shown below.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--x9w2wY_N--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800\/https:\/\/paper-attachments.dropboxusercontent.com\/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669245912011_Screenshot%2B2022-11-22%2Bat%2B23.56.16.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764222236\/blog-Build_a_Timeline_Tracker_With_Cloudinary_Xata_and_Next.js-2.png\" alt=\"Idris Olubisi Xata workspace\"\/><\/a><\/figure>\n\n\n\n<p>Next, we will click&nbsp;<code>Add a database<\/code>, enter our preferred database name*<strong>, and<\/strong>*&nbsp;<em>click<\/em>&nbsp;<code>create<\/code>. We will be redirected to the database page similar to what is shown below.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--sMRVC-8l--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800\/https:\/\/paper-attachments.dropboxusercontent.com\/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669246203683_Screenshot%2B2022-11-24%2Bat%2B00.28.14.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764222239\/blog-Build_a_Timeline_Tracker_With_Cloudinary_Xata_and_Next.js-3.png\" alt=\"Add database on Xata\"\/><\/a><\/figure>\n\n\n\n<p>We will create a table called&nbsp;<code>users<\/code>&nbsp;to save all the records of users that signed up on our platform, as shown below.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--pYcXkMT7--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800\/https:\/\/paper-attachments.dropboxusercontent.com\/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669246644628_Screenshot%2B2022-11-24%2Bat%2B00.30.38.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764222242\/blog-Build_a_Timeline_Tracker_With_Cloudinary_Xata_and_Next.js-4.png\" alt=\"Create database table on Xata\"\/><\/a><\/figure>\n\n\n\n<figure class=\"wp-block-image\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--4406vlNi--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800\/https:\/\/paper-attachments.dropboxusercontent.com\/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669246644591_Screenshot%2B2022-11-24%2Bat%2B00.33.30.png\"><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--4406vlNi--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800\/https:\/\/paper-attachments.dropboxusercontent.com\/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669246644591_Screenshot%2B2022-11-24%2Bat%2B00.33.30.png\" alt=\"Create Table\"\/><\/a><\/figure>\n\n\n\n<p>Add the following columns to the&nbsp;<code>users<\/code>&nbsp;table:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th><strong>Column Name<\/strong><\/th><th><strong>Data Type<\/strong><\/th><th><strong>Unique<\/strong><\/th><\/tr><\/thead><tbody><tr><td>firstName<\/td><td>String<\/td><td>[ ]<\/td><\/tr><tr><td>lastName<\/td><td>String<\/td><td>[ ]<\/td><\/tr><tr><td>email<\/td><td>Email<\/td><td>[x]<\/td><\/tr><tr><td>password<\/td><td>String<\/td><td>[ ]<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>We will repeat the above process to create a new table called&nbsp;<code>timelines<\/code>&nbsp;with the following columns:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th><strong>Column Name<\/strong><\/th><th><strong>Data Type<\/strong><\/th><th><strong>Unique<\/strong><\/th><th><strong>Table<\/strong><\/th><\/tr><\/thead><tbody><tr><td>title<\/td><td>String<\/td><td>[ ]<\/td><td>&#8211;<\/td><\/tr><tr><td>description<\/td><td>String<\/td><td>[ ]<\/td><td>&#8211;<\/td><\/tr><tr><td>timeline<\/td><td>String<\/td><td>[ ]<\/td><td>&#8211;<\/td><\/tr><tr><td>image_url<\/td><td>String<\/td><td>[ ]<\/td><td>&#8211;<\/td><\/tr><tr><td>user<\/td><td>link<\/td><td>[ ]<\/td><td>users table<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Next, we will connect our app to the remote Xata database we created using the following command below.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\"># Install the Xata CLI globally<\/span>\nnpm i -g @xata.io\/cli\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>After installing the Xata CLI globally, we will use the following command to initiate the database instance locally.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">xata init --db https:<span class=\"hljs-comment\">\/\/Olubisi-Idris-Ayinde-s-workspace-s.us-east-1.xata.sh\/db\/timeline<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>To get the above code snippet for our database, go to the user&#8217;s table and click&nbsp;<code>Get code snippet,<\/code>&nbsp;as shown below.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--BvQP-tC_--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800\/https:\/\/paper-attachments.dropboxusercontent.com\/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669247742239_Screenshot%2B2022-11-24%2Bat%2B00.52.18.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764222245\/blog-Build_a_Timeline_Tracker_With_Cloudinary_Xata_and_Next.js-5.png\" alt=\"Get code snippet on Xata.\"\/><\/a><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--ZSRVGKM4--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800\/https:\/\/paper-attachments.dropboxusercontent.com\/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669247742297_Screenshot%2B2022-11-24%2Bat%2B00.53.04.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764222248\/blog-Build_a_Timeline_Tracker_With_Cloudinary_Xata_and_Next.js-6.png\" alt=\"Copy code snippet on the Xata dashboard\"\/><\/a><\/figure>\n\n\n\n<p>after running the command to initialize the project; we will see a prompt asking us to select a few configurations. Let us choose the following.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--QN7iSx5I--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800\/https:\/\/paper-attachments.dropboxusercontent.com\/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669247966843_Screenshot%2B2022-11-24%2Bat%2B00.59.09.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764222250\/blog-Build_a_Timeline_Tracker_With_Cloudinary_Xata_and_Next.js-7.png\" alt=\"Xata cli configuration\"\/><\/a><\/figure>\n\n\n\n<p>Our browser automatically opens up to set a name for the API key, as shown below. Feel free to choose your preferred name.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--MwpJy-fQ--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800\/https:\/\/paper-attachments.dropboxusercontent.com\/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669248110574_Screenshot%2B2022-11-24%2Bat%2B01.00.44.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764222253\/blog-Build_a_Timeline_Tracker_With_Cloudinary_Xata_and_Next.js-8.png\" alt=\"Set API key for Xata\"\/><\/a><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--BJyl1Xd9--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800\/https:\/\/paper-attachments.dropboxusercontent.com\/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669248163313_Screenshot%2B2022-11-24%2Bat%2B01.02.19.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764222258\/blog-Build_a_Timeline_Tracker_With_Cloudinary_Xata_and_Next.js-9.png\" alt=\"API Key\"\/><\/a><\/figure>\n\n\n\n<p>On our terminal, we can accept other prompts to add&nbsp;<code>.gitignore<\/code>&nbsp;and&nbsp;<code>.env,<\/code>&nbsp;as shown below.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--aL7oJcJl--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800\/https:\/\/paper-attachments.dropboxusercontent.com\/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669248276329_Screenshot%2B2022-11-24%2Bat%2B01.03.33.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764222261\/blog-Build_a_Timeline_Tracker_With_Cloudinary_Xata_and_Next.js-10.jpg\" alt=\"Xata CLI\"\/><\/a><\/figure>\n\n\n\n<p>In the root of our project, we will see an&nbsp;<code>env<\/code>&nbsp;file with our&nbsp;<code>XATA_API_KEY<\/code>&nbsp;and&nbsp;<code>XataClient<\/code>&nbsp;inside the directory&nbsp;<code>\/src\/xata.js.<\/code><\/p>\n\n\n\n<p>We have successfully configured Xata in our project.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-timeline-tracker-with-cloudinary-xata-and-nextjs-3p4k#configuring-cloudinary-and-db-to-upload-images\"><\/a>Configuring Cloudinary and DB to Upload Images<\/h2>\n\n\n\n<p>We will be using Cloudinary&#8217;s upload media assets. Create a&nbsp;<a href=\"https:\/\/cloudinary.com\/users\/register\/free?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\">free Cloudinary account<\/a>&nbsp;to obtain&nbsp;<strong>Cloud Name<\/strong>,&nbsp;<strong>API Key<\/strong>, and&nbsp;<strong>API Secret<\/strong>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--ib1YKkMm--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800\/https:\/\/paper-attachments.dropboxusercontent.com\/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669248936724_Screenshot%2B2022-11-24%2Bat%2B01.13.12.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764222264\/blog-Build_a_Timeline_Tracker_With_Cloudinary_Xata_and_Next.js-11.png\" alt=\"Cloudinary vonfig\"\/><\/a><\/figure>\n\n\n\n<p>Update the&nbsp;<code>.env<\/code>&nbsp;file in the root directory of our project.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">CLOUDINARY_CLOUD_NAME= <span class=\"hljs-string\">'*********************'<\/span>\nCLOUDINARY_API_KEY=<span class=\"hljs-string\">'****************************'<\/span>\nCLOUDINARY_SECRET= <span class=\"hljs-string\">\"****************************\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Stop the application from running in the terminal and run yarn dev or npm run dev to restart the server.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-timeline-tracker-with-cloudinary-xata-and-nextjs-3p4k#implementing-user-authentication\"><\/a>Implementing User Authentication<\/h2>\n\n\n\n<p>In this section, we will implement user authentication functionality to help users register and login on to our application to create a timeline.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-timeline-tracker-with-cloudinary-xata-and-nextjs-3p4k#registration-functionality\"><\/a>Registration Functionality<\/h2>\n\n\n\n<p>Inside the&nbsp;<code>pages\/api\/<\/code>&nbsp;directory, update the&nbsp;<code>register.js<\/code>&nbsp;file with the following code snippet.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php shcb-wrap-lines\"><span class=\"hljs-comment\"># pages\/api\/register.js<\/span>\n\nimport { getXataClient } from <span class=\"hljs-string\">\"..\/..\/src\/xata\"<\/span>;\nimport { promisify } from <span class=\"hljs-string\">\"util\"<\/span>;\nimport bcrypt from <span class=\"hljs-string\">\"bcryptjs\"<\/span>; <span class=\"hljs-comment\">\/\/ bcrypt to hash user password<\/span>\n\n<span class=\"hljs-comment\">\/\/ Hash password with bcrypt<\/span>\n<span class=\"hljs-keyword\">const<\/span> hash = promisify(bcrypt.hash);\n\n<span class=\"hljs-comment\">\/\/ initialize XataClient function<\/span>\n<span class=\"hljs-keyword\">const<\/span> xata = getXataClient();\n\nexport <span class=\"hljs-keyword\">default<\/span> async <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">register<\/span><span class=\"hljs-params\">(req, res)<\/span> <\/span>{\n <span class=\"hljs-comment\">\/\/ Get user data from request body<\/span>\n <span class=\"hljs-keyword\">const<\/span> { firstName, lastName, email, password } = req.body;\n\n <span class=\"hljs-comment\">\/\/ Fetch user from database using email address as unique identifier if it exists<\/span>\n <span class=\"hljs-keyword\">const<\/span> userExist = await xata.db.users.filter({ email }).getFirst();\n\n <span class=\"hljs-comment\">\/\/ If user exists, return error<\/span>\n <span class=\"hljs-keyword\">if<\/span> (userExist) {\n     res.status(<span class=\"hljs-number\">400<\/span>);\n     <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> Error(<span class=\"hljs-string\">\"User already exists\"<\/span>); <span class=\"hljs-comment\">\/\/ throw error if user with email already exists<\/span>\n }\n\n <span class=\"hljs-comment\">\/\/ Create a new user in the database<\/span>\n <span class=\"hljs-keyword\">const<\/span> user = await xata.db.users.create({\n     firstName,\n     lastName,\n     email,\n     password: await hash(password, <span class=\"hljs-number\">10<\/span>),\n  });\n\n res.json({ message: <span class=\"hljs-string\">\"Success\"<\/span> });\n   <span class=\"hljs-keyword\">if<\/span> (!user) {\n   res.status(<span class=\"hljs-number\">400<\/span>);\n   <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> Error(<span class=\"hljs-string\">\"Invalid user data\"<\/span>);\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the code snippet above, we:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Created a function called&nbsp;<code>register<\/code>&nbsp;with&nbsp;<code>req<\/code>&nbsp;and&nbsp;<code>res<\/code>&nbsp;as a parameter<\/li>\n\n\n\n<li>Extracted&nbsp;<code>firstName, lastName, email, and password<\/code>&nbsp;from the request body<\/li>\n\n\n\n<li>Check if a user already exists using their email address<\/li>\n\n\n\n<li>Hash the user&#8217;s password using the bycrypt package<\/li>\n\n\n\n<li>Create a new user record in our database using the&nbsp;<a href=\"https:\/\/xata.io\/?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\">Xata<\/a>&nbsp;client&nbsp;<code>getXataClient<\/code>&nbsp;and return a&nbsp;<code>success<\/code>&nbsp;message if the user doedoesn&#8217;tist<\/li>\n<\/ul>\n\n\n\n<p>Next, please navigate the&nbsp;<code>register.js<\/code>&nbsp;file under the&nbsp;<code>\/pages<\/code>&nbsp;directory and update it with the following code snippet.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> React, { useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useRouter } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/router\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Navbar <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/components\/Navbar\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> Register = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> router = useRouter();\n  <span class=\"hljs-keyword\">const<\/span> &#91;firstName, setFirstName] = useState(<span class=\"hljs-string\">\"\"<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> &#91;lastName, setLastName] = useState(<span class=\"hljs-string\">\"\"<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> &#91;email, setEmail] = useState(<span class=\"hljs-string\">\"\"<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> &#91;password, setPassword] = useState(<span class=\"hljs-string\">\"\"<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> &#91;error, setError] = useState(<span class=\"hljs-string\">\"\"<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> &#91;loading, setLoading] = useState(<span class=\"hljs-literal\">false<\/span>);\n\n  <span class=\"hljs-keyword\">const<\/span> handleSubmit = <span class=\"hljs-keyword\">async<\/span> (e) =&gt; {\n    e.preventDefault();\n    setLoading(<span class=\"hljs-literal\">true<\/span>);\n\n    <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"Submitting form\"<\/span>);\n    <span class=\"hljs-built_in\">console<\/span>.log(email, password, firstName, lastName);\n\n    <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/register\"<\/span>, {\n      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n      <span class=\"hljs-attr\">headers<\/span>: {\n        <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n      },\n      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({\n        firstName,\n        lastName,\n        email,\n        password,\n      }),\n    })\n      .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">res<\/span>) =&gt;<\/span> res.json())\n      .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">data<\/span>) =&gt;<\/span> {\n        <span class=\"hljs-built_in\">console<\/span>.log(data);\n        <span class=\"hljs-keyword\">if<\/span> (data.error) {\n          setError(data.error);\n        } <span class=\"hljs-keyword\">else<\/span> {\n          router.push(<span class=\"hljs-string\">\"\/login\"<\/span>);\n        }\n      });\n\n    setLoading(<span class=\"hljs-literal\">false<\/span>);\n  };\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"container mx-auto flex justify-center items-center h-screen\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full max-w-xs bg-white rounded-lg shadow-md overflow-hidden mx-10\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-3xl font-bold text-center text-gray-700 mb-4 mt-6\"<\/span>&gt;<\/span>\n          Register\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span>\n          <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4\"<\/span>\n          <span class=\"hljs-attr\">onSubmit<\/span>=<span class=\"hljs-string\">{handleSubmit}<\/span>\n        &gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mb-4\"<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span>\n              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"form-control block w-full px-2 py-2 text-l font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none mt-2 mb-8\"<\/span>\n              <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"text\"<\/span>\n              <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"firstName\"<\/span>\n              <span class=\"hljs-attr\">placeholder<\/span>=<span class=\"hljs-string\">\"First Name\"<\/span>\n              <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{firstName}<\/span>\n              <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{(e)<\/span> =&gt;<\/span> setFirstName(e.target.value)}\n              required\n            \/&gt;\n\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span>\n              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"form-control block w-full px-2 py-2 text-l font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none mt-2 mb-8\"<\/span>\n              <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"text\"<\/span>\n              <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"lastName\"<\/span>\n              <span class=\"hljs-attr\">placeholder<\/span>=<span class=\"hljs-string\">\"Last Name\"<\/span>\n              <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{lastName}<\/span>\n              <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{(e)<\/span> =&gt;<\/span> setLastName(e.target.value)}\n              required\n            \/&gt;\n\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span>\n              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"form-control block w-full px-2 py-2 text-l font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none mt-2 mb-8\"<\/span>\n              <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"email\"<\/span>\n              <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"email\"<\/span>\n              <span class=\"hljs-attr\">placeholder<\/span>=<span class=\"hljs-string\">\"Email\"<\/span>\n              <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{(e)<\/span> =&gt;<\/span> setEmail(e.target.value)}\n              value={email}\n              required\n            \/&gt;\n\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span>\n              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"form-control block w-full px-2 py-2 text-l font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none mt-2 mb-6\"<\/span>\n              <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"password\"<\/span>\n              <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"password\"<\/span>\n              <span class=\"hljs-attr\">placeholder<\/span>=<span class=\"hljs-string\">\"Password\"<\/span>\n              <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{(e)<\/span> =&gt;<\/span> setPassword(e.target.value)}\n              value={password}\n              required\n            \/&gt;\n\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline mt-4 mb-4\"<\/span>\n              <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"submit\"<\/span>\n              <span class=\"hljs-attr\">disabled<\/span>=<span class=\"hljs-string\">{loading}<\/span>\n            &gt;<\/span>\n              {loading ? \"Loading...\" : \"Register\"}\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n            {error &amp;&amp; <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-red-500\"<\/span>&gt;<\/span>{error}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>}\n\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-center text-gray-500 text-xs\"<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span>\n                <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-blue-500 hover:text-blue-700 no-underline hover:underline cursor-pointer text-md mt-6\"<\/span>\n                <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/login\"<\/span>\n              &gt;<\/span>\n                Already have an account? Login here.\n              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> Register;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the code snippet above, the&nbsp;<code>handleSubmit<\/code>&nbsp;function sends the user data to the register API we created earlier, allowing us to create a new user in our database.<\/p>\n\n\n\n<p>Let&#8217;s head over to the&nbsp;<code>Navber.js<\/code>&nbsp;file in the&nbsp;<code>components\/<\/code>&nbsp;directory and update it with the following code snippet.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> Head <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/head\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Image <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/image\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Link <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/link\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useRouter } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/router\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> Navbar = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ isAuthenticated }<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> router = useRouter();\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"relative container mx-auto px-6 flex flex-col space-y-2\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex flex-col space-y-4 pb-10\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">header<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex flex-col items-center justify-center space-y-4\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-4xl font-bold text-center\"<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Link<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/\"<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-white-600 hover:text-gray-400\"<\/span>&gt;<\/span>\n              Build a Timeline Tracker with Cloudinary, Xata and NextJs\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Link<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n          {isAuthenticated ? (\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline\"<\/span>\n              <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"button\"<\/span>\n              <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> {\n                router.push(\"\/upload\");\n              }}\n            &gt;\n              Add New Timeline\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n          ) : (\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline\"<\/span>\n              <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"button\"<\/span>\n              <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span> {\n                router.push(\"\/register\");\n              }}\n            &gt;\n              Register or Login to Create a Timeline\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n          )}\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">header<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> Navbar;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the code snippet above, we added&nbsp;<code>isAuthenticated<\/code>&nbsp;props and a check to only display&nbsp;<code>Register or Login to Create a Timeline<\/code>&nbsp;when a user is not logged in.<\/p>\n\n\n\n<p>Next, we can update the&nbsp;<code>index.js<\/code>&nbsp;under the&nbsp;<code>pages\/<\/code>&nbsp;directory with the following code snippet to implement the&nbsp;<code>isAuthenticated<\/code>&nbsp;data and retrieve all the timeline records created in our database.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> Head <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/head\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Image <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/image\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Link <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/link\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useRouter } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/router\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Navbar <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/components\/Navbar\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> styles <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/styles\/Home.module.css\"<\/span>;\n\n<span class=\"hljs-keyword\">import<\/span> { getXataClient } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/src\/xata\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Home<\/span>(<span class=\"hljs-params\">{ records, isAuthenticated }<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> router = useRouter();\n  <span class=\"hljs-keyword\">const<\/span> userId = router.query.userId; <span class=\"hljs-comment\">\/\/ retrieve userId<\/span>\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">{styles.container}<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Head<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">title<\/span>&gt;<\/span>Build a Timeline Tracker with Cloudinary, Xata and NextJs<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">title<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">meta<\/span> <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"description\"<\/span> <span class=\"hljs-attr\">content<\/span>=<span class=\"hljs-string\">\"Generated by create next app\"<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">link<\/span> <span class=\"hljs-attr\">rel<\/span>=<span class=\"hljs-string\">\"icon\"<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/favicon.ico\"<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Head<\/span>&gt;<\/span>\n\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">main<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">{styles.main}<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"relative container mx-auto px-6 flex flex-col space-y-2\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Navbar<\/span> <span class=\"hljs-attr\">isAuthenticated<\/span>=<span class=\"hljs-string\">{isAuthenticated}<\/span> <span class=\"hljs-attr\">userId<\/span>=<span class=\"hljs-string\">{userId}<\/span> \/&gt;<\/span>\n\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"relative right-40\"<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"absolute z-0 w-2 h-full bg-white shadow-md inset-10 left-17 md:mx-auto md:right-0 md:left-0\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n            {records.map((record, index) =&gt; {\n              return (\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"relative z-10\"<\/span> <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{index}<\/span>&gt;<\/span>\n                  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span>\n                    <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{record.image_url}<\/span>\n                    <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">\"\"<\/span>\n                    <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"timeline-img\"<\/span>\n                    <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">{100}<\/span>\n                    <span class=\"hljs-attr\">height<\/span>=<span class=\"hljs-string\">{100}<\/span>\n                  \/&gt;<\/span>\n                  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"timeline-container\"<\/span>&gt;<\/span>\n                    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"timeline-pointer\"<\/span> <span class=\"hljs-attr\">aria-hidden<\/span>=<span class=\"hljs-string\">\"true\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n                    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"bg-white p-6 rounded-md shadow-md\"<\/span>&gt;<\/span>\n                      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">span<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"font-bold text-indigo-600 text-sm tracking-wide\"<\/span>&gt;<\/span>\n                        {record.timeline}\n                      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">span<\/span>&gt;<\/span>\n                      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-2xl font-bold pt-1 text-gray-900\"<\/span>&gt;<\/span>\n                        {record.title}\n                      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n                      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"pt-1 text-gray-800\"<\/span>&gt;<\/span>{record.description}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n                    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n                  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n              );\n            })}\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">main<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> getServerSideProps = <span class=\"hljs-keyword\">async<\/span> (context) =&gt; {\n  <span class=\"hljs-keyword\">let<\/span> isAuthenticated;\n  context.req.cookies&#91;<span class=\"hljs-string\">\"token\"<\/span>]\n    ? (isAuthenticated = <span class=\"hljs-literal\">true<\/span>)\n    : (isAuthenticated = <span class=\"hljs-literal\">false<\/span>);\n\n  <span class=\"hljs-keyword\">const<\/span> xata = getXataClient();\n\n  <span class=\"hljs-keyword\">const<\/span> records = <span class=\"hljs-keyword\">await<\/span> xata.db.timelines\n    .select(&#91;<span class=\"hljs-string\">\"*\"<\/span>, <span class=\"hljs-string\">\"user.firstName\"<\/span>, <span class=\"hljs-string\">\"user.lastName\"<\/span>])\n    .sort(<span class=\"hljs-string\">\"title\"<\/span>, <span class=\"hljs-string\">\"desc\"<\/span>)\n    .getAll();\n\n  <span class=\"hljs-keyword\">return<\/span> { <span class=\"hljs-attr\">props<\/span>: { records, isAuthenticated } };\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Heading over to our browser, we should have something similar to what is shown below. In the following section, we will implement the Login functionality.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/res.cloudinary.com\/practicaldev\/image\/fetch\/s--c71pzF8r--\/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800\/https:\/\/paper-attachments.dropboxusercontent.com\/s_5E340A634021481AD57D8D34688E6D339A5BA872FD7F90FC7DCC96BFBF1069A6_1669321707826_Screenshot%2B2022-11-24%2Bat%2B21.27.02.png\"><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1764222268\/blog-Build_a_Timeline_Tracker_With_Cloudinary_Xata_and_Next.js-12.png\" alt=\"Build a Timeline Tracker with Cloudinary,Xata and NextJs\"\/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-timeline-tracker-with-cloudinary-xata-and-nextjs-3p4k#login-functionality\"><\/a>Login Functionality<\/h2>\n\n\n\n<p>Similar to registering users on our platform, we will validate and log them into our system after registration. Let&#8217;s update&nbsp;<code>login.js<\/code>&nbsp;in the&nbsp;<code>pages\/api<\/code>&nbsp;directory with the following code snippet.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> cookie <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cookie\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { getXataClient } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/..\/src\/xata\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { promisify } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"util\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> bcrypt <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"bcryptjs\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> jwt <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"jsonwebtoken\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { use } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n\n<span class=\"hljs-comment\">\/\/ compare password with bcrypt<\/span>\n<span class=\"hljs-keyword\">const<\/span> compare = promisify(bcrypt.compare);\n\n<span class=\"hljs-comment\">\/\/ initialize XataClient<\/span>\n<span class=\"hljs-keyword\">const<\/span> xata = getXataClient();\n\n<span class=\"hljs-comment\">\/\/ Hash password with bcrypt<\/span>\n<span class=\"hljs-keyword\">const<\/span> KEY = <span class=\"hljs-string\">\"Our_Super_Secret_JWT_Key_For_Xata\"<\/span>;\n\n<span class=\"hljs-comment\">\/\/ Login<\/span>\n<span class=\"hljs-keyword\">const<\/span> Login = <span class=\"hljs-keyword\">async<\/span> (req, res) =&gt; {\n  <span class=\"hljs-comment\">\/\/ Get user data from request body<\/span>\n  <span class=\"hljs-keyword\">const<\/span> { email, password } = req.body;\n\n  <span class=\"hljs-comment\">\/\/ Fetch user from database using email address as unique identifier if it exists<\/span>\n  <span class=\"hljs-keyword\">const<\/span> user = <span class=\"hljs-keyword\">await<\/span> xata.db.users.filter({ email }).getFirst();\n\n  <span class=\"hljs-comment\">\/\/ compare password<\/span>\n  <span class=\"hljs-keyword\">const<\/span> passwordsMatch = compare(password, user.password);\n\n  <span class=\"hljs-keyword\">if<\/span> (passwordsMatch) {\n    <span class=\"hljs-keyword\">const<\/span> token = jwt.sign({ email, password }, KEY); <span class=\"hljs-comment\">\/\/ create token<\/span>\n\n    <span class=\"hljs-comment\">\/\/ Set multiple item in cookie<\/span>\n    res.setHeader(\n      <span class=\"hljs-string\">\"Set-Cookie\"<\/span>,\n      cookie.serialize(<span class=\"hljs-string\">\"token\"<\/span>, token, {\n        <span class=\"hljs-attr\">httpOnly<\/span>: <span class=\"hljs-literal\">true<\/span>,\n        <span class=\"hljs-attr\">secure<\/span>: <span class=\"hljs-string\">\"development\"<\/span>,\n        <span class=\"hljs-attr\">sameSite<\/span>: <span class=\"hljs-string\">\"strict\"<\/span>,\n        <span class=\"hljs-attr\">maxAge<\/span>: <span class=\"hljs-number\">60<\/span> * <span class=\"hljs-number\">60<\/span> * <span class=\"hljs-number\">24<\/span>, <span class=\"hljs-comment\">\/\/ 1 day<\/span>\n        <span class=\"hljs-attr\">path<\/span>: <span class=\"hljs-string\">\"\/\"<\/span>,\n      }) +\n        cookie.serialize(<span class=\"hljs-string\">\"userId\"<\/span>, user.id, {\n          <span class=\"hljs-attr\">httpOnly<\/span>: <span class=\"hljs-literal\">true<\/span>,\n          <span class=\"hljs-attr\">secure<\/span>: <span class=\"hljs-string\">\"development\"<\/span>,\n          <span class=\"hljs-attr\">sameSite<\/span>: <span class=\"hljs-string\">\"strict\"<\/span>,\n          <span class=\"hljs-attr\">maxAge<\/span>: <span class=\"hljs-number\">60<\/span> * <span class=\"hljs-number\">60<\/span> * <span class=\"hljs-number\">24<\/span>, <span class=\"hljs-comment\">\/\/ 1 day<\/span>\n          <span class=\"hljs-attr\">path<\/span>: <span class=\"hljs-string\">\"\/\"<\/span>,\n        })\n    );\n\n    <span class=\"hljs-comment\">\/\/ return userId<\/span>\n    res.json({ <span class=\"hljs-attr\">userId<\/span>: user.id });\n  } <span class=\"hljs-keyword\">else<\/span> {\n    res.status(<span class=\"hljs-number\">401<\/span>);\n    <span class=\"hljs-keyword\">throw<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Error<\/span>(<span class=\"hljs-string\">\"Invalid email or password\"<\/span>);\n  }\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> Login;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the code snippet above, we:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Created a function called&nbsp;<code>login<\/code>&nbsp;with&nbsp;<code>req<\/code>&nbsp;and&nbsp;<code>res<\/code>&nbsp;as a parameter<\/li>\n\n\n\n<li>Extracted&nbsp;<code>email and password<\/code>&nbsp;from the request body<\/li>\n\n\n\n<li>Check if a user already exists using their email address from the Xata database<\/li>\n\n\n\n<li>Compare the hashed user&#8217;s&nbsp;<code>password<\/code>&nbsp;using the bycrypt package<\/li>\n\n\n\n<li>Save the user token and Id in cookies to be used later in the tutorial<\/li>\n<\/ul>\n\n\n\n<p>Next, we will update the&nbsp;<code>login.js<\/code>&nbsp;file in the&nbsp;<code>pages\/<\/code>&nbsp;directory.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> React, { useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useRouter } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/router\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> Login = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> router = useRouter();\n  <span class=\"hljs-keyword\">const<\/span> &#91;email, setEmail] = useState(<span class=\"hljs-string\">\"\"<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> &#91;password, setPassword] = useState(<span class=\"hljs-string\">\"\"<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> &#91;error, setError] = useState(<span class=\"hljs-string\">\"\"<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> &#91;loading, setLoading] = useState(<span class=\"hljs-literal\">false<\/span>);\n\n  <span class=\"hljs-keyword\">const<\/span> handleSubmit = <span class=\"hljs-keyword\">async<\/span> (e) =&gt; {\n    e.preventDefault();\n    <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"Submitting form\"<\/span>);\n    <span class=\"hljs-built_in\">console<\/span>.log(email, password);\n    <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/login\"<\/span>, {\n      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n      <span class=\"hljs-attr\">headers<\/span>: {\n        <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n      },\n      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({\n        email,\n        password,\n      }),\n    })\n      .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">res<\/span>) =&gt;<\/span> res.json())\n      .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">data<\/span>) =&gt;<\/span> {\n        <span class=\"hljs-built_in\">console<\/span>.log(data);\n        <span class=\"hljs-keyword\">if<\/span> (data.error) {\n          setError(data.error);\n        } <span class=\"hljs-keyword\">else<\/span> {\n          router.push(<span class=\"hljs-string\">\"\/\"<\/span>);\n        }\n      });\n\n    setLoading(<span class=\"hljs-literal\">false<\/span>);\n  };\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"container mx-auto flex justify-center items-center h-screen\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full max-w-xs bg-white rounded-lg shadow-md overflow-hidden mx-10\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-3xl font-bold text-center text-gray-700 mb-4 mt-6\"<\/span>&gt;<\/span>\n          Sign In\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span>\n          <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4\"<\/span>\n          <span class=\"hljs-attr\">onSubmit<\/span>=<span class=\"hljs-string\">{handleSubmit}<\/span>\n        &gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mb-4\"<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span>\n              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"form-control block w-full px-2 py-2 text-l font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none mt-2 mb-8\"<\/span>\n              <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"email\"<\/span>\n              <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"email\"<\/span>\n              <span class=\"hljs-attr\">placeholder<\/span>=<span class=\"hljs-string\">\"Email\"<\/span>\n              <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{email}<\/span>\n              <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{(e)<\/span> =&gt;<\/span> setEmail(e.target.value)}\n              required\n            \/&gt;\n\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span>\n              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"form-control block w-full px-2 py-2 text-l font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none mt-2 mb-8\"<\/span>\n              <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"password\"<\/span>\n              <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"password\"<\/span>\n              <span class=\"hljs-attr\">placeholder<\/span>=<span class=\"hljs-string\">\"Password\"<\/span>\n              <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{password}<\/span>\n              <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{(e)<\/span> =&gt;<\/span> setPassword(e.target.value)}\n              required\n            \/&gt;\n\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline\"<\/span>\n              <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"submit\"<\/span>\n              <span class=\"hljs-attr\">disabled<\/span>=<span class=\"hljs-string\">{loading}<\/span>\n            &gt;<\/span>\n              {loading ? \"Loading...\" : \"Login\"}\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n\n            {error &amp;&amp; <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-red-500 text-xs italic\"<\/span>&gt;<\/span>{error}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>}\n\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-center text-gray-500 text-sm mt-6\"<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span>\n                <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-blue-500 hover:text-blue-700 no-underline hover:underline cursor-pointer text-md mt-6\"<\/span>\n                <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/register\"<\/span>\n              &gt;<\/span>\n                Don't have an account? Register here\n              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> Login;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h2 class=\"wp-block-heading\">Implementing User Upload Functionality<\/h2>\n\n\n\n<p>In the previous steps, we successfully implemented user authentication; we will proceed in this section to implement the upload functionality to allow logged-in users to create timelines.<\/p>\n\n\n\n<p>In the&nbsp;<code>upload.js<\/code>&nbsp;file under the&nbsp;<code>pages\/api<\/code>&nbsp;directory, let\u2019s update it with the following code snippet.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { getXataClient } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/..\/src\/xata\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> cookie <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cookie\"<\/span>;\n\n<span class=\"hljs-comment\">\/\/ import cloudinary<\/span>\n<span class=\"hljs-keyword\">import<\/span> { v2 } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cloudinary\"<\/span>;\n\n<span class=\"hljs-comment\">\/\/ Cloudinary config<\/span>\nv2.config({\n  <span class=\"hljs-attr\">cloud_name<\/span>: process.env.CLOUDINARY_CLOUD_NAME,\n  <span class=\"hljs-attr\">api_key<\/span>: process.env.CLOUDINARY_API_KEY,\n  <span class=\"hljs-attr\">api_secret<\/span>: process.env.CLOUDINARY_SECRET,\n});\n\n<span class=\"hljs-keyword\">const<\/span> xata = getXataClient();\n\n<span class=\"hljs-keyword\">const<\/span> uploader = <span class=\"hljs-keyword\">async<\/span> (req, res) =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> cookies = cookie.parse(req.headers.cookie || <span class=\"hljs-string\">\"\"<\/span>);\n\n  <span class=\"hljs-comment\">\/\/ check if user is authenticated<\/span>\n  <span class=\"hljs-keyword\">const<\/span> isAuthenticated = cookies.token;\n  <span class=\"hljs-keyword\">if<\/span> (!isAuthenticated) {\n    <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"not authenticated\"<\/span>);\n    res.status(<span class=\"hljs-number\">401<\/span>).end();\n    <span class=\"hljs-keyword\">return<\/span>;\n  }\n\n  <span class=\"hljs-keyword\">const<\/span> { title, description, timeline, image } = <span class=\"hljs-built_in\">JSON<\/span>.parse(req.body);\n\n  <span class=\"hljs-keyword\">const<\/span> result = <span class=\"hljs-keyword\">await<\/span> v2.uploader.upload(image, {\n    <span class=\"hljs-comment\">\/\/ upload to cloudinary<\/span>\n    <span class=\"hljs-attr\">auto_tagging<\/span>: <span class=\"hljs-number\">0.6<\/span>,\n  });\n\n  <span class=\"hljs-keyword\">await<\/span> xata.db.timelines.create({\n    title,\n    description,\n    <span class=\"hljs-attr\">user<\/span>: cookies.userId,\n    <span class=\"hljs-attr\">image_url<\/span>: result.secure_url,\n    timeline,\n  });\n  res.status(<span class=\"hljs-number\">200<\/span>).json(result);\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> uploader;\n\n<span class=\"hljs-comment\">\/\/ Allow only a specific size limit<\/span>\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> config = {\n  <span class=\"hljs-attr\">api<\/span>: {\n    <span class=\"hljs-attr\">bodyParser<\/span>: {\n      <span class=\"hljs-attr\">sizeLimit<\/span>: <span class=\"hljs-string\">\"10mb\"<\/span>,\n    },\n  },\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In the code snippet above, we implemented an API that allows users to create a new timeline, upload it to Cloudinary and save it into our&nbsp;<a href=\"https:\/\/xata.io\/?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\">Xata<\/a>&nbsp;database.<\/p>\n\n\n\n<p>Next, we will update the&nbsp;<code>upload.js<\/code>&nbsp;file in the&nbsp;<code>pages\/<\/code>&nbsp;directory to consume the&nbsp;<code>upload<\/code>&nbsp;API we just implemented with the following code snippet.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> React, { useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useRouter } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/router\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> Upload = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> router = useRouter();\n  <span class=\"hljs-keyword\">const<\/span> &#91;title, setTitle] = useState(<span class=\"hljs-string\">\"\"<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> &#91;description, setDescription] = useState(<span class=\"hljs-string\">\"\"<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> &#91;timeline, setTimeline] = useState(<span class=\"hljs-string\">\"\"<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> &#91;file, setFile] = useState(<span class=\"hljs-literal\">null<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> &#91;error, setError] = useState(<span class=\"hljs-string\">\"\"<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> &#91;loading, setLoading] = useState(<span class=\"hljs-literal\">false<\/span>);\n\n  <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handleOnChange<\/span>(<span class=\"hljs-params\">changeEvent<\/span>) <\/span>{\n    <span class=\"hljs-keyword\">const<\/span> reader = <span class=\"hljs-keyword\">new<\/span> FileReader(); <span class=\"hljs-comment\">\/\/ create new reader to read image file as a data URL<\/span>\n    reader.onload = <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">onLoadEvent<\/span>) <\/span>{\n      setFile(onLoadEvent.target.result); <span class=\"hljs-comment\">\/\/ save base64 encoded version of image<\/span>\n    };\n    reader.readAsDataURL(changeEvent.target.files&#91;<span class=\"hljs-number\">0<\/span>]);\n  }\n\n  <span class=\"hljs-keyword\">const<\/span> handleSubmit = <span class=\"hljs-keyword\">async<\/span> (e) =&gt; {\n    e.preventDefault();\n\n    setLoading(<span class=\"hljs-literal\">true<\/span>);\n\n    <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"Submitting form...\"<\/span>);\n\n    <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/upload\"<\/span>, {\n      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({\n        title,\n        description,\n        timeline,\n        <span class=\"hljs-attr\">image<\/span>: file,\n      }),\n    })\n      .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">res<\/span>) =&gt;<\/span> res.json())\n      .then(<span class=\"hljs-function\">(<span class=\"hljs-params\">data<\/span>) =&gt;<\/span> {\n        <span class=\"hljs-built_in\">console<\/span>.log(data);\n        <span class=\"hljs-keyword\">if<\/span> (data.error) {\n          setError(data.error);\n        } <span class=\"hljs-keyword\">else<\/span> {\n          router.push(<span class=\"hljs-string\">\"\/\"<\/span>);\n        }\n      });\n\n    setLoading(<span class=\"hljs-literal\">false<\/span>);\n  };\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"container mx-auto flex justify-center items-center h-screen\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"w-full max-w-xs bg-white rounded-lg shadow-md overflow-hidden mx-10\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h1<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-3xl font-bold text-center text-gray-700 mb-4 mt-6\"<\/span>&gt;<\/span>\n          Create Timeline\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h1<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span>\n          <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4\"<\/span>\n          <span class=\"hljs-attr\">onSubmit<\/span>=<span class=\"hljs-string\">{handleSubmit}<\/span>\n        &gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span>\n            <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"form-control block w-full px-2 py-2 text-l font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none mt-2 mb-8\"<\/span>\n            <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"text\"<\/span>\n            <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"Title\"<\/span>\n            <span class=\"hljs-attr\">placeholder<\/span>=<span class=\"hljs-string\">\"Title\"<\/span>\n            <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{title}<\/span>\n            <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{(e)<\/span> =&gt;<\/span> setTitle(e.target.value)}\n            required\n          \/&gt;\n\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span>\n            <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"form-control block w-full px-2 py-2 text-l font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none mt-2 mb-8\"<\/span>\n            <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"text\"<\/span>\n            <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"Description\"<\/span>\n            <span class=\"hljs-attr\">placeholder<\/span>=<span class=\"hljs-string\">\"Description\"<\/span>\n            <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{description}<\/span>\n            <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{(e)<\/span> =&gt;<\/span> setDescription(e.target.value)}\n            required\n          \/&gt;\n\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span>\n            <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"form-control block w-full px-2 py-2 text-l font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none mt-2 mb-8\"<\/span>\n            <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"date\"<\/span>\n            <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"Date\"<\/span>\n            <span class=\"hljs-attr\">placeholder<\/span>=<span class=\"hljs-string\">\"Date\"<\/span>\n            <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{timeline}<\/span>\n            <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{(e)<\/span> =&gt;<\/span> setTimeline(e.target.value)}\n            required\n          \/&gt;\n\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mb-4\"<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span>\n              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"form-control block w-full px-2 py-2 text-l font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none mt-2 mb-8\"<\/span>\n              <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"file\"<\/span>\n              <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"file\"<\/span>\n              <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{handleOnChange}<\/span>\n              <span class=\"hljs-attr\">required<\/span>\n            \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n            <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline\"<\/span>\n            <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"submit\"<\/span>\n            <span class=\"hljs-attr\">disabled<\/span>=<span class=\"hljs-string\">{loading}<\/span>\n          &gt;<\/span>\n            {loading ? \"Loading...\" : \"Submit\"}\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n          {error &amp;&amp; <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-red-500 text-xs italic\"<\/span>&gt;<\/span>{error}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>}\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> Upload;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Testing our application, we should have something similar to this.<\/p>\n\n\n\n<figure class=\"wp-block-embed aligncenter is-type-video is-provider-loom wp-block-embed-loom\"><div class=\"wp-block-embed__wrapper\">\n<iframe loading=\"lazy\" title=\"Build a Timeline Tracker with Cloudinary, Xata, and NextJs\" src=\"https:\/\/www.loom.com\/embed\/c154afad4b894ccdb088ce4cacf519d7\" frameborder=\"0\" width=\"500\" height=\"375\" webkitallowfullscreen mozallowfullscreen allowfullscreen><\/iframe>\n<\/div><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>This blog post teaches us how to build a timeline tracker using Cloudinary,&nbsp;<a href=\"https:\/\/xata.io\/?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\" target=\"_blank\" rel=\"noreferrer noopener\">Xata<\/a>, and Next.js. Try Cloudinary for yourself and <a href=\"https:\/\/cloudinary.com\/users\/register_free\">sign up for a free account today<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><a href=\"https:\/\/dev.to\/hackmamba\/build-a-timeline-tracker-with-cloudinary-xata-and-nextjs-3p4k#resources\"><\/a>Resources<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/xata.io\/docs\/overview?utm_source=hackmamba&amp;utm_campaign=hackmamba-hackathon&amp;utm_medium=hackmamba-blog\" target=\"_blank\" rel=\"noreferrer noopener\">Xata Documentation<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/cloudinary.com\/documentation\/upload_images\" target=\"_blank\" rel=\"noreferrer noopener\">Media upload on Cloudinary<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>A timeline visually represents the work required to finish our project. It displays the dates that each activity was completed so we can monitor our progress. On the other hand, it may also convey what&#8217;s required to achieve deadlines, helping assignees and stakeholders manage their expectations and assign priorities to projects in the long term. [&hellip;]<\/p>\n","protected":false},"author":87,"featured_media":31551,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[212],"class_list":["post-31548","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-next-js"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Build a Timeline Tracker With Cloudinary, Xata, and Next.js<\/title>\n<meta name=\"description\" content=\"Monitor progress and assign priorities to long-term projects. Build a timeline tracker with\u00a0Cloudinary\u00a0and\u00a0Xata\u00a0in a Next.js application.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Build a Timeline Tracker With Cloudinary, Xata, and Next.js\" \/>\n<meta property=\"og:description\" content=\"Monitor progress and assign priorities to long-term projects. Build a timeline tracker with\u00a0Cloudinary\u00a0and\u00a0Xata\u00a0in a Next.js application.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2023-10-23T14:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-11-27T05:47:55+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_timeline_tracker\/Blog-Hackmamba_Building_a_timeline_tracker.jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"2000\" \/>\n\t<meta property=\"og:image:height\" content=\"1100\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"melindapham\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"Build a Timeline Tracker With Cloudinary, Xata, and Next.js\",\"datePublished\":\"2023-10-23T14:00:00+00:00\",\"dateModified\":\"2025-11-27T05:47:55+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js\"},\"wordCount\":1330,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_timeline_tracker\/Blog-Hackmamba_Building_a_timeline_tracker.jpg?_i=AA\",\"keywords\":[\"Next.js\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2023\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js\",\"url\":\"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js\",\"name\":\"Build a Timeline Tracker With Cloudinary, Xata, and Next.js\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_timeline_tracker\/Blog-Hackmamba_Building_a_timeline_tracker.jpg?_i=AA\",\"datePublished\":\"2023-10-23T14:00:00+00:00\",\"dateModified\":\"2025-11-27T05:47:55+00:00\",\"description\":\"Monitor progress and assign priorities to long-term projects. Build a timeline tracker with\u00a0Cloudinary\u00a0and\u00a0Xata\u00a0in a Next.js application.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_timeline_tracker\/Blog-Hackmamba_Building_a_timeline_tracker.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_timeline_tracker\/Blog-Hackmamba_Building_a_timeline_tracker.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Build a Timeline Tracker With Cloudinary, Xata, and Next.js\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\",\"name\":\"melindapham\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"caption\":\"melindapham\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Build a Timeline Tracker With Cloudinary, Xata, and Next.js","description":"Monitor progress and assign priorities to long-term projects. Build a timeline tracker with\u00a0Cloudinary\u00a0and\u00a0Xata\u00a0in a Next.js application.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js","og_locale":"en_US","og_type":"article","og_title":"Build a Timeline Tracker With Cloudinary, Xata, and Next.js","og_description":"Monitor progress and assign priorities to long-term projects. Build a timeline tracker with\u00a0Cloudinary\u00a0and\u00a0Xata\u00a0in a Next.js application.","og_url":"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js","og_site_name":"Cloudinary Blog","article_published_time":"2023-10-23T14:00:00+00:00","article_modified_time":"2025-11-27T05:47:55+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_timeline_tracker\/Blog-Hackmamba_Building_a_timeline_tracker.jpg?_i=AA","type":"image\/jpeg"}],"author":"melindapham","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"Build a Timeline Tracker With Cloudinary, Xata, and Next.js","datePublished":"2023-10-23T14:00:00+00:00","dateModified":"2025-11-27T05:47:55+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js"},"wordCount":1330,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_timeline_tracker\/Blog-Hackmamba_Building_a_timeline_tracker.jpg?_i=AA","keywords":["Next.js"],"inLanguage":"en-US","copyrightYear":"2023","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js","url":"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js","name":"Build a Timeline Tracker With Cloudinary, Xata, and Next.js","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_timeline_tracker\/Blog-Hackmamba_Building_a_timeline_tracker.jpg?_i=AA","datePublished":"2023-10-23T14:00:00+00:00","dateModified":"2025-11-27T05:47:55+00:00","description":"Monitor progress and assign priorities to long-term projects. Build a timeline tracker with\u00a0Cloudinary\u00a0and\u00a0Xata\u00a0in a Next.js application.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_timeline_tracker\/Blog-Hackmamba_Building_a_timeline_tracker.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_timeline_tracker\/Blog-Hackmamba_Building_a_timeline_tracker.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/build-timeline-tracker-cloudinary-xata-next-js#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Build a Timeline Tracker With Cloudinary, Xata, and Next.js"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9","name":"melindapham","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","caption":"melindapham"}}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1696620194\/Blog-Hackmamba_Building_a_timeline_tracker\/Blog-Hackmamba_Building_a_timeline_tracker.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/31548","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/87"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=31548"}],"version-history":[{"count":8,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/31548\/revisions"}],"predecessor-version":[{"id":39467,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/31548\/revisions\/39467"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/31551"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=31548"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=31548"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=31548"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}