A Chat GPT-3 Generated Portfolio Website: Are we there yet?
Tags: gpt3, ai, nextjs, webdev
Published: 2023-02-10 17:15:22

I told Chat GPT to generate a portfolio website using Tailwind and React. It generated a prototype working version of it with less than 10 prompts, here are the results. The demo and code-repo are available in the conclusion.

<aside>
📢 This represents a message I send to the bot.

</aside>

## The header component

<aside>
📢 write a simple header for a personal portfolio using tailwind and react

</aside>

By writing it this way I set the expectation of the model. The website will be built using tailwind and react, here’s what it gave me:

```jsx
import React from 'react';

const Header = () => {
  return (
    <header className="bg-gray-900 text-white p-6">
      <h1 className="text-2xl font-medium">My Personal Portfolio</h1>
    </header>
  );
}

export default Header;
```

Not bad, but I needed something better, this was the second prompt

<aside>
📢 add a nav component, and make it mobile-friendly by using a hamburger menu

</aside>

```jsx
import React, { useState } from 'react';

const Header = () => {
  const [isMenuOpen, setIsMenuOpen] = useState(false);

  const toggleMenu = () => {
    setIsMenuOpen(!isMenuOpen);
  };

  return (
    <header className="bg-gray-900 text-white p-6">
      <h1 className="text-2xl font-medium">My Personal Portfolio</h1>
      <nav className={`${isMenuOpen ? 'block' : 'hidden'} md:block`}>
        <a href="#about" className="block mt-4 text-lg font-medium text-white hover:text-gray-300">About</a>
        <a href="#projects" className="block mt-4 text-lg font-medium text-white hover:text-gray-300">Projects</a>
        <a href="#contact" className="block mt-4 text-lg font-medium text-white hover:text-gray-300">Contact</a>
      </nav>
      <button
        className="block md:hidden rounded-md focus:outline-none focus:bg-gray-800"
        onClick={toggleMenu}
      >
        <svg className="h-6 w-6 fill-current text-gray-300" viewBox="0 0 24 24">
          <path d="M4 5h16a1 1 0 0 1 0 2H4a1 1 0 1 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2zm0 6h16a1 1 0 0 1 0 2H4a1 1 0 0 1 0-2z" />
        </svg>
      </button>
    </header>
  );
};

export default Header;
```

Much better, since it decided to include these 3 sections, I decided to go with the flow, and further instructed it to create the portfolio as a landing page.

## The homepage

<aside>
📢 now create the homepage based on the links in the header, and add sample content, it's a portfolio for an amateur photographer

</aside>

This is the first time I let it know who’s the portfolio for, I did this intentionally, so it could be “creative” in the beginning.

```jsx
import React from 'react';

const HomePage = () => {
  return (
    <main>
      <section id="about" className="py-10">
        <h2 className="text-3xl font-medium mb-4">About Me</h2>
        <p className="text-lg leading-loose">
          Hi, my name is John Doe and I am an amateur photographer. I have been passionate about photography for as long as I can remember, and I love nothing more than capturing beautiful moments and memories through my lens. I specialize in nature and landscape photography, but I am also experienced in portrait and event photography. 
        </p>
      </section>
      <section id="projects" className="py-10">
        <h2 className="text-3xl font-medium mb-4">My Projects</h2>
        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
          <div className="bg-gray-300 p-4">
            <img src="path/to/project-1.jpg" alt="Project 1" className="w-full h-64 object-cover" />
            <h3 className="text-xl font-medium mt-4">Project 1</h3>
            <p className="text-sm text-gray-700">A brief description of project 1</p>
          </div>
          <div className="bg-gray-300 p-4">
            <img src="path/to/project-2.jpg" alt="Project 2" className="w-full h-64 object-cover" />
            <h3 className="text-xl font-medium mt-4">Project 2</h3>
            <p className="text-sm text-gray-700">A brief description of project 2</p>
          </div>
          <div className="bg-gray-300 p-4">
            <img src="path/to/project-3.jpg" alt="Project 3" className="w-full h-64 object-cover" />
            <h3 className="text-xl font-medium mt-4">Project 3</h3>
            <p className="text-sm text-gray-700">A brief description of project 3</p>
          </div>
        </div>
      </section>
      <section id="contact" className="py-10">
        <h2 className="text-3xl font-medium mb-4">Contact Me</h2>
        <p className="text-lg leading-loose mb-4">
          If you have any questions or would like to book a photoshoot, please don't hesitate to contact me. You can reach me by phone at 555-555-5555 or by email at johndoe@example.com.
        </p>
        <a href="mailto:johndoe@example.com" className="bg-gray-800 text-white p-3 rounded-md hover:bg-gray-900">
          Contact Me
        </a>
			</section>
    </main>
  );
};

export default HomePage;
```

