Building an Invoice Generator using Cloudinary and Xata
Tags: vue, xata, cloudinary, javascript
Published: 2022-11-24 15:53:48

Invoices are documents tended by a service provider to the recipient of the service. Crafting a detailed invoice regarding the services rendered, the price, and all the needed details are fast becoming a vital skill of business owners as the ease of conducting transactions also determines the scope of future business encounters.

This article will discuss creating an invoice generator in Vue using Cloudinary and Xata.

[Cloudinary](https://cloudinary.com/signup?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) is a cloud-based video and image management platform that offers services for managing and transforming uploaded assets for usage on the web.

Xata is a [Serverless Data platform](https://xata.io/docs/intro/serverless-data-platform?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog) that simplifies how developers work with data by providing the power of a traditional database with the usability of a SaaS spreadsheet app.


## GitHub

Check out the complete source code [here](https://github.com/MoeRayo/invoice-generator).


## Netlify

We can access the live demo [here](https://remarkable-speculoos-cc817a.netlify.app/).

## Prerequisite

Understanding this article requires the following:

- Installation of Node.js
- Basic knowledge of JavaScript
- A Cloudinary account (sign up [here](https://cloudinary.com/signup?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog))
- Creating a free account with [Xata](https://xata.io?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)


## Creating a Vue app

Using the `vue create <project-name>` command, we will create a new Vue app.
The process of scaffolding the project would provide a list of options, which could look like this:

![Vue application scaffolding options](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0d9i4gtwfrgd6o2g1mn.png)

We can start our Vue application by running the following command:

```javascript
cd <project name>
npm run serve
```
Vue will, in turn, start a hot-reloading development environment that is accessible by default at `http://localhost:8080`.

## Styling

The CSS framework to be used in this project is [Tachyons CSS](https://tachyons.io/). Install it by running the command below in the terminal.

```javascript
npm i tachyons
```
Afterward, make it globally available for usage in the project by adding the line below in our `src/main.js`:

```javascript
import 'tachyons/css/tachyons.css';
```

## Creating a Xata Database
To create a database on Xata, we must either log into our account or create a new [account](https://xata.io?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog). Next, we will go to the user dashboard's database tab to create an appropriate database.


![Database creation on Xata](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5k5yo2zbau5kdqo6jnq8.png)

We will create a new table within the database and add the appropriate records/fields that we want the table to have. In this case, we created a `user` table with three fields: `email`, `password`, and `username`.
These fields signify the data we will be tracking in our application and saving to the database.

![Table creation on Xata](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6gkkgdnaqgpaytuenh74.png)

## Setting up Xata in our Vue application

**Installing Xata CLI**
We will first need to install Xata CLI on our machine. To do this, we will open the command line terminal as an administrator and run the command below.

```javascript
npm install -g @xata.io/cli@latest
```
### Installing Xata in our project
To use Xata in our project, we will install the Xata software development kit (SDK) from the command line like so:

```javascript
npx xata
```
After this, we can then initialize Xata for use in our application by running the command below:

```javascript
xata init
```
Xata will present us varieties of options from which we can choose. In the end, Xata will generate some files for usage, among which we will have the `.xatrc` and `.env` files.
We will need to edit the name of the Xata API key in our Vue app to the one below to allow the Vue application to pick up the environment variable.


```javascript  
VUE_APP_XATA_API_KEY="add your api key"
```
## Cloudinary Setup

For our application's asset management, if you haven't already, you need to create an account with Cloudinary by clicking [**here**](https://cloudinary.com/). Creating an account is entirely free.
We will need to install the [Cloudinary Vue.js SDK](https://cloudinary.com/documentation/vue_integration) with the command below before configuring it in your Vue app.

 ```javascript  
npm install cloudinary-vue
```
We will need to configure the installed SDK in our Vue app by navigating to the `main.js` and adding the following to the file.

```javascript
import Cloudinary from "cloudinary-vue";

Vue.use(Cloudinary, {
  configuration: { 
    cloudName: "XXX," //add the cloudinary cloudname here,
    secure: true }
});
```
We will upload to Cloudinary the invoicing template we want to use in our application. In this case, we used this Invoicing [template](https://www.canva.com/design/DAFSOS4btpE/P6cikRvQMNawL0aAi7jlSg/view?utm_content=DAFSOS4btpE&utm_campaign=designshare&utm_medium=link2&utm_source=sharebutton) from Canva.


## Creating the invoice dashboard

The invoice dashboard is where the bulk of the work happens, as this is where we will fill in the data we will add to the invoice and generate the invoice. We will start by creating a file called `components/TheDashboardBody.vue`, and add a form that allows the user to add the inputs they will show on their invoice.

### Creating the basic input data file
Our dashboard page will have a form where users can add the information they want to display on their invoices. While we can manually add all the input fields, we also would like to use a different approach to minimize the number of input fields we will have to add to our file. 
To do this, we will create a JSON file called `util/basic-form-data.json` and add all the data for the basic input fields we want, like so:

```javascript
[
  {
    "id": 1,
    "inputdata": "",
    "formdata": "Invoice From",
    "type": "text",
    "otherdata": "invoicefrom"
  },
  {
    "id": 2,
    "inputdata": "",
    "formdata": "Invoice To",
    "type": "text",
    "otherdata": "invoiceto"
  },
]
```
### Creating the items input data file
The invoice we are building will also have a section catering to the items or services we rendered. As a result, we will add a JSON file called `items-form-data.json` and add the things we want. Like so:

```javascript
[
  {
    "id": 11,
    "inputdata": "",
    "formdata": "Item One",
    "type": "text",
    "otherdata": "itemone"
  },
  {
    "id": 12,
    "inputdata": "",
    "formdata": "Item Two",
    "type": "text",
    "otherdata": "itemtwo"
  },
]
```
### Creating the dashboard input component
We will create an input component with props from the JSON files we created, which will help us create an input field that we will loop over later. To do this, we will create a  `components/TheInputTemplate.vue` file and add the code below:


 ```javascript  
<template>
  <div>
    <label for="otherData" class="db mb3 black-70 ttu fw7">{{formData}}</label>
    <input
      :value="inputData"
      :type="type"
      id="otherData"
      name="otherData"
      class="db mb3 w-90 br2 pa2 ba bw1 b--black bg-black-05"
      :placeholder="formData"
      @input="$emit('input', $event.target.value)"
    >
  </div>
</template>

<script>
export default {
  props: ["inputData", "formData", "type", "otherData"]
}
</script>
```
If we match the code above with the items we have in the JSON files, we can see each JSON data's use cases.

### Creating the dashboard body
Now, we will pull it together to create a dashboard body by creating a file named `components/TheDashboardBody.vue` and adding the code below.

```javascript  
<template>
  <div class="flex absolute vh-auto top-4 left-2 right-2 w-90 center mw9">
    <div class="mr3 bg-white w-70 br3 pa3">
      <form @submit.prevent="generateInvoice" class="flex justify-between flex-wrap">
        <div v-for="basicinfo in basicData" :key="basicinfo.id" class="w-50-l w-100">
          <TheDashboardInput v-model="basicinfo.inputdata" :formData="basicinfo.formdata" :type="basicinfo.type" :otherData="basicinfo.otherdata"/>
        </div>
        
        <h3 class="dark-blue w-100 f3 underline">ITEMS</h3>
        <div v-for="item in itemsData" :key="item.id" class="w-50-l w-100">
          <TheDashboardInput v-model="item.inputdata" :formData="item.formdata" :type="item.type" :otherData="item.otherdata"/>
        </div>
    
        <div class="w-100">
          <button type="submit" class="f6 ttu tracked black-80 bg-black pa3 br3 white bb link b--black hover-white hover-bg-dark-blue bg-animate pointer">
            Create Invoice
          </button>
        </div>
      </form>
    </div>
    <div class="bg-white w-30 br3 pa3">
      <iframe :src="iframeSRC ? iframeSRC : initialSRC" width="100%" height="100%" />
    </div>
  </div>
</template>
  
<script>
import TheDashboardInput from "@/components/TheDashboardInput.vue";
import itemsData from "../../util/items-form-data.json";
import basicData from "../../util/basic-form-data.json"
export default {
  components: {
  TheDashboardInput
  },
  data: ()=>({
  basicData,
  itemsData,
  basicDataInfo: {},
  itemInfo: {},
  iframeSRC: '',
  initialSRC: 'https://res.cloudinary.com/moerayo/image/upload/v1668849124/invoiced.pdf',
  }),
  methods: {
    generateBasicDataInfo() {
      let newBasicData = basicData.map(({otherdata,inputdata })=> ({[otherdata]:inputdata}))
      this.basicDataInfo = Object.assign({}, ...newBasicData)
    },
    generateItemInfo(){
      let newItemData = itemsData.map(({otherdata,inputdata })=> ({[otherdata]:inputdata}))
      this.itemInfo = Object.assign({}, ...newItemData)
    }
  }
}
</script>
```
From the code block above, we achieved the following:

- We imported the two JSON files we created - `itemsData` and `basicData` - and added both to the `data` options of the Vue script tag to use later in our code
- Looped through each of the JSON data we instantiated in the data options using the Vue `v-for` directive
- Imported the `TheDashboardInput` component we created and passed in the suitable data to fill the props option
- Added an iframe that shows the current invoice template and the yet to be generated filled invoice template
- Created a `generateBasicDataInfo()` and `generateItemInfo()` methods that both take in their respective data from the JSON files. These data are reformatted using the JavaScript `map()` function to create a new array with the `otherdata` as the array `key` and the `inputdata` as the `value`. The result of this formatted array is then copied into an object using the JavaScipt `Object.assign()` method. Doing this is crucial for picking the data passed through the `v-model`


## Creating the invoice template

To generate our invoice template - i.e., what we will download and send to the client - we will use Cloudinary's powerful image and text transformation features. These features will enable us to overlay the invoice template with texts. To do this, we will create a file called `components/TheInvoiceTemplate.vue` and add the code below like so:

```javascript  
<template>
  <div>
    <cld-image publicId="invoiced.pdf" ref="ref" >
      <cld-transformation flags="rasterize" />
      <cld-transformation :overlay="{fontFamily: 'Poppins', fontSize: 60, text: basicDataInfo.invoicefrom,  fontWeight: 'bold'}" color="#023982" gravity="west" x="130" y="-670"/>
      <cld-transformation :overlay="{fontFamily: 'Poppins', fontSize: 45, text: basicDataInfo.invoiceto}" color="#023982" gravity="west" x="130" y="-480"/>
      <cld-transformation :overlay="{fontFamily: 'Poppins', fontSize: 20, text: basicDataInfo.date}" color="#000" gravity="west" x="560" y="-525"/>
      <cld-transformation :overlay="{fontFamily: 'Poppins', fontSize: 20, text: basicDataInfo.invoicenumber}" color="#000" gravity="west" x="610" y="-465"/>
      <cld-transformation :overlay="{fontFamily: 'Poppins', fontSize: 43, text: basicDataInfo.total}" color="#023982" gravity="west" x="990" y="-475"/>
      <cld-transformation :overlay="{fontFamily: 'Poppins', fontSize: 20, text: basicDataInfo.bankname}" color="#5b4f47" gravity="west" x="265" y="255"/>
      <cld-transformation :overlay="{fontFamily: 'Poppins', fontSize: 20, text: basicDataInfo.accountnumber}" color="#5b4f47" gravity="west" x="265" y="285"/>
      <cld-transformation :overlay="{fontFamily: 'Poppins', fontSize: 30, text: basicDataInfo.subtotal}" color="#000" gravity="west" x="1000" y="180"/>
      <cld-transformation :overlay="{fontFamily: 'Poppins', fontSize: 30, text: basicDataInfo.tax}" color="#000" gravity="west" x="1000" y="230"/>
      <cld-transformation :overlay="{fontFamily: 'Poppins', fontSize: 43, text: basicDataInfo.total}" color="#000" gravity="west" x="1000" y="300"/>
      <cld-transformation :overlay="{fontFamily: 'Poppins', fontSize: 35, text: itemInfo.itemone}" color="#000" gravity="west" x="155" y="-248"/>
      <cld-transformation :overlay="{fontFamily: 'Poppins', fontSize: 35, text: itemInfo.itemtwo}" color="#000" gravity="west" x="155" y="-180"/>
      <cld-transformation :overlay="{fontFamily: 'Poppins', fontSize: 35, text: itemInfo.itemthree}" color="#000" gravity="west" x="155" y="-80"/>
      <cld-transformation :overlay="{fontFamily: 'Poppins', fontSize: 35, text: itemInfo.itemfour}" color="#000" gravity="west" x="155" y="-1"/>
    </cld-image>  
  </div>
</template>
<script>
export default {
  name: "TheResumeTemplate",
  props: {
    basicDataInfo: {
      type: Object
    },
    itemInfo: {
      type: Object
    }
  }
}
</script>
```
From the above, we achieved the following:

- Cloudinary provides us with several templates to use in our Vue app. One of which is the `<cld-image />`  and the `<cld-transformation />` components we will use to generate our invoice
- Passed in the `publicId` of the Cloudinary file we will be using, which is named `invoiced.pdf`
- Configured the Cloudinary components to accept the `basicDataInfo` and `itemInfo` props which we will pass in from the `TheDashboardbody` component.
- Cloudinary components support asset transformations, which we leveraged by adding text transformations like the font family, colors, font sizes, and the specific places we want the texts to be placed on the invoice.

To render this template, we will need to import it in the `TheDashboadBody` component like so:

 ```javascript  

<template>
<div class="dn" v-if="showTemplate">
    <TheInvoiceTemplate :basicDataInfo="basicDataInfo" :itemInfo="itemInfo" ref="ref" />
  </div>
</template>

<script>
import TheInvoiceTemplate from "@/components/TheInvoiceTemplate.vue";
export default {
  components: {
  TheInvoiceTemplate,
  },
  data: ()=>({
  showTemplate: false,
  })
}
</script>
```
We can find the complete code for the `TheDashboardBody.vue` in this GitHub [gist](https://gist.github.com/MoeRayo/a6bfefbbb94627a8ac262385f22c7587).
At this point, the UI for `TheDashboardBody.vue` file should look like the below:

![The dashboard body interface](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0b3zsh2rqzlwcrijgu7h.png)

## Creating the Signup Page

We will need a user to sign up for our application to create an invoice. To do this, we will create a file named `views/SignupView.vue` and add the following code to it:

 ```javascript  

<template>
  <div class="bg-lightest-blue vh-100">
    <div class="pv5 ph2">
      <form class="ba b--dark-blue bw3 bg-white br2 mw6 w-40-m w-70 w-20-l center pa3 shadow-5" @submit.prevent="signUp">
        <h2 class="ttc tc">
          Sign up
        </h2>
        <label for="name" class="db mb2 black-70">Name</label>
        <input
          id="name"
          v-model="username"
          name="name"
          type="text"
          class="db mb3 w-100 br2 ph2 pv3 ba bw1 b--lightest-blue"
          placeholder="John Doe"
        >
        <label for="email" class="db mb2 black-70">Email</label>
        <input
          id="email"
          v-model="email"
          name="email"
          type="email"
          class="db mb3 w-100 br2 ph2 pv3 ba bw1 b--lightest-blue"
          placeholder="example@email.com"
        >
        <label for="password" class="db mb2 black-70">Password</label>
        <input
          id="password"
          v-model="password"
          name="password"
          type="password"
          class="db mb3 w-100 br2 ph2 pv3 ba bw1 b--lightest-blue"
          placeholder="••••••••"
        >
        <button type="submit" class="center db pa3 mb3 tracked bg-dark-blue ba br3 white pointer hover-black hover-bg-lightest-blue bg-animate pointer">
          Sign up
        </button>
        <p>Already have an account? <a href="/signin" class="black-70 b">Sign in</a> </p>
      </form>
    </div>
  </div>
</template>
```
By this, we have successfully created an interface for our Signup page. Now we will add the functionality for authentication and authorization. 

We will use Xata to accomplish our authentication goal. To do this, we will add this to the `SignupView.view` file:

```javascript  
<script>
import { getXataClient } from '@/xata'
export default {
  name: 'signup',
  data: () => ({
    username: '',
    email: '',
    password: '',
  }),
  methods: {
    async signUp() {
      const xata = getXataClient()
      const user = await xata.db.users.filter('username', this.username).getFirst()
      if (!user) {
        await xata.db.users.create({
          username: this.username,
          password: this.password,
          email: this.email
        }).then((res) => {
          this.$router.push({path:`/dashboard/${res.username}`, params: user})
        })
        this.$notify({type: 'success', text: "Account creation successful!" })
      }
    }
  }
}
</script>
```
From the code block above, we achieved the following:

- Imported the Xata client 
- Checked if a user exists and created a new user if the current user does not exist
- At successful account creation, the user's view gets changed to the dashboard from where they can generate an invoice.

At this point, our application should look like the below:


![signup interface](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4fw19lk50ixkbhwr6dde.gif)

## Creating the SignIn page

We also need to create options to allow signing in for people who have already created an account. To do this, we will create a file called `SigninView.vue` and add the code below:

 ```javascript  

<template>
  <div class="bg-lightest-blue vh-100">
    <div class="pv5 ph2">
      <form class="ba b--dark-blue bw3 bg-white br2 mw6 w-40-m w-70 w-20-l center pa3 shadow-5" @submit.prevent="signIn">
        <h2 class="ttc tc">
          Sign In
        </h2>
        <label for="email" class="db mb2 black-70">Email</label>
        <input
          id="email"
          v-model="email"
          name="email"
          type="email"
          class="db mb3 w-100 br2 ph2 pv3 ba bw1  b--lightest-blue"
          placeholder="example@email.com"
        >
        <label for="password" class="db mb2 black-70">Password</label>
        <input
          id="password"
          v-model="password"
          name="password"
          type="password"
          class="db mb3 w-100 br2 ph2 pv3 ba bw1  b--lightest-blue"
          placeholder="••••••••"
        >
        <button type="submit" class="center db pa3 mb3 tracked bg-dark-blue ba br3 white pointer hover-black hover-bg-lightest-blue bg-animate pointer">
          Sign in
        </button>
      </form>
    </div>
  </div>
</template>
```
The above creates a sign-in interface for us. But we will need to wrap some functionalities around it to make it work as expected. 
To do this, we will add the code below in the `SigninView.vue`. 

 ```javascript  

<script>
import { getXataClient } from '@/xata'
export default {
  data: () => ({
    email: '',
    password: ''
  }),
  methods: {
    async signIn() {
      const xata = getXataClient()
      const user = await xata.db.users.filter('email', this.email).getFirst()
      if (!this.email || !this.password ){
        this.$notify({type: 'error', text: "Please fill all empty fields"})
      } else if (this.email !== user.email || this.password !== user.password){
        this.$notify({type: 'error', text: "Incorrect credentials"})
        this.email = '';
        this.password = '';
      } else {
        this.$router.push({path:`/dashboard/${user.username}`, params: user})
        this.$notify({type: 'success', text: "Login successful!"})
      }
    }
  }
}
</script>
```
From the above, we created a function called `signIn` function that does the following:

- Imported the Xata client 
- Checks if the user passes an `email` and `password`, then returns the appropriate error if it is unavailable
- Cross checks the email and password entered to be sure it matches the email and password from the database
- If the checks pass, the page gets routed to the dashboard, where the user can generate n invoice

At this point, our interface will look like this:

![Signin interface](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uet4wtw2uhgfvej4u4va.gif)

At this point, our complete application will look like the below:

![Complete application interface](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ksx870hm2cmlq2yb3pfm.gif)

## Conclusion

This article discusses generating an invoice using Cloudinary and Xata. Cloudinary was used for asset management and text transformation, while we used Xata for authentication.

## Resources
- [Xata.io documentation](https://xata.io/docs/overview?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)
- [Cloudinary transformation reference](https://cloudinary.com/documentation/transformation_reference?utm_source=hackmamba&utm_campaign=hackmamba-hackathon&utm_medium=hackmamba-blog)