🔴 Live now: I am building Post generator with Next.js, PrismaDB, OpenAI
Tags: begginers, nextjs, openai, buildinginpublic
Published: 2023-07-03 16:42:34

This is not just post, it is live post, everyday couple of times a day I share updates how I am building Ultimate Post Generator with help of ChatGPT. 

![Let chatgpt do it](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zdmiff1z0ri8xv7yj3ux.gif)


## Latest updates

- Update 1. Sunday 02.07  23.00 CEST -Custom generation
- Update 2. Monday 03.07  18.00 CEST -Login Page

## Project I am building 
Project I am working on is Open Source LinkedIn Post Generator. I am updating working on it this week. 
https://www.postgenerator.app

Here full story how I build first part of it. https://dev.to/shnai0/how-i-build-my-first-open-source-project-with-chatgpt-and-nextjs-10k-users-in-24-hours-2m7n


Currently it is purely LinkedIn Post Generator wanna turn it into Ultimate post generator and improve it for sign up users. All the setting which need to sign up and see your posts there. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vacmo9fk8u6e4lzfjsm4.png)


## Update 1. Sunday 02.07  23.00 CEST 

I updated the features during weekend and previous week not it is not one feature post generator. 

But I added different types of generation and different prompts:
- Generate posts with custom prompts
- Generate posts via templates 
- Create enhancement for your posts
- Create post ideas



Here is my button component. 
```tsx
import Link from "next/link";

interface CustomButtonProps {
  currentTab: string | number;
}

const CustomButton = ({ currentTab }: CustomButtonProps) => {
  const buttons = [
    {
      link: "/",
      text: "Styles💃",
      tooltip: "Generate posts using styles from top LinkedIn creators.",
      tabName: "vibe",
    },
    {
      link: "/custom",
      text: "Custom 🏗️",
      tooltip: "Use your custom prompt to generate post",
      tabName: "custom",
    },
    {
      link: "/template",
      text: "Template 📋",
      tooltip: "Generate post based on example",
      tabName: "template",
    },
    {
      link: "/enhancer",
      text: "Enhancer 💫",
      tooltip: "Enchance your post, make it shorter, longer, correct gramamr",
      tabName: "enhancer",
    },
    {
      link: "/ideas",
      text: "Ideas💡",
      tooltip: "Generate ideas for your post",
      tabName: "ideas",
    },
  ];

  return (
    <>
      {buttons.map((button, index) => (
        <Link href={button.link} key={index}>
          <div className="relative group">
            <button
              className={`px-3 py-2 rounded-md text-xs font-medium ${
                currentTab === button.tabName
                  ? "bg-gray-300 text-black"
                  : "border border-gray-300 bg-white text-gray-700 shadow-sm hover:bg-gray-50"
              }`}
            >
              {button.text}
            </button>
            <span
              className="tooltip-text text-sm bg-gray-100 text-gray-700 p-1 rounded-md absolute bottom-full mb-2 left-1/2 transform -translate-x-1/2 opacity-0 group-hover:opacity-100 transition duration-300"
              style={{ width: "150px" }}
            >
              {button.tooltip}
            </span>
          </div>
        </Link>
      ))}
    </>
  );
};

export default CustomButton;
```
![Buttons here](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lgdx1ob2n6m0h9ez7c0v.png)


## Update 2. Monday 03.07  18.00 CEST - Login Page

Step 1. I built login page 

Step 2. Next authentication file 

Step 3. Prisma. prisma schema for it

Step 4. Also connected all API. keys, to Prisma DB and next Auth