I wasn’t really satisfied with the first version of the homepage, so I started prompting it to add the components that I imagined it would have.

## Creating the components

I wanted a way to display more info about each project.

<aside>
📢 now create a modal component in which more information about each project will be displayed

</aside>

```jsx
import React, { useState } from 'react';

const Modal = ({ project }) => {
  const [isOpen, setIsOpen] = useState(false);

  const openModal = () => {
    setIsOpen(true);
  };

  const closeModal = () => {
    setIsOpen(false);
  };

  return (
    <div onClick={() => { if (isOpen) setIsOpen(false) }}>
      <button className="bg-gray-800 text-white p-3 rounded-md hover:bg-gray-900" onClick={openModal}>
        More Info
      </button>
      {isOpen && (
        <div className="fixed top-0 left-0 h-full w-full bg-black bg-opacity-75">
          <div className="m-auto max-w-sm bg-white rounded-md p-6">
            <button className="absolute top-0 right-0 m-2 text-gray-500 hover:text-gray-700" onClick={closeModal}>
              <svg className="h-6 w-6" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path d="M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
                <path d="M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
              </svg>
            </button>
            <h2 className="text-lg font-medium mb-4">{project.title}</h2>
            <img src={project.img} alt={project.title} className="w-full h-64 object-cover mb-4" />
            <p className="text-sm leading-loose">{project.description}</p>
          </div>
        </div>
      )}
    </div>
  );
};

export default Modal;
```

Now we’re talking, I needed one more component for a way to let users contact the photographer.

<aside>
📢 create the contact form, which will save info with some online service

</aside>

```jsx
import React, { useState } from 'react';

const ContactForm = () => {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [message, setMessage] = useState("");
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [success, setSuccess] = useState(false);
  const [error, setError] = useState(null);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsSubmitting(true);
    setError(null);

    try {
      // Use your preferred online service for saving the data here
      // for example, you can use a service like Formspree to save the contact form data
      //const response = await fetch("https://formspree.io/your-email-address", {
      //  method: "POST",
      //  headers: {
      //    "Content-Type": "application/json"
      //  },
      //  body: JSON.stringify({ name, email, message })
      //});
      //const data = await response.json();
      //if (!response.ok) {
      //  throw new Error(data.message);
      //}
      //setSuccess(true);
    } catch (err) {
      setError(err.message);
    } finally {
      setIsSubmitting(false);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      {error && <p className="text-red-500">{error}</p>}
      {success && <p className="text-green-500">Your message was sent successfully!</p>}
      <label>
        Name:
        <input
          type="text"
          value={name}
          onChange={(e) => setName(e.target.value)}
          className="block w-full border border-gray-400 p-2 rounded-md"
          disabled={isSubmitting}
        />
      </label>
      <label>
        Email:
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          className="block w-full border border-gray-400 p-2 rounded-md mt-2"
          disabled={isSubmitting}
        />
      </label>
      <label>
        Message:
        <textarea
          value={message}
          onChange={(e) => setMessage(e.target.value)}
          className="block w-full border border-gray-400 p-2 rounded-md mt-2"
          rows={5}
          disabled={isSubmitting}
        />
      </label>
      <button
        type="submit"
        className="bg-gray-800 text-white p-3 rounded-md hover:bg-gray-900 mt-4"
        disabled={isSubmitting}
      >
        Send
      </button>
    </form>
  );
};

export default ContactForm;
```

All that was left to do was give a more meaningful and personal description about the photographer

<aside>
📢 The photographer's name is Erik Vacev, he mostly takes pictures of landscapes and nature, and occasionally he does portraits.
Create an about me description for him

</aside>

> "Meet Erik Vacev, an amateur photographer with a passion for capturing the beauty of nature and landscapes. His love for photography began at a young age and has grown over the years, as he traveled to different places and captured breathtaking images. Erik's attention to detail and his ability to capture the essence of a place make his photographs stand out. He mostly takes pictures of landscapes and nature, but occasionally he does portraits. He believes that every picture tells a story and he wants to share his stories with the world. He is always looking for new and exciting places to photograph, and he hopes to inspire others to see the world in a new way through his work.”
> 

