DEV Community

Cover image for πŸ”΄ Live now: I am building Post generator with Next.js, PrismaDB, OpenAI
Iuliia Shnai
Iuliia Shnai

Posted on • Edited on

πŸ”΄ Live now: I am building Post generator with Next.js, PrismaDB, OpenAI

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

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

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.

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

Buttons here

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

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

Enter fullscreen mode Exit fullscreen mode

Login Page

Login Page

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.

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

Enter fullscreen mode Exit fullscreen mode

Templates part

I am gonna share more updates today.

Image description

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

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

Top comments (3)

Collapse
 
juanfrank77 profile image
Juan F Gonzalez

Great stuff! I like seeing you literally "build in public" here.

Collapse
 
shnai0 profile image
Iuliia Shnai

haahah Trying my best.
I am not yet ready to literally sit in front of camera as i am always so distracted

Collapse
 
mfts profile image
Marc Seitz

Waiting for more updates..