Proposal generator powered by ChatGPT

Proposal Generator

ongoing

As a freelancer, writing proposals is one of those tasks that always takes longer than I’d like. Every proposal needs to be clear, polished, and tailored to the client - but that eats into time I’d rather spend building websites.

So I decided to build a tool that makes the whole process quicker and easier. With on-page editing and AI-assisted content generation, it will help me adjust key sections while leveraging ChatGPT to generate tailored content on demand.

Technologies used

  • Next.js framework
  • TypeScript frontend
  • Tailwind CSS
  • ChatGPT API
  • Shadcn/UI component library

Purpose & Goals

This project is about taking a repetitive task and turning it into something streamlined and flexible. The tool is about removing friction from the proposal process so I can move faster without lowering the quality. I’m aiming for something that feels lightweight to use, but powerful enough to handle both quick changes and more detailed content.

Approach

I’m building it with React, Next.js, Tailwind CSS and ChatGPT API, using reusable components to keep the layout flexible and easy to scale. For some of the UI components, shadcn/ui gives me a base of accessible and consistent inputs, buttons, and labels.

Each proposal section is editable right on the page - just click, type, and save. For longer blocks like goals or challenges, I’ve integrated ChatGPT with a custom prompt configuration for each section. These prompts are handled through the API layer using Next.js.

Once everything’s filled out, the proposal can be exported as a PDF.

Here’s a quick overview of how each part of the stack fits together:

  • Front-end: React components styled with Tailwind CSS make up the base layout.
  • On-page editing: React state management handles on-page editing for each section.
  • ChatGPT integration: Longer sections use ChatGPT with custom prompts per section.
  • API logic: Prompts are handled server-side using Next.js (NextRequest/NextResponse).

Process

Handling states

To make on-page editing work smoothly, I created a simple pattern using React state to manage which section is being edited, what the current input value is, and when to save or cancel.

Each editable field has its own state—for example, in the info section, I manage values like service type, author name, and dates individually. When editing starts, the current value is copied into a temporary state. Once the new input is confirmed, it replaces the saved value; otherwise, the change is discarded.

This approach makes it easy to add new editable fields while keeping the UX smooth and the codebase maintainable.

// Store field values
const [infoSectionTitle, setInfoSectionTitle] = useState("");
const [infoSectionService, setInfoSectionService] = useState("");

// Track current editing field and its temporary value
const [editingField, setEditingField] = useState<string | null>(null);
const [tempInputValue, setTempInputValue] = useState("");

// Handle editing actions
const startEditing = (id: string, currentValue: string) => {
  setEditingField(id);
  setTempInputValue(currentValue || "");
};

const saveValue = (onUpdate: (value: string) => void) => {
  onUpdate(tempInputValue);
  setEditingField(null);
};

const cancelEditing = () => {
  setEditingField(null);
};

Authoring experience

To support markdown editing in AI-generated sections, I’m using react-simplemde-editor, which gives users a cleaner way to work with rich text elements like bold, lists, and links. It fits neatly into the existing editing pattern.

Since the markdown is rendered as HTML, this opened the door to potential XSS attacks, especially given how easily ChatGPT can be prompted to generate scripts. To lock things down, I added two key safeguards:

  • All output is sanitized with DOMPurify, allowing only a set of safe tags.
  • Character limits are enforced in the editing hook to prevent oversized or overly complex inputs.

Lessons Learned

Modularity and reusable components

Building this tool has pushed me to improve how I think about component reuse and modular design. One example is the editable fields - different sections require different semantics (like <h1>, <h2>, <p>, etc.), so I learned about and started using polymorphic components that can adapt based on where they’re used.

As the project has evolved, I’ve started to notice repeatable patterns. That’s led to a lot of refactoring: naming things more clearly, simplifying logic, and making components flexible enough to reuse without becoming bloated or hard to maintain.

Security awareness

I’ve also started thinking more about security - especially when allowing users (and ChatGPT) to inject HTML into the page. Adding markdown editing made it clear how even small features can open the door to real risks if you’re not careful.

Current Status

I've finished the first version of the editable components for both non-AI and AI-generated sections.

Right now, I’m focused on hardening the security layer around AI-generated content. Filtering out unsafe patterns and preventing deeply nested structures.

Future Implementations

Once I have a solid version of the core features in place, there are a few things I’d like to build out to make the tool more useful and flexible:

  • Section reordering:
    I want to allow users to drag and reorder proposal sections directly from the front end. This will make it easier to tailor the flow of each proposal based on the client's needs or project type.
  • Otter.ai integration:
    I use Otter.ai during discovery calls to capture automated notes. In the future, I’d like to integrate a step where I can select relevant transcripts before generating a proposal. These notes would be sent along with the ChatGPT prompt to provide more context and have more accurate outputs with less manual prep.

More projects

Let's connect

I'm currently looking for a full-time opportunity where I can build great web experiences. If you're hiring or just want to chat about front-end development, feel free to reach out.

Contact details