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 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 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.
Netlify
We can access the live demo here.
Prerequisite
Understanding this article requires the following:
- Installation of Node.js
- Basic knowledge of JavaScript
- A Cloudinary account (sign up here)
- Creating a free account with Xata
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:
We can start our Vue application by running the following command:
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. Install it by running the command below in the terminal.
npm i tachyons
Afterward, make it globally available for usage in the project by adding the line below in our src/main.js
:
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. Next, we will go to the user dashboard's database tab to create an appropriate database.
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.
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.
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:
npx xata
After this, we can then initialize Xata for use in our application by running the command below:
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.
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. Creating an account is entirely free.
We will need to install the Cloudinary Vue.js SDK with the command below before configuring it in your Vue app.
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.
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 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:
[
{
"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:
[
{
"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:
<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.
<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
andbasicData
- and added both to thedata
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()
andgenerateItemInfo()
methods that both take in their respective data from the JSON files. These data are reformatted using the JavaScriptmap()
function to create a new array with theotherdata
as the arraykey
and theinputdata
as thevalue
. The result of this formatted array is then copied into an object using the JavaSciptObject.assign()
method. Doing this is crucial for picking the data passed through thev-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:
<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 namedinvoiced.pdf
- Configured the Cloudinary components to accept the
basicDataInfo
anditemInfo
props which we will pass in from theTheDashboardbody
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:
<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.
At this point, the UI for TheDashboardBody.vue
file should look like the below:
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:
<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:
<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:
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:
<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
.
<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
andpassword
, 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:
At this point, our complete application will look like the below:
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.
Top comments (2)
Thanks for sharing, @ IDURAR , we use are using node.js react.js & redux
Here Tutorial about : 🚀 Building and Generate Invoice PDF with React.js , Redux and Node.js : dev.to/idurar/building-an-invoice-...
Your content is really helpful information! totka to get lost love back