Build a blog profile in Next using Cloudinary and Xata

Ishaq Nasir - Nov 30 '22 - - Dev Community

Prerequisite

  1. Javascript
  2. 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.

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>
      );
    }

Enter fullscreen mode Exit fullscreen mode

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>
      </>
      )
    }

Enter fullscreen mode Exit fullscreen mode

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;  
    }

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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);

Enter fullscreen mode Exit fullscreen mode

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>
      );
    }

Enter fullscreen mode Exit fullscreen mode

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>
      );
    }


Enter fullscreen mode Exit fullscreen mode

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;
    }

Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Building a Backend with Xata

A quick tour of how xata works on the video.

https://www.youtube.com/watch?v=-KNRS2fIWdA&

https://youtu.be/-KNRS2fIWdA

Initializing the xata our project making it auto-generate the code into an src file either in javascript or typescript.

 xata init
Enter fullscreen mode Exit fullscreen mode

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,
    },
  };
};

Enter fullscreen mode Exit fullscreen mode

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.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .