Build a concert tickets generator with NextJS using Xata and Cloudinary
Tags: jamstack, nextjs, xata, javascript
Published: 2022-11-30 02:08:03

As events evolve in the digital landscape, we might have seen some fun, engaging, and personalized event tickets shared on social media. This innovation is fascinating and showcases just how many excellent low-cost services and capabilities exist in web development.

This post will discuss building concert tickets using [Cloudinary](https://cloudinary.com?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog), [Xata](https://xata.io/docs/quickstart/index?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog), and [Next.js](https://nextjs.org?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog). At the end of this tutorial, we will learn how to store images on Cloudinary and explore the rich capabilities of Xata, a serverless database.

Check here for the source code on [GitHub](https://github.com/Tundesamson26/xata-concert-ticket?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) and the demo [here](https://xata-concert-ticket.vercel.app/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog).

## Prerequisites

The knowledge of the following is required:

- Knowledge of JavaScript and React.js
- Node.js and its package manager, npm. We run the command `node -v` && `npm -v` to verify we have them installed or install them from [here](https://nodejs.org/en/)
- [Signup](https://cloudinary.com/users/register/free?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) for a free Cloudinary account
- A [Xata account](https://xata.io/signin?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)
- Understanding Next.js would help us quickly follow this tutorial

## Creating a Next.js application

To create the Next.js app, we go to our terminal or command line. Using the `cd` command, we navigate to the directory we want our to create our app.

```javascript
cd <directory-name>
```

Once inside the directory, we create a new project using the command:

```javascript
npx create-next-app
    
# or
    
yarn create next-app
```
Once that's finished, we navigate into that directory and start a hot-reloading development server for the project on http://localhost:3000 with:

```javascript
npm run dev
#or
yarn dev
```

## Installing Cloudinary

[Cloudinary](https://cloudinary.com/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) provides a robust solution to store, transform, optimize and deliver images and videos in software applications.

Install the _[cloudinary-react](https://www.npmjs.com/package/cloudinary-react?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)_ package that exposes various media delivery and transformation functions using the command line.

```javascript
npm i cloudinary-react lodash
```
> Lodash is a dependency of the Cloudinary package.

## Installing TailwindCSS

[TailwindCSS](https://tailwindcss.com/) is a utility-first CSS framework packed with classes to help us style web pages.

We install `tailwindcss` and its peer dependencies via npm, which generates `tailwind.config.js` and `postcss.config.js`.

```javascript
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```
We need to add the paths to all of our template files in the tailwind.config.js file.

```javascript
//tailwind.config.js
module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
```
We should add the `@tailwind` directives for Tailwind’s layers to our `./styles/globals.css` file.

```javascript
//globals.css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

## Setting up the Xata database

Let’s create a new database on our Xata dashboard called `concert-ticket`

![xata-database](https://paper-attachments.dropboxusercontent.com/s_4824FE67598837A0BC429306980E407942ABEE36289E86ACB837CF605093866F_1668751532368_image.png)


Click on the created database and add a table titled `concert`. Next, add  `concert_name` and `artist` column of type String to the table.

Our database should look like this:

![xata-schema](https://paper-attachments.dropboxusercontent.com/s_A2D89DDB2504F69A8AE8579AD7557EB193C327134A8C90B862CA5796569697BC_1668840861046_image.png)

## Setting up the Xata instance

To set up Xata, we'll need to install the CLI globally:

```javascript
npm install @xata.io/cli -g
```

Then, authorize Xata to log us in:

```javascript
xata auth login
```

Next, we select `Create a new API key in the browser` from the prompts in our terminal. This opens our browser, where we can type in any name we choose. Once successful, we will get a display page indicating that we are all set.

Now, let’s `cd` into the Nextjs project created earlier and run `xata init` to initialize Xata in our project;

```javascript
cd <directory-name>
xata init
```
This is where we select the name of the database created earlier, then select `Generate Javascript code with ES modules code` and choose our output source as `src/xata.js` which is where the Xata codegen will be generated.

## Image Upload to Cloudinary

In our dashboard, we will have access to upload and save the downloaded images in Cloudinary by clicking the `Media Library` tab and Upload button, as displayed below.  

![cloudinary-media](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lva94ms95xq6wfzsvenq.png)

Next, we need to copy the `publicId` for the saved images from our dashboard, which will be necessary to generate the article banner.

![cloudinary dasshboard](https://paper-attachments.dropboxusercontent.com/s_B16FE34C17EE10BD1482C31292E24665F4B857CB461D2AD898B5D063D9BB95A3_1649782968470_image.png)

## Setting up the image collection

We’ll create a `utils` folder in the project root directory, where we need to create a `ticket.json` file to hold to data of the uploaded images.

Here is the JSON data for the `ticket.json` file.

```javascript
[
    {
        "id": 1,
        "publicId": "photo-1649894221695-45abb718a190_sgjhwd.jpg"
    },
    {
        "id": 2,
        "publicId": "photo-1649894222226-056a1a79d9fb_xlv73h.jpg"
    },
    {
        "id": 3,
        "publicId": "photo-1648800475313-2bb7fbec8701_ae60yw.jpg"
    },
    {
        "id": 4,
        "publicId": "photo-1647067867267-e01d98462f3c_ugtnwe.jpg"
    },
    {
        "id": 5,
        "publicId": "photo-1644241687200-eadaf7601290_xcz2kh.jpg"
    }
]
```

## Creating the concert ticket

With our project fully set up and configured, we can start building the concert ticket.

First, we need to modify the `index.js` file in the `pages`  folder to the gist below:

{% embed https://gist.github.com/Tundesamson26/52ec30218b5291b020a0d99b0517aa4f %}

From the code snippet in the gist, we:

- Import required dependencies and image collections
- Create state variables to manage selected Image ID, form data, and display the Concert ticket
- A `handleChange()` function to control form inputs
- Markups for form elements and conditionally render the list of Images using `cloudinary-react`. The list of images also has an `onClick()` function that sets the current image `id` to show the active image, the form object, and the `showCard` property

By now, the application should look like this:

![concert generator](https://paper-attachments.dropboxusercontent.com/s_B16FE34C17EE10BD1482C31292E24665F4B857CB461D2AD898B5D063D9BB95A3_1650395424443_image.png)

Next, we create a `components` folder in the root directory, and create a `Concert.js` file with the following snippet:

```javascript
import { CloudinaryContext, Transformation, Image } from "cloudinary-react";

export const Concert = ({ message, name, publicId }) => {
  return (
    <div>
      <CloudinaryContext cloudName="beswift">
        <Image publicId={publicId} alt="image" width={1000} ref={ref}>
          <Transformation crop="fit" effect="blur:100" />
          <Transformation effect="brightness_hsb:-50" />
          <Transformation
            color="#FFFFFF"
            overlay={{
              background: "",
              fontFamily: "Neucha",
              fontSize: 150,
              fontWeight: "bold",
              text: message,
              textAlign: "center",
            }}
            width="1300"
            crop="fit"
          />
          <Transformation flags="layer_apply" />
          <Transformation
            color="#FFFFFF"
            overlay={{
              fontFamily: "Dancing Script",
              fontSize: 50,
              fontWeight: "bold",
              text: `Artist: ${name}`,
            }}
          />
          <Transformation
            flags="layer_apply"
            gravity="center"
            x="450"
            y="350"
          />
        </Image>
      </CloudinaryContext>
     
    </div>
  );
};
```
The snippet above does the following:

- Imports the required dependencies
- The `Concert` component accepts a `message`, `name`, and `publicId` props
- Configure `CloudinaryContext`, `Image`, and `Transformation` to render the image, message, and name
- We also leverage Cloudinary to apply multiple transformations on the image. We added the following transformations, cropping, blurring, brightening, and adding overlays for text, text position, text properties, and flags to alter the position of the text

Then, we update `index.js` to conditionally render the `Concert.js` component as shown below:

```javascript 
//imports here
import { Concert } from '../components/Concert'; //add

export default function Home() {
  //states here

  const handleChange = (e) => {
    //handle change codes here
  };
  
  //add
  const handleSubmit = (e) => {
    e.preventDefault();
    if (imageId) {
      setShowCard(true);
    } else {
      setFormData({ ...formData, error: true });
    }
  };

  return (
    <div className='p-10'>
      {/* Head JSX comes here */}
      <main className=''>
        <h1 className='text-3xl'>Concert ticket</h1>
        <header className='flex border-b-2 mt-7 mb-7'>
          {/* Header contents JSX comes here */}
        </header>
        <form onSubmit={handleSubmit} className='lg:w-2/5'> {/* add handleSubmit */}
          {/* Form contents JSX comes here */}
        </form>

        {/* Conditionally render card Components */}
        {showCard && (
          <div className='mt-10'>
            <Concert
              message={formData.message}
              name={formData.name}
              publicId={formData.publicId}
            />
          </div>
        )}
      </main>
    </div>
  );
}
```
In the snippet above, we:

-  Imported the `Concert` component
- Created an `handleSubmit` function that checks if an image is selected and then conditionally renders the `Concert` component with necessary `props`

## Storing data in the database

Let’s create a new file in the `api` folder and name it `add-concert.js`. Paste the code below in the file:

```javascript
import { getXataClient } from "../../src/xata";
const handler = async (req, res) => {
  const xata = await getXataClient();
  const { concert_name, artist } =
    req.body;
  await xata.db.concert.create({
    concert_name,
    artist,
  });
  res.end();
};
export default handler;
```
We get the data sent to the API and save it to the Xata database.

Finally, we test our application by starting the development server and creating different event tickets.

![xata-cloudinary concert ticket app](https://videoapi-muybridge.vimeocdn.com/animated-thumbnails/image/49b47ae8-4f86-43a9-bcda-3bca80fdd7e9.gif?ClientID=vimeo-core-prod&Date=1650398328&Signature=ee003399f0c328ff99ee52ce6ddcd298be99fb08)


We can right-click to copy the URL of the generated image.

## Conclusion

This post discussed building a concert tickets generator using Cloudinary’s image transformation and saving data in the Xata database. Explore adding more text information to the image and a copy-to-clipboard function for the image URL.

## Resources

We may find these resources helpful:

- [Cloudinary Image Transformation](https://cloudinary.com/documentation/image_transformations/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)
- [Xata documentation](https://xata.io/docs/quickstart/index?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)
- [TailwindCSS](https://tailwindcss.com/?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)