Voila, now I have all the building blocks, all I need to do is put them together. Well why waste time when I can just ask it to do it for me?

## Putting it all together

I didn’t like the way the header was styled, so I asked it to recreate it

<aside>
📢 better style the header you previously created using tailwind, align items to center and add some bg color to it

</aside>

It failed to recreate it as it was before, but I did copy the new styles and applied them to the previous version of it.

The next step was to recreate the homepage using the new components

<aside>
📢 regenerate the homepage using everything I told you after, add padding, and make it mobile friendly

</aside>

```jsx
import React from "react";
import Modal from "../components/Modal";
import ContactForm from "../components/Contact";

const HomePage = () => {
  return (
    <main className="p-8">
     
      <section id="about" className="mb-12">
        <h2 className="text-2xl font-medium mb-4">About Me</h2>
        <p className="text-sm leading-loose">
          Meet Erik Vacev, an amateur photographer with a passion for capturing the beauty of nature and landscapes. His love for photography began at a young age and has grown over the years, as he traveled to different places and captured breathtaking images. Erik's attention to detail and his ability to capture the essence of a place make his photographs stand out. He mostly takes pictures of landscapes and nature, but occasionally he does portraits. He believes that every picture tells a story and he wants to share his stories with the world. He is always looking for new and exciting places to photograph, and he hopes to inspire others to see the world in a new way through his work.
        </p>
      </section>
      <section id="projects" className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-12">
        <div className="bg-gray-300 p-4">
          <img src="path/to/project-1.jpg" alt="Project 1" className="w-full h-64 object-cover" />
          <h3 className="text-xl font-medium mt-4">Project 1</h3>
          <p className="text-sm text-gray-700">A brief description of project 1</p>
          <Modal project={{
            title: "Project 1",
            img: "path/to/project-1.jpg",
            description: "A more detailed description of project 1, including information about the techniques used, equipment, and any interesting facts or stories related to the project."
          }} />
        </div>
        <div className="bg-gray-300 p-4">
          <img src="path/to/project-2.jpg" alt="Project 2" className="w-full h-64 object-cover" />
          <h3 className="text-xl font-medium mt-4">Project 2</h3>
          <p className="text-sm text-gray-700">A brief description of project 2</p>

          <Modal project={{
            title: "Project 2",
            img: "path/to/project-2.jpg",
            description: "A more detailed description of project 2, including information about the techniques used, equipment, and any interesting facts or stories related to the project."
          }} />
        </div>
        <div className="bg-gray-300 p-4">
          <img src="path/to/project-3.jpg" alt="Project 3" className="w-full h-64 object-cover" />
          <h3 className="text-xl font-medium mt-4">Project 3</h3>
          <p className="text-sm text-gray-700">A brief description of project 3</p>
          <Modal project={{
            title: "Project 3",
            img: "path/to/project-3.jpg",
            description: "A more detailed description of project 3, including information about the techniques used, equipment, and any interesting facts or stories related to the project."
          }} />
        </div>
      </section>
      <section id="contact">
        <h2 className="text-2xl font-medium mb-4">Contact Me</h2>
        <ContactForm />
      </section>
    </main>
  );
};

export default HomePage;
```

The final result

![https://res.cloudinary.com/dvvbls283/image/upload/v1673605085/Blog/x7kohmynmzcliccqluv8.webp](https://res.cloudinary.com/dvvbls283/image/upload/v1673605085/Blog/x7kohmynmzcliccqluv8.webp)

## Conclusion and demo

I was impressed by the code it was able to write, I can see that it’s not yet ready to write production-level code, but it’s a good starting point for any project I will create in the future. 

Maybe my prompts weren’t good enough, but it couldn’t create the mobile header I was expecting it to create, even after trying 10+ times. In the end, I just changed some styles, to make it pretty and responsive.

If you want to check out the code base [click here.](https://github.com/dameradev/chat-gpt3-generated-portfolio) All I needed to do was generate a next app using `create-next-app` install and configure tailwind, and copy over the code that was generated.

A demo of the app is [available here.](https://chat-gpt3-generated-portfolio.vercel.app/)