Build a blog profile in Next using Cloudinary and Xata
Ishaq Nasir
Posted on November 30, 2022
Prerequisite
- Javascript
- Next | React
Introduction
Building a blog profile using xata as the backend and cloudinary to handle images.
xata is a serverless backend created to connect with your front-end application.
Setting up NextJS
In this section we’ll be building the structure of our front-end application with next js.
N:B at the moment of this article, version 12.2.0 was implemented and there can be slight changes to version 13 which will be out soon.
- Installation of NextJS : Before the installation of next, the article assumes we have the node and npm installed https://nodejs.org/en/download/package-manager/
Npm installation.
https://docs.npmjs.com/cli/v6/commands/npm-install
Install NextJS
https://nextjs.org/learn/basics/create-nextjs-app/setup
Upon the successful installation of these tools, we’d have a structure of
In my app folder, we need to create a component folder, the folder will hold every component of our web app.
We’ll be creating files
- Header.js
- post.js
- uploadwidget.js
In our Header.js
file, we’re going to build a navigation bar and button that handles our authentication upon signing into the app for us to render in the pages/index.js
.
import { useSession, signIn, signOut } from "next-auth/react";
import Link from "next/link";
export default function Header() {
const handleSignin = (e) => {
e.preventDefault();
signIn();
};
const handleSignout = (e) => {
e.preventDefault();
signOut();
};
const { data: session } = useSession();
return (
<div className="header">
<Link href="/">
<a className="logo">FlauntSpace</a>
</Link>
{session && (
<a href="#" onClick={handleSignout} className="btn-signin">
SIGN OUT
</a>
)}
{!session && (
<a href="#" onClick={handleSignin} className="btn-signin">
SIGN IN
</a>
)}
</div>
);
}
We imported the next link and the next-auth for it to handle authentication using third-party (GitHub, google) while we did this, the next auth library gives us the functionality to handle the signing.
Now, we open up our pages/index.js
and clear the default code that makes us thinker
import { useSession } from "next-auth/react";
export default function Home() {
const { data: session, status } = useSession();
const loading = status === "loading";
return (
<>
<div className={styles.container}>
<Head>
<title>FlauntSpace</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Header />
</div>
</>
)
}
We have to style our code to beautify it for a better interface and experience. The next package gives the default styles/global.css
html,
body {
padding: 0;
margin: 0;
background-color: #2d3436;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
a {
color: inherit;
ext-decoration: none;
}
* {
box-sizing: border-box;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
background-color: rgb(110, 90, 90);
color: #fff;
padding: 0 2rem;
width: 100%;
box-shadow: 0 10px 20px rgba(82, 71, 71, 0.19), 0 6px 6px rgba(0,0,0,0.23);
}
.logo {
font-weight: 800;
color: #fff;
font-size: 1.8rem;
font-style: oblique;
}
.btn-signin {
box-sizing: border-box;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-color: #000;;
outline: none;
border: 0;
cursor: pointer;
display: -webkit-box;
display: flex;
align-self: center;
font-size: 1.2rem;
line-height: 1;
margin: 20px;
font-weight: 700;
padding: 1.2rem;
width: 14rem;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-family: inherit;
user-select: none;
color: #000;
background-color: #353430;
}
.btn-signin:hover {
background-color: #353430;
color: #000;
}
We’d use this CSS code snippet.
Moving forward that we create our authentication to go into our app profile, and we’re using GitHub and google authentication to bypass this phase.
To create a GitHub client Id and secret key
https://www.knowband.com/blog/user-manual/get-github-client-id-client-secret-api-details/
We’d do the same to google authentication.
https://developers.google.com/adwords/api/docs/guides/authentication
Now we will be creating our .env
file in our app, this is for us not to reveal our security keys to users
Our .env
file should look like this after following the procedure on
https://next-auth.js.org/providers/github
GOOGLE_ID=<your googleID generated>
GOOGLE_SECRET=<secret key generated by google>
GITHUB_ID=<githubID>
GITHUB_SECRET=<AUTHENTICATION SECRETKEY BY GITHUB USING THE ACTION>
NEXTAUTH_URL=http://localhost:3000
In the pages/api/auth/…nextauth.js
file, import the generated authentication from Google and GitHub for our application to interact with API.
import NextAuth from "next-auth";
import GitHubProvider from "next-auth/providers/github";
import GoogleProvider from "next-auth/providers/google";
const options = {
providers: [
GitHubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
authorization: { params: { scope: "notifications" } },
}),
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
authorization: {
params: {
scope: "notifications",
},
},
}),
],
};
export default (req, res) => NextAuth(req, res, options);
We’ve securely added our authentication and it should allow our app to be displayed when authentication is passed
In our app, we’re to have a display that lets users upload a profile picture and they can also post a mini blog.
Create a post.js
file in our components folder that takes in a text area and post button to allow users to post content
import { useState } from "react";
import styles from "../styles/Home.module.css";
export function Post() {
return (
<form action="api/form" method="post">
<label htmlFor="body-content" className={styles.flauntText}>
Flaunt
</label>
<textarea rows="4" cols="50" className={styles.bodyText}>
Enter text here...
</textarea>
<br />
<button type="submit" className={styles.postbut}>
Post
</button>
</form>
);
}
Also, we’d create uploadWidget.js
in the components.
import { useState } from "react";
import styles from "../styles/Home.module.css";
export function ImageUpload() {
const [isImageUploaded, setIsImageUploaded] = useState(false);
async function handleWidgetClick() {
const widget = window.cloudinary.createUploadWidget(
{
cloudName: "dhpp7gaty",
uploadPreset: "my upload",
resourceType: "image",
},
(error, result) => {
if (!error && result && result.event === "success") {
console.log("Done! Here is the image info: ", result.info);
setIsImageUploaded(true);
} else if (error) {
console.log(error);
}
}
);
widget.open();
}
return (
<div className={styles.container}>
<div className={styles.vertical}>
<button
className={styles.button}
type="button"
onClick={handleWidgetClick}
>
Upload image
</button>
</div>
{isImageUploaded ? (
<>
<div>Successfully uploaded</div>
</>
) : null}
</div>
);
}
We have our upload button is working using cloudinary upload widget.
https://cloudinary.com/documentation/upload_widget and now we’ll be styling our app.
Let’s create Home.module.css
file in the styles folder and we should use this snippet.
@import url('https://fonts.googleapis.com/css2?family=Island+Moments&display=swap');
.container {
min-height: 10vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.main {
}
.title {
border-radius: 50%
}
.profile {
grid-column: 2;
grid-row: 2;
background-color: #eef6f8;
}
.content {
display: grid;
grid-column: 1/3;
grid-row: 1;
}
.uploadsec {
grid-column: 1;
grid-row: 2;
background-color: #ccc;
}
.profileimg {
border-radius: 50%;
}
.avatar {
flex: 1;
flex-basis: 250px;
}
.details {
flex: 2;
}
.button {
background-color: #449a79; /* Green */
border-radius: 10px;
border: none;
color: white;
padding: 15px 32px;
text-align: center;
cursor: pointer;
text-decoration: none;
display: inline-block;
font-size: 16px;
}
.bodyText {
width: 100%;
height: 150px;
padding: 12px 20px;
box-sizing: border-box;
border: 2px solid #ccc;
border-radius: 4px;
background-color: #f8f8f8;
resize: none;
}
.post {
align-items: center;
justify-content: center;
display: flex;
}
.flauntText {
font-family: 'Island Moments', cursive;
font-weight: bold;
font-size: 40px;
}
.postbut {
background-color: #449a79; /* Green */
border-radius: 10px;
border: none;
color: white;
padding: 15px 30px;
text-align: center;
cursor: pointer;
text-decoration: none;
display: inline-block;
font-size: 16px;
}
.imageCloud {
/*border: 5px solid #555;*/
border-radius: 50%;
display: inline-block;
justify-content: center;
align-items: center;
width: 100px;
border: 3px solid black;
}
Now we have to render all those components on pages/index.js
file
import Head from "next/head";
import Header from "../components/Header";
import styles from "../styles/Home.module.css";
import { useSession } from "next-auth/react";
import { ImageUpload } from "../components/uploadWidget";
import { Post } from "../components/post";
import { getXataClient, XataClient } from "../src/xata";
export default function Home() {
const { data: session, status } = useSession();
const loading = status === "loading";
// const [url, updateUrl] = useState();
// const [error, updateError] = useState();
return (
<>
<div className={styles.container}>
<Head>
<title>FlauntSpace</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Header />
</div>
<main className={styles.main}>
<div className={styles.user}>
{loading && <div className={styles.title}>Loading...</div>}{" "}
{/*authentication process */}
{/**Renders the home page after authentication passed of third-party */}
{session && (
<div className={styles.content}>
<div className={styles.profile}>
<>
<p>
You're logged in as
{/**Get's the name and mail from the third-party nextauth and display it as the user name on the home page */}
{session.user.name ?? session.user.email}
</p>
{/**SPLIT spage to two section where one is for the upload preset and the other is for posting blog post */}
<form className={styles.post}>
<Post />
</form>
</>
</div>
<div className={styles.uploadsec}>
<img
src="https://res.cloudinary.com/dhpp7gaty/image/upload/v1667560004/Profile-uploads/jnbkgndxzlnhapxv8eed.jpg"
alt="upload here"
className={styles.imageCloud}
/>
<ImageUpload />
</div>
</div>
)}
</div>
</main>
</>
);
}
Building a Backend with Xata
A quick tour of how xata works on the video.
https://www.youtube.com/watch?v=-KNRS2fIWdA&
Initializing the xata our project making it auto-generate the code into an src
file either in javascript or typescript.
xata init
Now,let's import xata into our pages/index.js
import Head from "next/head";
import Header from "../components/Header";
import styles from "../styles/Home.module.css";
import { useSession } from "next-auth/react";
import { ImageUpload } from "../components/uploadWidget";
import { Post } from "../components/post";
import { getXataClient, XataClient } from "../src/xata";
export default function Home() {
const { data: session, status } = useSession();
const loading = status === "loading";
return (
<>
<div className={styles.container}>
<Head>
<title>FlauntSpace</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Header />
</div>
<main className={styles.main}>
<div className={styles.user}>
{loading && <div className={styles.title}>Loading...</div>}{" "}
{/*authentication process */}
{/**Renders the home page after authentication passed of third-party */}
{session && (
<div className={styles.content}>
<div className={styles.profile}>
<>
<p>
You're logged in as
{/**Get's the name and mail from the third-party nextauth and display it as the user name on the home page */}
{session.user.name ?? session.user.email}
</p>
{/**SPLIT spage to two section where one is for the upload preset and the other is for posting blog post */}
<form className={styles.post}>
<Post />
</form>
</>
</div>
<div className={styles.uploadsec}>
<img
src="https://res.cloudinary.com/dhpp7gaty/image/upload/v1667560004/Profile-uploads/jnbkgndxzlnhapxv8eed.jpg"
alt="upload here"
className={styles.imageCloud}
/>
<ImageUpload />
</div>
</div>
)}
</div>
</main>
</>
);
}
{
/**Connecting the xata to our app */
}
const xata = new XataClient();
export const getServerSideProps = async () => {
const FlauntSpace = await xata.db.items.getAll();
return {
props: {
FlauntSpace,
},
};
};
We successfully connected our database of xata to the front-end application and more functionality is querying data from our database to render it on the front-end.
N:B This article intend to add more functionality and would be edited as soon as the changes are made.
Posted on November 30, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.