```tsx 
import { signIn } from "next-auth/react";
import { useRouter } from "next/router";

export default function Login() {
  const router = useRouter();
  const { next } = router.query as { next?: string };

  return (
    <div className="flex h-screen w-screen justify-center">
      <div
        className="absolute inset-x-0 top-10 -z-10 flex transform-gpu justify-center overflow-hidden blur-3xl"
        aria-hidden="true"
      >
        <div
          className="aspect-[1108/632] w-[69.25rem] flex-none bg-gradient-to-r from-[#80caff] to-[#4f46e5] opacity-20"
          style={{
            clipPath:
              "polygon(73.6% 51.7%, 91.7% 11.8%, 100% 46.4%, 97.4% 82.2%, 92.5% 84.9%, 75.7% 64%, 55.3% 47.5%, 46.5% 49.4%, 45% 62.9%, 50.3% 87.2%, 21.3% 64.1%, 0.1% 100%, 5.4% 51.1%, 21.4% 63.9%, 58.9% 0.2%, 73.6% 51.7%)",
          }}
        />
      </div>
      <div className="z-10 mt-[calc(30vh)] h-fit w-full max-w-md overflow-hidden border border-blue-900 rounded-lg sm:shadow-xl">
        <div className="flex flex-col items-center justify-center space-y-3 bg-blue-900 px-4 py-6 pt-8 text-center sm:px-16">
          <h3 className="text-xl text-white font-semibold">
            Sign in to Linkedin Post Generator
          </h3>
          <p className="text-sm text-gray-400">
            Start generating posts with no limitations
          </p>
        </div>
        <div className="flex flex-col bg-blue-900 px-4 py-8 sm:px-16">
          <button
            onClick={() => {
              signIn("google", {
                ...(next && next.length > 0 ? { callbackUrl: next } : {}),
              });
            }}
            className="rounded px-10 py-2 font-medium transition-colors text-gray-900 bg-gray-100 hover:text-gray-100 hover:bg-gray-500"
          >
            Continue with Google
          </button>
        </div>
      </div>
    </div>
  );
}

```
Login Page 

![Login Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ovzdhabwnfr348e3dm55.png)


## Update 3. Tuesday 03.07  18.00 CEST - Generation of posts from templates 

So I spend all day, figuring out how to build the format that way that there the template is taken and from this template the post can be generated for your own topic. 

I messed up with variables in my component. So I needed to change it all.  

```tsx
return (
    <>
      <div className="w-full">
        <textarea
          maxLength={10000}
          onChange={(e) => {
            setInputData(e.target.value);
            processData(e.target.value); // process the text
          }}
          value={selectedTemplate ? selectedTemplate.text : inputData}
          placeholder="Add existing post which you wrote before so it would be clear which format you are writing in and can write the same"
          className="text-black w-full h-32 p-2 text-s bg-white border border-gray-300 rounded-md shadow-inner md:h-240"
        />
        {/* {selectedTemplate && (
          <img src={selectedTemplate.img} alt={selectedTemplate.name} />
        )} */}
      </div>

      <Menu as="div" className="relative block text-left w-full">
        <div>
          <Menu.Button className="inline-flex w-full justify-between items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue">
            {selectedTemplate ? selectedTemplate.name : "Select template"}
            <ChevronUpIcon
              className="-mr-1 ml-2 h-5 w-5 ui-open:hidden"
              aria-hidden="true"
            />
            <ChevronDownIcon
              className="-mr-1 ml-2 h-5 w-5 hidden ui-open:block"
              aria-hidden="true"
            />
          </Menu.Button>
        </div>

        <Transition
          as={Fragment}
          enter="transition ease-out duration-100"
          enterFrom="transform opacity-0 scale-95"
          enterTo="transform opacity-100 scale-100"
          leave="transition ease-in duration-75"
          leaveFrom="transform opacity-100 scale-100"
          leaveTo="transform opacity-0 scale-95"
        >
          <Menu.Items className="absolute left-0 z-10 mt-2 w-full origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
            <div className="">
              {templates.map((template) => (
                <Menu.Item key={template.name}>
                  {({ active }) => (
                    <button
                      onMouseEnter={handleMouseEnter(template)}
                      onMouseLeave={handleMouseLeave}
                      onClick={() => {
                        setInputData(template.text);
                        setSelectedTemplate(template);
                        processData(template.text); // process the text
                      }}
                      className="px-4 py-2 text-sm w-full text-left flex items-center space-x-2 justify-between"
                    >
                      <span>{template.name}</span>
                      {selectedTemplate === template && (
                        <CheckIcon className="w-4 h-4 text-bold" />
                      )}
                    </button>
                  )}
                </Menu.Item>
              ))}
            </div>
          </Menu.Items>
        </Transition>
      </Menu>
      {showImage && previewTemplate && (
        <div className="absolute top-4/6 left-2/3 transform -translate-x-1/3 -translate-y-1/2 w-256 h-256">
          <img
            className="w-full h-full object-scale-down"
            src={previewTemplate.img}
            alt={previewTemplate.name}
          />
        </div>
      )}
    </>
  );
};

```

![Templates part](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kajj9lxcpn5oviiwlc3t.png)




## I am gonna share more updates today. 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3t7nuooqdtakeol1x548.gif)


https://github.com/shnai0/linkedin-post-generator

All contributions are welcome here, it is an open source project:)