[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a bug report for AI landing page generator\ntitle: \"[BUG]\"\nlabels: ''\nassignees: ''\n\n---\n\n# Bug Report\n\n**Description**\n⚠️ Please provide a clear and concise description of the bug.\n\n**Steps to Reproduce**\n⚠️ Please provide step-by-step instructions to reproduce the bug.\n\n**Expected Behavior**\n⚠️ Please describe what you expected to happen.\n\n**Actual Behavior**\n⚠️ Please describe what actually happened.\n\n**Additional Information**\n⚠️ Add any additional information or context about the bug here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n# Feature Request\n\n**Description**\nPlease provide a clear and concise description of the feature request.\n\n**Proposed Solution**\nPlease describe the proposed solution or new feature in detail.\n\n**Alternatives Considered**\nPlease describe any alternative solutions or features you've considered.\n\n**Additional Information**\nAdd any additional information or context about the feature request here.\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n.env\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n/.idea\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# local env files\n.env*.local\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\nAs contributors and maintainers of the AI Landing Page Generator project, we pledge to make participation in our project a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment include:\n\n- Being respectful and considerate of others' opinions and ideas.\n- Using inclusive language and being mindful of our words and their impact.\n- Being open to constructive feedback and providing feedback in a respectful manner.\n- Showing empathy and kindness towards others.\n- Focusing on collaboration and fostering a supportive community.\n\nExamples of unacceptable behavior include:\n\n- Harassment, discrimination, or derogatory comments and personal attacks.\n- Any form of offensive or inappropriate language or imagery.\n- Trolling, flaming, or insulting/derogatory comments.\n- Intimidating or bullying behavior.\n- Any other conduct that could be considered inappropriate in a professional setting.\n\n## Scope\n\nThis Code of Conduct applies to all project contributors, both online and offline, as well as in all project-related spaces.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [zinedkaloc](https://twitter.com/zinedkaloc). The project team is committed to reviewing and addressing all reported incidents promptly and fairly.\n\n## Consequences\n\nAny contributor who engages in behavior violating this Code of Conduct may be temporarily or permanently excluded from project participation at the discretion of the project team.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.0, available at [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html](https://www.contributor-covenant.org/version/2/0/code_of_conduct.html).\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to AI Landing Page Generator\n\nWe welcome and appreciate contributions from the community! By contributing to the AI Landing Page Generator project, you can help us make it even better.\n\n## Ways to Contribute\n\nThere are several ways you can contribute to this project:\n\n- Report bugs: If you come across any issues or bugs, please [open a new issue](https://github.com/zinedkaloc/ai-page/issues) and provide as much detail as possible.\n\n- Suggest new features: Have an idea for a new feature or improvement? We'd love to hear it! [Open a new issue](https://github.com/zinedkaloc/ai-page/issues) and let us know.\n\n- Submit pull requests: If you have code changes or enhancements you'd like to contribute, you can do so by opening a pull request. Make sure to follow the guidelines below when submitting your pull request.\n\n## Guidelines for Pull Requests\n\nTo ensure smooth collaboration and maintain code quality, please follow these guidelines when submitting a pull request:\n\n1. Fork the repository and create a new branch for your changes.\n\n2. Before making changes, make sure your branch is up to date with the master repository.\n\n3. Follow the coding style and conventions used in the project.\n\n4. Include clear and concise commit messages that describe the purpose of your changes.\n\n5. Provide a detailed description of the changes you've made in the pull request.\n\n6. Test your changes thoroughly to ensure they work as intended.\n\n7. Make sure your code is properly documented.\n\n8. Ensure that your changes do not introduce any breaking changes to the existing functionality.\n\n9. Be responsive to any feedback or questions regarding your pull request.\n\n10. Once your changes are approved, they will be merged into the master repository.\n\n## Code of Conduct\n\nPlease note that by participating in this project, you are expected to adhere to the [Code of Conduct](CODE_OF_CONDUCT.md). We kindly ask you to respect the guidelines and treat others with respect and kindness.\n\n## Questions or Concerns\n\nIf you have any questions or concerns regarding the project or the contribution process, please feel free to [zinedkaloc](https://twitter.com/zinedkaloc).\n\nThank you for your interest in contributing to the AI Landing Page Generator project!\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# AI Landing Page Generator\n\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\n## Description\n\n![A landing page generator](/public/logo.png)\n\nThe AI Landing Page Generator is a powerful tool that allows you to quickly create stunning landing pages using artificial intelligence. With this generator, you can save time and effort by automating the process of designing and coding landing pages. It is now experimental and only supports the creation of landing pages with HTML and Tailwind CSS.\n\n## Installation\n\n1. Clone the repository: `git clone https://github.com/zinedkaloc/aipage.dev.git`\n2. Install the required dependencies: `pnpm install`\n3. Navigate to the project directory `cd aipage.dev`\n4. Copy the `.env.example` file to `.env` and fill in the required information.\n\n> Note: If you are not comfortable with creating OPENAI_API_KEY, please reach out to me at [zinedkaloc](https://twitter.com/zinedkaloc) and I will be happy to help you.\n\n## Usage\n\n1. Run the generator: `pnpm dev`\n2. Open the browser and go to `http://localhost:3000`\n3. Describe your website and click on the \"Enter\".\n4. Wait for the genie to generate the landing page.\n\n### Test prompts\n\n> A landing page for a design studio located in New york. They make websites and custom logos. All the work is very stylish and minimalist. The page should include a contact form a some call to actions as well as a link to the portfolio.\n\n> A landing page for an interior architecture company based in Istanbul. They specialize in creating exceptional spatial designs and innovative interior solutions. Their work showcases a unique blend of style, minimalism, and functionality. The landing page should feature a captivating design with using gradients, including a contact form, compelling call-to-action elements, and a prominent link to their portfolio.\n\n> A landing page for a cutting-edge technology company based in Istanbul. They specialize in developing innovative software solutions and advanced technological products. Their work is known for its sleek design, seamless user experience, and transformative capabilities. The landing page should feature a modern design, including a contact form, compelling call-to-action elements, and a prominent link to their product offerings.\n\n## Roadmap\n\n- [x] Create a basic landing page generator.\n- [x] Display the generated landing page in the main page with an iframe.\n- [ ] Update text input to textarea and add submit button.\n- [ ] Add support to download the generated landing page as a zip file.\n- [ ] Add support to deploy the generated landing page to Vercel or Netlify with a single click.\n- [ ] Add support for dark mode.\n- [ ] Add support for multiple pages.\n- [ ] Add support for multiple languages.\n\nThis project is still in its early stages of development. If you have any suggestions or ideas, please feel free to open an issue or submit a pull request.\n\n## Contributing\n\nThank you for considering contributing to this project! To contribute, follow these steps:\n\n1. Fork the repository.\n2. Create a new branch for your feature/fix: `git checkout -b feature/your-feature-name`.\n3. Add your changes with: `git add .`.\n4. Make your changes and commit them: `git commit -m \"Add your feature description\"`.\n5. Push to the branch: `git push origin feature/your-feature-name`.\n6. Open a pull request to the `master` branch of the original repository. Provide a clear and descriptive title and description for your pull request. Include any relevant information or context that would help with the review process.\n7. Wait for the maintainers to review your pull request. Make any necessary changes or address any feedback provided.\n8. Once your pull request is approved, it will be merged into the `master` branch.\n\nCongratulations on your contribution!\n\n## License\n\nThis project is licensed under the [MIT License](LICENSE).\n\n## Contact\n\nFor any inquiries or feedback, please reach out to us at [zinedkaloc](https://twitter.com/zinedkaloc).\n"
  },
  {
    "path": "app/(aipage)/layout.tsx",
    "content": "import \"@smastrom/react-rating/style.css\";\nimport { ReactNode } from \"react\";\nimport Header from \"@/components/Header\";\n\nexport default async function AipageLayout({\n  children,\n}: {\n  children: ReactNode;\n}) {\n  return (\n    <>\n      <Header />\n      {children}\n    </>\n  );\n}\n"
  },
  {
    "path": "app/(aipage)/loading.tsx",
    "content": "import LoadingSpinner from \"@/components/loadingSpinner\";\n\nexport default function RootLoading() {\n  return (\n    <div className=\"pt-[72px] flex items-center justify-center\">\n      <LoadingSpinner />\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(aipage)/not-found.tsx",
    "content": "import Link from \"next/link\";\nimport Button from \"@/components/Button\";\n\nexport default async function NotFound() {\n  return (\n    <section className=\"bg-white fixed inset-0\">\n      <div className=\"container flex items-center min-h-screen px-6 py-12 mx-auto\">\n        <div>\n          <p className=\"text-2xl font-medium text-blue-500\">404 NOT FOUND</p>\n          <h1 className=\"mt-3 text-3xl font-semibold text-gray-800 md:text-4xl\">\n            We can’t find that page\n          </h1>\n          <p className=\"mt-4 text-gray-500\">\n            Sorry, the page you are looking for doesn't exist or has been moved.\n          </p>\n\n          <div className=\"flex items-center mt-6 gap-x-3\">\n            <Link href=\"/profile/projects\">\n              <Button variant=\"pill\">Go to projects</Button>\n            </Link>\n          </div>\n        </div>\n      </div>\n    </section>\n  );\n}\n"
  },
  {
    "path": "app/(aipage)/page.tsx",
    "content": "\"use client\";\nimport { useChat } from \"ai/react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport Frame from \"react-frame-component\";\nimport Image from \"next/image\";\nimport html2canvas from \"html2canvas\";\nimport TweetButton from \"@/components/tweetButton\";\nimport { useAuth } from \"@/context/AuthContext\";\nimport useSearchParams from \"@/hooks/useSearchParams\";\nimport RateModal from \"@/components/RateModal\";\nimport { cn, updateProject } from \"@/utils/helpers\";\nimport Link from \"next/link\";\n\nenum DeviceSize {\n  Mobile = \"w-1/2\",\n  Tablet = \"w-3/4\",\n  Desktop = \"w-full\",\n}\n\nexport default function Chat() {\n  const { user, setUser } = useAuth();\n  const [lastMessageId, setLastMessageId] = useState<string | null>(null);\n  const [hasNoCreditsError, setHasNoCreditsError] = useState(false);\n  const { set } = useSearchParams();\n\n  const { messages, input, handleInputChange, handleSubmit, isLoading, stop } =\n    useChat({\n      onResponse: (message) => {\n        setHasNoCreditsError(false);\n        setLastMessageId(null);\n        decreaseCredit();\n      },\n      onFinish: async (message) => {\n        try {\n          const res = JSON.parse(message.content) as { credits: number };\n          setCredits(res.credits);\n          setHasNoCreditsError(res.credits === 0);\n        } catch {\n          await saveResult(message.content);\n        }\n      },\n    });\n\n  function decreaseCredit(by: number = 1) {\n    if (user) {\n      setUser({ ...user, credits: user.credits - by });\n    }\n  }\n\n  function setCredits(credits: number) {\n    if (user) {\n      setUser({ ...user, credits });\n    }\n  }\n\n  async function saveResult(result: string) {\n    const { _id } = await updateProject({\n      result,\n    });\n    setLastMessageId(_id);\n    set(\"rateModal\", \"true\");\n  }\n\n  const [iframeContent, setIframeContent] = useState(\"\");\n  const [imageSrc, setImageSrc] = useState<string>(\"\");\n\n  const [deviceSize, setDeviceSize] = useState(DeviceSize.Desktop);\n  const iframeRef = useRef(null);\n  const [fileName, setFileName] = useState(\"\");\n  const [selectedElement, setSelectedElement] = useState<Element | null>(null);\n  const [editedContent, setEditedContent] = useState<string>(\"\");\n  const [editingMode, setEditingMode] = useState(false);\n  const [codeViewActive, setCodeViewActive] = useState(false);\n  const [isStopped, setIsStopped] = useState(false);\n\n  const appendToIframe = (content: any) => {\n    if (iframeRef.current) {\n      const iframeDocument = (iframeRef.current as HTMLIFrameElement)\n        .contentDocument;\n      if (iframeDocument) {\n        const newNode = iframeDocument.createElement(\"div\");\n        newNode.innerHTML = content;\n        newNode.querySelectorAll<HTMLElement>(\"*\").forEach((element) => {\n          element.addEventListener(\"mouseover\", () => {\n            element.classList.add(\"outline-blue\"); // Blue border\n          });\n          element.addEventListener(\"mouseout\", () => {\n            element.style.outline = \"none\";\n          });\n          element.addEventListener(\"click\", () => {\n            setSelectedElement(element);\n            setEditedContent(element.innerHTML);\n          });\n        });\n        requestAnimationFrame(() => {\n          iframeDocument.body.appendChild(newNode);\n        });\n      }\n    }\n  };\n\n  const captureIframeContent = async () => {\n    if (iframeRef.current) {\n      const iframeDocument = (iframeRef.current as HTMLIFrameElement)\n        .contentDocument;\n      if (iframeDocument) {\n        const canvas = await html2canvas(iframeDocument.body);\n        const imgURL = canvas.toDataURL();\n        // You can use imgURL as the src for an image tag to display the image representation of the iframe content\n        // For simplicity, let's just set it to a state variable\n        setImageSrc(imgURL);\n      }\n    }\n  };\n\n  useEffect(() => {\n    const stream = new EventSource(\"/api/chat\");\n    stream.onmessage = (event) => {\n      appendToIframe(event.data);\n    };\n\n    return () => stream.close();\n  }, []);\n\n  useEffect(() => {\n    const lastMessage = messages[messages.length - 1];\n    if (lastMessage && lastMessage.role !== \"user\") {\n      setIframeContent(lastMessage.content);\n    }\n  }, [messages]);\n\n  const handleSave = () => {\n    const element = document.createElement(\"a\");\n    const file = new Blob([iframeContent], { type: \"text/html\" });\n    element.href = URL.createObjectURL(file);\n    element.download = fileName || \"index.html\";\n    document.body.appendChild(element);\n    element.click();\n    document.body.removeChild(element);\n    const completionInput = iframeContent;\n  };\n\n  const listenersMap = useRef<\n    Map<HTMLElement, { mouseover: () => void; mouseout: () => void }>\n  >(new Map());\n\n  // Create a map to store the listeners for each element\n\n  const handleEdit = () => {\n    if (editingMode) {\n      // Save the updated iframe content\n      if (iframeRef.current) {\n        const iframeDocument = (iframeRef.current as HTMLIFrameElement)\n          .contentDocument;\n        if (iframeDocument) {\n          setIframeContent(iframeDocument.documentElement.innerHTML);\n        }\n      }\n\n      // Disable editing mode by setting the contentEditable property of all elements to false and remove the event listeners\n      if (iframeRef.current) {\n        const iframeDocument = (iframeRef.current as HTMLIFrameElement)\n          .contentDocument;\n        if (iframeDocument) {\n          iframeDocument\n            .querySelectorAll<HTMLElement>(\"*\")\n            .forEach((element) => {\n              element.contentEditable = \"false\";\n\n              // Get the listeners for the element from the map\n              const listeners = listenersMap.current.get(element);\n              if (listeners) {\n                // Remove the listeners\n                element.removeEventListener(\"mouseover\", listeners.mouseover);\n                element.removeEventListener(\"mouseout\", listeners.mouseout);\n                // Remove the element from the map\n                listenersMap.current.delete(element);\n              }\n            });\n        }\n      }\n    } else {\n      // Enable editing mode by setting the contentEditable property of all elements to true and add event listeners\n      if (iframeRef.current) {\n        const iframeDocument = (iframeRef.current as HTMLIFrameElement)\n          .contentDocument;\n        if (iframeDocument) {\n          iframeDocument\n            .querySelectorAll<HTMLElement>(\"*\")\n            .forEach((element) => {\n              element.contentEditable = \"true\";\n\n              // Create the event listeners\n              const mouseoverListener = () => {\n                element.classList.add(\"outline-blue\");\n                console.log(\"Mouseover event fired\");\n              };\n              const mouseoutListener = () => {\n                console.log(\"Mouseout event fired\");\n                element.classList.remove(\"outline-blue\");\n              };\n\n              // Add the listeners to the element\n              element.addEventListener(\"mouseover\", mouseoverListener);\n              element.addEventListener(\"mouseout\", mouseoutListener);\n\n              // Store the listeners in the map\n              listenersMap.current.set(element, {\n                mouseover: mouseoverListener,\n                mouseout: mouseoutListener,\n              });\n            });\n        }\n      }\n    }\n\n    setEditingMode(!editingMode);\n  };\n\n  const handleUpdate = () => {\n    if (selectedElement) {\n      selectedElement.innerHTML = editedContent;\n      setSelectedElement(null);\n      setEditedContent(\"\");\n      if (iframeRef.current) {\n        const iframeDocument = (iframeRef.current as HTMLIFrameElement)\n          .contentDocument;\n        if (iframeDocument) {\n          setIframeContent(iframeDocument.documentElement.innerHTML);\n        }\n      }\n    }\n  };\n\n  function onFocusHandler() {\n    if (!user) {\n      set(\"authModal\", \"true\");\n    }\n  }\n\n  const handleStop = async () => {\n    stop();\n    setIsStopped(true);\n    await saveResult(iframeContent);\n  };\n\n  return (\n    <>\n      <div className=\"flex flex-col w-full min-h-screen bg-gradient-to-b from-white via-white to-slate-300 mx-auto px-4 md:px-16 lg:px-24 overflow-hidden items-center pt-24 md:pt-36\">\n        <section>\n          <div className=\"fixed bottom-16 right-6 cursor-pointer transition-colors group\">\n            <div className=\"tooltip opacity-0 group-hover:opacity-100 bg-gray-700 text-white text-xs rounded py-1 px-2 absolute right-8 bottom-4 transform translate-y-2 w-64\">\n              Help spread the word! 📢 Post a tweet of your creation on Twitter\n              and tag @aipagedev for early access to our exclusive beta—packed\n              with stunning features. 🚀\n            </div>\n            <TweetButton />\n          </div>\n        </section>\n\n        {/* Display the image if imageSrc is set */}\n\n        <section>\n          <div className=\"fixed bottom-6 right-6 cursor-pointer transition-colors group\">\n            <div className=\"tooltip opacity-0 group-hover:opacity-100 bg-gray-700 text-white text-xs rounded py-1 px-2 absolute  right-8 bottom-4 transform translate-y-2 w-48\">\n              Star us on Github to show your support\n            </div>\n            <a\n              href=\"https://github.com/zinedkaloc/aipage.dev\"\n              target=\"_blank\"\n              rel=\"noreferrer\"\n              className=\"text-2xl\"\n            >\n              ⭐️\n            </a>\n          </div>\n        </section>\n\n        {isLoading ? null : (\n          <div className=\"relative py-6 flex flex-col justify-center\">\n            <Image\n              src=\"/logoa.png\"\n              alt=\"AIPage.dev logo\"\n              width={200}\n              height={200}\n              className=\"mx-auto h-32 w-32\"\n            />\n            <div className=\"text-center sm:w-11/12 md:w-[800px]\">\n              <h1 className=\"text-5xl font-bold text-ellipsis tracking-tight\">\n                🚧 Under Renovation 🚧\n              </h1>\n\n              <p className=\"text-lg text-gray-700 mt-4 tracking-tight\">\n                A refreshed experience is on the horizon. <br /> Follow us on\n                <Link href=\"https://x.com/aipagedev\">\n                  <b> X</b>\n                </Link>{\" \"}\n                to stay updated!\n              </p>\n              <p className=\"text-xs pt-2 ml-4 font-medium text-gray-500 cursor-pointer animate-pulse\"></p>\n            </div>\n          </div>\n        )}\n\n        {editingMode && selectedElement && (\n          <div className=\"absolute z-50\">\n            <p>Edit the selected element:</p>\n            <input\n              value={editedContent}\n              onChange={(e) => setEditedContent(e.target.value)}\n            />\n            <button onClick={handleUpdate}>Update</button>\n          </div>\n        )}\n\n        {hasNoCreditsError ? (\n          <div className=\"flex flex-col items-center justify-center\">\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"100\"\n              height=\"100\"\n              viewBox=\"0 0 100 100\"\n            >\n              <polygon points=\"50,15 85,85 15,85\" fill=\"#FF6B6B\" />\n              <text\n                x=\"50\"\n                y=\"75\"\n                fontSize=\"40\"\n                fontWeight=\"bold\"\n                textAnchor=\"middle\"\n                fill=\"#FFFFFF\"\n              >\n                !\n              </text>\n            </svg>\n            <h1 className=\"text-3xl text-gray-800 mb-2\">No Credits</h1>\n            <p className=\"text-gray-600 mb-6\">\n              You do not have enough credits to proceed with this request today.\n              Please try again tomorrow.\n            </p>\n          </div>\n        ) : (\n          iframeContent && (\n            <div className=\"flex flex-col items-center py-4 w-full\">\n              <div className={cn(deviceSize)}>\n                <div className=\"border flex items-center bg-white rounded-t-xl justify-between p-3 border-b lg:px-12 sticky top-4 z-10\">\n                  <div className=\"flex items-center space-x-2\">\n                    <div className=\"w-3 h-3 bg-red-500 rounded-full\"></div>\n                    <div className=\"w-3 h-3 bg-yellow-500 rounded-full\"></div>\n                    <div className=\"w-3 h-3 bg-green-500 rounded-full\"></div>\n                  </div>\n                  <div className=\"flex-1 text-center\">\n                    <div className=\"flex items-center justify-between space-x-2 bg-gray-200 rounded-xl mx-8 py-1 px-2\">\n                      <div className=\"flex items-center w-3 h-3\"></div>\n                      <div className=\"flex items-center space-x-2\">\n                        acme.co\n                        {isLoading && (\n                          <span className=\"ml-4 animate-spin\">🟠</span>\n                        )}\n                        <button\n                          className=\"ml-4 hidden md:flex\"\n                          onClick={() => setDeviceSize(DeviceSize.Mobile)}\n                        >\n                          📱\n                        </button>\n                        <button\n                          className=\" hidden md:flex\"\n                          onClick={() => setDeviceSize(DeviceSize.Tablet)}\n                        >\n                          💻\n                        </button>\n                        <button\n                          className=\" hidden md:flex\"\n                          onClick={() => setDeviceSize(DeviceSize.Desktop)}\n                        >\n                          🖥️\n                        </button>\n                        <button\n                          className=\"\"\n                          onClick={() => setCodeViewActive(!codeViewActive)}\n                        >\n                          {codeViewActive ? \"🖼️\" : \"🖨️\"}\n                        </button>\n                      </div>\n                      {/* Clear and Stop buttons */}\n                      <div className=\"flex items-center space-x-4\">\n                        <button\n                          className={`${\n                            isLoading ? \"\" : \"opacity-70 cursor-not-allowed\"\n                          }`}\n                          onClick={handleStop}\n                        >\n                          <span role=\"img\" aria-label=\"stop\">\n                            🟥\n                          </span>\n                        </button>\n                        <button\n                          className={`${\n                            isStopped ? \"\" : \"opacity-70 cursor-not-allowed\"\n                          }`}\n                          onClick={() => {\n                            setIframeContent(\"\");\n                            setIsStopped(false);\n                          }}\n                        >\n                          <span role=\"img\" aria-label=\"clear\">\n                            🧽\n                          </span>\n                        </button>\n                      </div>\n                    </div>\n                  </div>\n                  <div></div>\n                  <div className=\"flex justify-end space-x-4\">\n                    <button onClick={handleSave}>\n                      <span role=\"img\" aria-label=\"paper-plane\">\n                        📩\n                      </span>\n                    </button>\n                    {!isLoading && iframeContent && (\n                      <button onClick={handleEdit} className=\"ml-4\">\n                        {editingMode ? \"💾\" : \"✏️\"}\n                      </button>\n                    )}\n                  </div>\n                </div>\n                <div className=\"border bg-white rounded-b-xl border-t-0 h-[calc(100vh-100px)] overflow-auto\">\n                  <Frame\n                    ref={iframeRef}\n                    sandbox=\"allow-same-origin allow-scripts\"\n                    className=\"w-full h-full\"\n                  >\n                    {codeViewActive ? (\n                      <pre>{iframeContent}</pre>\n                    ) : (\n                      <div\n                        dangerouslySetInnerHTML={{ __html: iframeContent }}\n                      />\n                    )}\n                  </Frame>\n                </div>\n              </div>\n            </div>\n          )\n        )}\n      </div>\n      <RateModal key={lastMessageId} show={!!lastMessageId} />\n    </>\n  );\n}\n"
  },
  {
    "path": "app/(aipage)/profile/invoices/loading.tsx",
    "content": "import LoadingSpinner from \"@/components/loadingSpinner\";\n\nexport default function InvoicesLoading() {\n  return (\n    <div className=\"w-ful mx-auto l flex h-full items-center justify-center max-w-screen-xl p-6\">\n      <LoadingSpinner />\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(aipage)/profile/invoices/page.tsx",
    "content": "import { fetchInvoices } from \"@/utils/auth\";\nimport { moneyFormat, stripePrice } from \"@/utils/helpers\";\nimport Button from \"@/components/Button\";\nimport { FileText } from \"lucide-react\";\nimport { Invoice } from \"@/types\";\n\nexport default async function InvoicesPage() {\n  const invoices = await fetchInvoices();\n\n  const hasInvoices = invoices?.length > 0;\n\n  return (\n    <section className=\"w-full px-6 py-6 bg-gray-50 h-full flex-1 flex flex-col\">\n      <div className=\"mx-auto w-full sm:max-w-screen-2xl sm:px-2.5 lg:px-20 space-y-4\">\n        {hasInvoices && (\n          <div>\n            <h2 className=\"text-lg leading-6 font-medium text-gray-900\">\n              Invoices\n            </h2>\n            <p className=\"mt-1 text-sm text-gray-500\">\n              Check the invoices for your purchases.\n            </p>\n          </div>\n        )}\n        {!hasInvoices ? (\n          <div className=\"flex flex-1 flex-col items-center justify-center rounded-md border border-gray-200 bg-white py-12\">\n            <h2 className=\"z-10 text-xl font-semibold text-gray-700\">\n              You don't have any invoices yet!\n            </h2>\n            <img\n              alt=\"No domains yet\"\n              loading=\"lazy\"\n              width={500}\n              className=\"pointer-events-none blur-0\"\n              src=\"/no-project.png\"\n            />\n          </div>\n        ) : (\n          <>\n            <div className=\"hidden md:block\">\n              <DesktopTable invoices={invoices} />\n            </div>\n            <div className=\"block md:hidden\">\n              <MobileTable invoices={invoices} />\n            </div>\n          </>\n        )}\n      </div>\n    </section>\n  );\n}\n\nfunction DesktopTable({ invoices }: { invoices: Invoice[] }) {\n  return (\n    <div className=\"flex flex-col\">\n      <div className=\"-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8\">\n        <div className=\"py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8\">\n          <div className=\"overflow-hidden border rounded-lg\">\n            <table className=\"min-w-full divide-y divide-gray-200\">\n              <thead className=\"bg-gray-50\">\n                <tr>\n                  <th\n                    scope=\"col\"\n                    className=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\"\n                  >\n                    Product name\n                  </th>\n                  <th\n                    scope=\"col\"\n                    className=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\"\n                  >\n                    Order date\n                  </th>\n                  <th\n                    scope=\"col\"\n                    className=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\"\n                  >\n                    Status\n                  </th>\n                  <th\n                    scope=\"col\"\n                    className=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\"\n                  >\n                    Total amount\n                  </th>\n                  <th scope=\"col\" className=\"relative px-6 py-3\"></th>\n                </tr>\n              </thead>\n              <tbody>\n                {invoices.map((invoice, index) => (\n                  <tr\n                    key={index}\n                    className={index % 2 === 0 ? \"bg-white\" : \"bg-gray-50\"}\n                  >\n                    <td className=\"px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900\">\n                      {invoice.lines.data[0]?.price?.nickname ?? \"Unknown\"}{\" \"}\n                      Credits\n                    </td>\n                    <td className=\"px-6 py-4 whitespace-nowrap text-sm text-gray-500\">\n                      {new Date(invoice.created * 1000).toLocaleDateString()}\n                    </td>\n                    <td className=\"px-6 py-4 whitespace-nowrap text-sm text-gray-500\">\n                      {invoice.status.toUpperCase()}\n                    </td>\n                    <td className=\"px-6 py-4 whitespace-nowrap text-sm text-gray-500\">\n                      {moneyFormat(stripePrice(invoice.total))}\n                    </td>\n                    <td className=\"px-6 py-4 whitespace-nowrap text-right text-sm font-medium\">\n                      <div className=\"flex justify-end w-full\">\n                        <a\n                          href={invoice.hosted_invoice_url}\n                          className=\"block w-full sm:w-auto\"\n                          target=\"_blank\"\n                        >\n                          <Button\n                            disabled={!invoice.hosted_invoice_url}\n                            className=\"w-full sm:w-auto gap-2 [&:not(:hover)]:text-gray-500\"\n                          >\n                            <FileText className=\"h-4 w-4\" />\n                            View Invoice\n                          </Button>\n                        </a>\n                      </div>\n                    </td>\n                  </tr>\n                ))}\n              </tbody>\n            </table>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n\nfunction MobileTable({ invoices }: { invoices: Invoice[] }) {\n  return (\n    <div className=\"flex gap-2 flex-col\">\n      {invoices?.map((invoice) => (\n        <div\n          key={invoice.id}\n          className=\"bg-white border-gray-200 shadow-sm rounded-lg border p-4\"\n        >\n          <div className=\"flex flex-col gap-2 sm:gap-0 sm:flex-row\">\n            <dl className=\"flex-1 grid sm:grid-cols-2 gap-2 md:grid-cols-4\">\n              <div>\n                <dt className=\"font-medium text-gray-900\">Product Name</dt>\n                <dd className=\"mt-1 text-gray-500\">\n                  {invoice.lines.data[0]?.price?.nickname ?? \"Unknown\"} Credits\n                </dd>\n              </div>\n              <div>\n                <dt className=\"font-medium text-gray-900\">Order date</dt>\n                <dd className=\"mt-1 text-gray-500\">\n                  <time dateTime={invoice.created.toString()}>\n                    {new Date(invoice.created * 1000).toLocaleDateString()}\n                  </time>\n                </dd>\n              </div>\n              <div>\n                <dt className=\"font-medium text-gray-900\">Order status</dt>\n                <dd className=\"mt-1 font-medium text-gray-500\">\n                  {invoice.status}\n                </dd>\n              </div>\n              <div>\n                <dt className=\"font-medium text-gray-900\">Total amount</dt>\n                <dd className=\"mt-1 font-medium text-gray-500\">\n                  {moneyFormat(stripePrice(invoice.total))}\n                </dd>\n              </div>\n            </dl>\n            <div className=\"flex gap-1 sm:items-center justify-center\">\n              <a\n                href={invoice.hosted_invoice_url}\n                className=\"block w-full sm:w-auto\"\n                target=\"_blank\"\n              >\n                <Button className=\"w-full sm:w-auto gap-2 [&:not(:hover)]:text-gray-500\">\n                  <FileText className=\"h-4 w-4\" />\n                  View Invoice\n                </Button>\n              </a>\n            </div>\n          </div>\n        </div>\n      ))}\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(aipage)/profile/layout.tsx",
    "content": "import { ReactNode } from \"react\";\nimport ProfileLayout from \"@/components/ProfileLayout\";\n\nexport default async function ProfileBaseLayout({\n  children,\n}: {\n  children: ReactNode;\n}) {\n  return <ProfileLayout>{children}</ProfileLayout>;\n}\n"
  },
  {
    "path": "app/(aipage)/profile/projects/[id]/domains/layout.tsx",
    "content": "\"use client\";\nimport AddDomainModal from \"@/components/AddDomainModal\";\nimport Button from \"@/components/Button\";\nimport DomainCard from \"@/components/DomainCard\";\nimport useSearchParams from \"@/hooks/useSearchParams\";\nimport { ReactNode } from \"react\";\n\nexport default function DomainLayout({ children }: { children: ReactNode }) {\n  const { set } = useSearchParams();\n\n  return (\n    <>\n      <AddDomainModal />\n      <div className=\"bg-gray-50 flex-1\">\n        <div className=\"flex h-36 items-center border-b border-gray-200 bg-white\">\n          <div className=\"mx-auto w-full max-w-screen-xl px-2.5 lg:px-20\">\n            <div className=\"flex items-center justify-between\">\n              <div className=\"flex items-center space-x-2\">\n                <h1 className=\"text-2xl text-gray-600\">Domains</h1>\n              </div>\n              <Button\n                onClick={() => set(\"domainModal\", \"true\")}\n                variant=\"default\"\n              >\n                Add Domain\n              </Button>\n            </div>\n          </div>\n        </div>\n        <div className=\"mx-auto w-full max-w-screen-xl px-2.5 lg:px-20 py-10\">\n          {children}\n        </div>\n      </div>\n    </>\n  );\n}\n"
  },
  {
    "path": "app/(aipage)/profile/projects/[id]/domains/page.tsx",
    "content": "import DomainCard from \"@/components/DomainCard\";\nimport { toReversed } from \"@/utils/helpers\";\nimport { fetchProjectById } from \"@/utils/auth\";\nimport { SetProject } from \"@/hooks/useProject\";\n\nexport default async function ProjectDomains({\n  params,\n}: {\n  params: { id: string };\n}) {\n  const project = await fetchProjectById(params.id);\n  const hasDomains = !!(project && project?.domains.length > 0);\n\n  return (\n    <div className=\"flex flex-col gap-4\">\n      <SetProject project={project} />\n      {hasDomains ? (\n        toReversed(project?.domains).map((domain) => (\n          <DomainCard domain={domain} key={domain._id} />\n        ))\n      ) : (\n        <div className=\"flex flex-1 flex-col items-center justify-center rounded-md border border-gray-200 bg-white py-12\">\n          <h2 className=\"z-10 text-xl font-semibold text-gray-700\">\n            No domains yet\n          </h2>\n          <img\n            alt=\"No links yet\"\n            loading=\"lazy\"\n            width={500}\n            className=\"pointer-events-none blur-0\"\n            src=\"/no-project.png\"\n          />\n        </div>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(aipage)/profile/projects/[id]/integrations/page.tsx",
    "content": "export default function ProjectIntegrations({\n  params,\n}: {\n  params: {\n    id: string;\n  };\n}) {\n  return <div></div>;\n}\n"
  },
  {
    "path": "app/(aipage)/profile/projects/[id]/layout.tsx",
    "content": "import { ReactNode } from \"react\";\nimport { fetchProjectById } from \"@/utils/auth\";\nimport { notFound } from \"next/navigation\";\nimport { SetProject } from \"@/hooks/useProject\";\n\nexport default async function ProjectLayout({\n  children,\n  params,\n}: {\n  children: ReactNode;\n  params: {\n    id: string;\n  };\n}) {\n  const project = await fetchProjectById(params.id);\n  if (!project || project?.deletedAt) return notFound();\n\n  return (\n    <>\n      <SetProject project={project} />\n      {children}\n    </>\n  );\n}\n"
  },
  {
    "path": "app/(aipage)/profile/projects/[id]/loading.tsx",
    "content": "import LoadingSpinner from \"@/components/loadingSpinner\";\n\nexport default function ProjectByIdLoading() {\n  return (\n    <div className=\"w-full mx-auto flex h-full items-center justify-center max-w-screen-xl p-6\">\n      <LoadingSpinner />\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(aipage)/profile/projects/[id]/page.tsx",
    "content": "import { fetchProjectById, fetchProjects } from \"@/utils/auth\";\nimport { SetProjects } from \"@/hooks/useProjectList\";\nimport { SetProject } from \"@/hooks/useProject\";\nimport { FormEvent } from \"react\";\nimport {\n  Sheet,\n  SheetClose,\n  SheetContent,\n  SheetFooter,\n  SheetHeader,\n  SheetTitle,\n} from \"@/components/Drawer\";\nimport Button from \"@/components/Button\";\nimport ProjectDesign from \"@/components/ProjectDesign\";\n\nexport const revalidate = 0;\n\nexport default async function ProjectDetail({\n  params,\n}: {\n  params: { id: string };\n}) {\n  const projects = await fetchProjects();\n  const project = await fetchProjectById(params.id);\n\n  return (\n    <div className=\"mx-auto project-detail-page grid flex-1 w-full max-w-screen-2xl py-4 sm:py-6 px-2.5 lg:px-20\">\n      <SetProjects projects={projects ?? []} />\n      <SetProject project={project ?? null} />\n      <div className=\"h-full flex-1 grid\">\n        <div className=\"flex-1\">\n          {project?.result ? (\n            <ProjectDesign project={project} />\n          ) : (\n            <div className=\"flex-1 h-full flex items-center justify-center text-gray-400\">\n              No HTML to display\n            </div>\n          )}\n        </div>\n      </div>\n    </div>\n  );\n}\n\ninterface PanelProps {\n  open: boolean;\n  onOpenChange?: (open: boolean) => void;\n  selected?: HTMLElement | null;\n  onSave: (values: [string, string][]) => void;\n}\nfunction Panel(props: PanelProps) {\n  const { open, onOpenChange, selected, onSave } = props;\n  const includedKeys = [\n    \"padding\",\n    \"color\",\n    \"width\",\n    \"height\",\n    \"margin\",\n    \"fontSize\",\n    \"display\",\n    \"position\",\n    \"top\",\n    \"left\",\n    \"right\",\n    \"bottom\",\n    \"border\",\n    \"background\",\n    \"transform\",\n  ];\n\n  function onSubmit(e: FormEvent) {\n    e.preventDefault();\n    if (!selected) return;\n    const formData = new FormData(e.target as HTMLFormElement);\n    onSave?.(Array.from(formData.entries()) as [string, string][]);\n  }\n\n  return (\n    <Sheet open={open} onOpenChange={onOpenChange}>\n      <SheetContent className=\"px-0\">\n        <form\n          onSubmit={onSubmit}\n          className=\"grid gap-0 max-h-full grid-rows-[auto_1fr_60px] px-0 h-full\"\n        >\n          <SheetHeader className=\"sticky border-b px-6 py-4 top-0 bg-white\">\n            <SheetTitle>Edit selected section</SheetTitle>\n          </SheetHeader>\n          <div className=\"flex flex-col gap-2 px-6 py-4 overflow-y-auto\">\n            <div>\n              <label>Text Content</label>\n              <input\n                name=\"textContent\"\n                className=\"border-gray-200 rounded w-full\"\n                type=\"text\"\n                defaultValue={selected?.innerText}\n              />\n            </div>\n            {selected &&\n              Object.entries(getComputedStyle(selected))\n                .filter(\n                  ([key]) =>\n                    !key.match(/\\d/) &&\n                    includedKeys.includes(key) &&\n                    !key.startsWith(\"webkit\"),\n                )\n                .map(([key, value]) => (\n                  <div key={key}>\n                    <label>{key}</label>\n                    <input\n                      name={key}\n                      className=\"border-gray-200 rounded w-full\"\n                      type=\"text\"\n                      defaultValue={value}\n                    />\n                  </div>\n                ))}\n          </div>\n          <SheetFooter className=\"px-6 py-4 border-t flex items-center\">\n            <SheetClose asChild>\n              <Button type=\"submit\">Save changes</Button>\n            </SheetClose>\n          </SheetFooter>\n        </form>\n      </SheetContent>\n    </Sheet>\n  );\n}\n"
  },
  {
    "path": "app/(aipage)/profile/projects/[id]/settings/page.tsx",
    "content": "\"use client\";\n\nimport useProject from \"@/hooks/useProject\";\nimport { FormEvent, useEffect, useState } from \"react\";\nimport { cn, updateProject } from \"@/utils/helpers\";\nimport NavLink from \"@/components/NavLink\";\nimport Button from \"@/components/Button\";\nimport DeleteProjectConfirmDialog from \"@/components/DeleteProjectConfirmDialog\";\nimport LoadingSpinner from \"@/components/loadingSpinner\";\nimport { useRouter } from \"next/navigation\";\n\nexport default function ProjectSettingsPage() {\n  const { setProject, project } = useProject();\n  const [name, setName] = useState(project?.name);\n  const [loading, setLoading] = useState(false);\n  const { refresh } = useRouter();\n\n  async function onNameFormSubmit(event: FormEvent) {\n    event.preventDefault();\n    if (!name || name === project?.name) return;\n    setLoading(true);\n\n    const { message: projectFromAPI } = await updateProject(\n      {\n        name,\n      },\n      project?._id as string,\n    );\n\n    setLoading(false);\n    setProject(projectFromAPI);\n    refresh();\n  }\n\n  return (\n    <div className=\"w-full max-w-screen-xl p-6 grid items-start gap-5 md:grid-cols-5\">\n      <div className=\"flex gap-1 md:grid\">\n        <NavLink\n          className={cn(\n            \"rounded-md p-2.5 text-sm transition-all duration-75 hover:bg-gray-100 active:bg-gray-200 font-semibold text-black\",\n            \"data-[active]:bg-gray-100 data-[active]:active:bg-gray-200\",\n          )}\n          href={`/profile/projects/${project?._id}/settings`}\n        >\n          General\n        </NavLink>\n      </div>\n      <section className=\"grid gap-5 md:col-span-4\">\n        <form\n          className=\"rounded-lg border border-gray-200 bg-white\"\n          onSubmit={onNameFormSubmit}\n        >\n          <div className=\"relative flex flex-col space-y-6 p-5 sm:p-10\">\n            <div className=\"flex flex-col space-y-3\">\n              <h2 className=\"text-xl font-medium\">Project Name</h2>\n              <p className=\"text-sm text-gray-500\">\n                This will be your project name on AIPage.dev\n              </p>\n            </div>\n            <input\n              maxLength={32}\n              type=\"text\"\n              className=\"w-full max-w-md rounded-md border border-gray-300 text-gray-900 placeholder-gray-300 focus:border-gray-500 focus:outline-none focus:ring-gray-500 sm:text-sm\"\n              value={name}\n              onChange={(e) => setName(e.target.value)}\n            />\n          </div>\n          <div className=\"flex items-center justify-between rounded-b-lg border-t border-gray-200 bg-gray-50 p-3\">\n            <p className=\"text-sm text-gray-500\">Max 32 characters.</p>\n            <div>\n              <Button\n                disabled={name === project?.name}\n                type=\"submit\"\n                variant=\"default\"\n              >\n                {loading && <LoadingSpinner />}\n                <p>Save Changes</p>\n              </Button>\n            </div>\n          </div>\n        </form>\n        <div className=\"rounded-lg border border-red-600 bg-white\">\n          <div className=\"flex flex-col space-y-3 p-5 sm:p-10\">\n            <h2 className=\"text-xl font-medium\">Delete Project</h2>\n            <p className=\"text-sm text-gray-500\">\n              Permanently delete your AIPage.dev project\n            </p>\n          </div>\n          <div className=\"border-b border-red-600\" />\n          <div className=\"flex items-center justify-end p-3\">\n            <div>\n              <DeleteProjectConfirmDialog project={project} />\n            </div>\n          </div>\n        </div>\n      </section>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(aipage)/profile/projects/loading.tsx",
    "content": "import LoadingSpinner from \"@/components/loadingSpinner\";\n\nexport default function ProjectsLoading() {\n  return (\n    <div className=\"w-ful mx-auto l flex h-full items-center justify-center max-w-screen-xl p-6\">\n      <LoadingSpinner />\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(aipage)/profile/projects/page.tsx",
    "content": "import { cn } from \"@/utils/helpers\";\nimport Link from \"next/link\";\nimport Button from \"@/components/Button\";\nimport ListProjects from \"@/components/ListProjects\";\nimport { fetchProjects } from \"@/utils/auth\";\n\nexport const revalidate = 0;\n\nexport default async function ProfileProjects() {\n  const projects = await fetchProjects();\n  const hasProjects = !!projects && projects.length > 0;\n\n  return (\n    <div className=\"w-full px-6 py-6 bg-gray-50 h-full flex-1 flex flex-col\">\n      <div\n        className={cn(\n          \"mx-auto w-full sm:max-w-screen-2xl sm:px-2.5 lg:px-20\",\n          !hasProjects && \"flex-1 flex flex-col\",\n        )}\n      >\n        <div\n          className={cn(\n            hasProjects\n              ? \"grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-5\"\n              : \"flex flex-col flex-1\",\n          )}\n        >\n          {!hasProjects ? (\n            <div className=\"flex flex-1 flex-col items-center justify-center rounded-md border border-gray-200 bg-white py-12\">\n              <h2 className=\"z-10 text-xl font-semibold text-gray-700\">\n                You don't have any projects yet!\n              </h2>\n              <img\n                alt=\"No links yet\"\n                loading=\"lazy\"\n                width={500}\n                className=\"pointer-events-none blur-0\"\n                src=\"/no-project.png\"\n              />\n              <Link href=\"/\">\n                <Button variant=\"pill\">Create a project</Button>\n              </Link>\n            </div>\n          ) : (\n            <ListProjects projects={projects} />\n          )}\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(aipage)/profile/settings/loading.tsx",
    "content": "import LoadingSpinner from \"@/components/loadingSpinner\";\n\nexport default function SettingsLoading() {\n  return (\n    <div className=\"w-ful mx-auto l flex h-full items-center justify-center max-w-screen-xl p-6\">\n      <LoadingSpinner />\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(aipage)/profile/settings/page.tsx",
    "content": "\"use client\";\nimport { useAuth } from \"@/context/AuthContext\";\nimport { useState, FormEvent } from \"react\";\nimport LoadingSpinner from \"@/components/loadingSpinner\";\nimport Button from \"@/components/Button\";\nimport { cn } from \"@/utils/helpers\";\nimport DeleteAccountConfirmDialog from \"@/components/DeleteAccountConfirmDialog\";\nimport NavLink from \"@/components/NavLink\";\n\nexport default function ProfileSettings() {\n  const { user, setUser } = useAuth();\n  const [name, setName] = useState(user?.name);\n  const [loading, setLoading] = useState(false);\n\n  async function onNameFormSubmit(event: FormEvent) {\n    event.preventDefault();\n    if (!name || name === user?.name) return;\n    setLoading(true);\n    const res = await fetch(\"/api/user\", {\n      method: \"PATCH\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n      },\n      body: JSON.stringify({ name }),\n    });\n    const { user: userFromAPI, errors } = await res.json();\n    setLoading(false);\n    if (!errors) {\n      setUser(userFromAPI);\n    }\n  }\n\n  return (\n    <div className=\"w-full max-w-screen-xl p-6 grid items-start gap-5 md:grid-cols-5\">\n      <div className=\"flex gap-1 md:grid\">\n        <NavLink\n          className={cn(\n            \"rounded-md p-2.5 text-sm transition-all duration-75 hover:bg-gray-100 active:bg-gray-200 font-semibold text-black\",\n            \"data-[active]:bg-gray-100 data-[active]:active:bg-gray-200\",\n          )}\n          href=\"/profile/settings\"\n        >\n          General\n        </NavLink>\n      </div>\n      <div className=\"grid gap-5 md:col-span-4\">\n        <form\n          className=\"rounded-lg border border-gray-200 bg-white\"\n          onSubmit={onNameFormSubmit}\n        >\n          <div className=\"relative flex flex-col space-y-6 p-5 sm:p-10\">\n            <div className=\"flex flex-col space-y-3\">\n              <h2 className=\"text-xl font-medium\">Your Name</h2>\n              <p className=\"text-sm text-gray-500\">\n                This will be your display name on AIPage.dev\n              </p>\n            </div>\n            <input\n              name=\"name\"\n              placeholder=\"Steve Jobs\"\n              maxLength={32}\n              type=\"text\"\n              required\n              className=\"w-full max-w-md rounded-md border border-gray-300 text-gray-900 placeholder-gray-300 focus:border-gray-500 focus:outline-none focus:ring-gray-500 sm:text-sm\"\n              value={name}\n              onChange={(e) => setName(e.target.value)}\n            />\n          </div>\n          <div className=\"flex items-center justify-between rounded-b-lg border-t border-gray-200 bg-gray-50 p-3\">\n            <p className=\"text-sm text-gray-500\">Max 32 characters.</p>\n            <div>\n              <Button\n                disabled={name === user?.name}\n                type=\"submit\"\n                variant=\"default\"\n              >\n                {loading && <LoadingSpinner />}\n                <p>Save Changes</p>\n              </Button>\n            </div>\n          </div>\n        </form>\n        <div className=\"rounded-lg border border-red-600 bg-white\">\n          <div className=\"flex flex-col space-y-3 p-5 sm:p-10\">\n            <h2 className=\"text-xl font-medium\">Delete Account</h2>\n            <p className=\"text-sm text-gray-500\">\n              Permanently delete your AIPage.dev account and all of your data.\n              This action cannot be undone - please proceed with caution.\n            </p>\n          </div>\n          <div className=\"border-b border-red-600\" />\n          <div className=\"flex items-center justify-end p-3\">\n            <div>\n              <DeleteAccountConfirmDialog />\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(preview)/not-found.tsx",
    "content": "import Link from \"next/link\";\nimport Button from \"@/components/Button\";\n\nexport default async function NotFound() {\n  return (\n    <section className=\"bg-white fixed inset-0\">\n      <div className=\"container flex items-center min-h-screen px-6 py-12 mx-auto\">\n        <div>\n          <p className=\"text-2xl font-medium text-blue-500\">404 NOT FOUND</p>\n          <h1 className=\"mt-3 text-3xl font-semibold text-gray-800 md:text-4xl\">\n            We can’t find that page\n          </h1>\n          <p className=\"mt-4 text-gray-500\">\n            Sorry, the page you are looking for doesn't exist or has been moved.\n          </p>\n\n          <div className=\"flex items-center mt-6 gap-x-3\">\n            <Link href=\"/profile/projects\">\n              <Button variant=\"pill\">Go to projects</Button>\n            </Link>\n          </div>\n        </div>\n      </div>\n    </section>\n  );\n}\n"
  },
  {
    "path": "app/(preview)/profile/projects/[id]/preview/loading.tsx",
    "content": "import LoadingSpinner from \"@/components/loadingSpinner\";\n\nexport default function PreviewLoading() {\n  return (\n    <div className=\"h-screen w-screen flex items-center justify-center\">\n      <LoadingSpinner />\n    </div>\n  );\n}\n"
  },
  {
    "path": "app/(preview)/profile/projects/[id]/preview/page.tsx",
    "content": "import { fetchProjectById } from \"@/utils/auth\";\nimport { notFound } from \"next/navigation\";\nimport HTMLPreview from \"@/components/HTMLPreview\";\n\nexport default async function projectPreview({\n  params,\n}: {\n  params: { id: string };\n}) {\n  const project = await fetchProjectById(params.id);\n  if (!project || !project.result) return notFound();\n  return <HTMLPreview html={project.result} />;\n}\n"
  },
  {
    "path": "app/api/chat/route.ts",
    "content": "import { Configuration, OpenAIApi } from \"openai-edge\";\nimport { Ratelimit } from \"@upstash/ratelimit\";\nimport redis from \"../../../utils/redis\";\nimport { OpenAIStream, StreamingTextResponse } from \"ai\";\nimport { headers, cookies } from \"next/headers\";\nimport { NextResponse } from \"next/server\";\n\n/* // REMOVE THIS IF YOU DON'T WANT RATE LIMITING\n// START\nconst ratelimit = redis\n  ? new Ratelimit({\n      redis: redis,\n      limiter: Ratelimit.fixedWindow(5, \"1440 m\"),\n      analytics: true,\n    })\n  : undefined;\n\n// END\n */\nconst config = new Configuration({\n  apiKey: process.env.OPENAI_API_KEY,\n});\n\nconst openai = new OpenAIApi(config);\n\nexport const runtime = \"edge\";\n\nexport async function POST(req: Request) {\n  const cookieStore = cookies();\n  /*   // REMOVE THIS IF YOU DON'T WANT RATE LIMITING\n  // START\n  if (ratelimit) {\n    const headersList = headers();\n    const ipIdentifier = headersList.get(\"x-real-ip\");\n\n    const result = await ratelimit.limit(ipIdentifier ?? \"\");\n\n    if (!result.success) {\n      const fakeStream =\n        \"Too many requests in 1 day. Please try again in a 24 hours. Thank you. 🙏\";\n      return new Response(fakeStream, {\n        status: 429,\n        headers: {\n          \"X-RateLimit-Limit\": result.limit,\n          \"X-RateLimit-Remaining\": result.remaining,\n        } as any,\n      });\n    }\n  }\n  // END */\n\n  const { messages } = await req.json();\n\n  // Implemented for to test the API\n\n  const sessionToken = cookieStore.get(\"sessionToken\")?.value as string;\n\n  const storeMessage = await fetch(\n    \"https://c3-na.altogic.com/e:64d52ccfc66bd54b97bdd78a/test\",\n    {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n        Session: sessionToken,\n      },\n      body: JSON.stringify({ content: messages[0].content }),\n    },\n  );\n\n  const { credits } = await storeMessage.json();\n\n  if (credits === 0) {\n    return NextResponse.json({ code: \"no-credits\", credits });\n  }\n\n  const systemPrompt = `You are a talented UI designer who needs help creating a clear and concise HTML UI using Tailwind CSS. The UI should be visually appealing and responsive. Please design a UI component that includes the following elements:\n\n1. A header Section: Include a logo and a navigation menu.\n2. A hero Section: Create a captivating headline and a call-to-action button. Use a random image related to the prompt for the background image, generating it with the URL \"https://source.unsplash.com/featured/1280x720/?{description}\" (replace {description} with a relevant keyword).\n3. A feature Section: Showcase three standout feature cards with eye-catching featured icons from the Fontawesome CDN icon library. Apply subtle CSS animations, such as fade-in or slide-in effects using Animate.css, to enhance visual appeal.\n4. An individual Feature Sections: Create a separate section for each feature card. Each section should include a captivating title, description, and a call-to-action button. Use a random image related to the prompt for the background image, generating it with the URL \"https://source.unsplash.com/featured/1280x720/?{description}\" (replace {description} with a relevant keyword). You can float the image to the left or right of the text.\n5. A testimonial Section: Display two testimonials with names, roles, and feedback. Apply a CSS animation, like fade-in or slide-in animation using Animate.css, to reveal testimonials when scrolled into view.\n6. A blog Section: Include a section that displays recent blog posts with a title, short description, and a \"Read More\" link.\n7. An FAQ Section: Add a section for frequently asked questions and answers.\n8. A Team Section: Showcase the team with photos, names, roles, and social media links.\n9. A Newsletter Subscription: Add a section for users to subscribe to a newsletter.\n10. A Contact Form: Create fields for name, email, and message. Apply appropriate CSS animations or transitions using jQuery for smooth interactivity.\n11. A map Section: Include a Google Maps section with a marker showing the location of the business (you may need a Google Maps API key).\n12. A footer Section: Add links to social media profiles, utilizing the Fontawesome CDN icon library for social media icons.\n\nPlease ensure the HTML code is valid and properly structured, incorporating the necessary CDN links for Tailwind CSS, Fontawesome icons, jQuery, Animate.css, Google Maps API, and any additional CSS or JS files.\n\nRemember to keep the design minimalistic, intuitive, and visually appealing. Your attention to detail is highly appreciated. Once you complete the design, provide the HTML code for the UI component. The code should be valid HTML, formatted for readability, and include the necessary CDN links for Tailwind CSS, icons, and any additional libraries used for data visualization.\n\n  Given the prompt, generate the only HTML code for the UI component. The code should be valid HTML and include the necessary CDN links for Tailwind CSS, Fontawesome icons, and any additional CSS and JavaScript files.\n\n  Start with <!DOCTYPE html> and end with </html>. The code should be formatted for readability.`;\n\n  const combinedMessages = [\n    ...messages,\n    { role: \"system\", content: systemPrompt },\n  ];\n\n  let response;\n  let stream;\n\n  response = await openai.createChatCompletion({\n    model: \"gpt-3.5-turbo-16k\",\n    messages: combinedMessages.map((message: any) => ({\n      role: message.role,\n      content: message.content,\n    })),\n    stream: true,\n  });\n\n  stream = OpenAIStream(response);\n  // Continue generating the response if incomplete\n\n  // If rate limited, return a fake response\n  return new StreamingTextResponse(stream);\n}\n"
  },
  {
    "path": "app/api/create-checkout-session/route.ts",
    "content": "import { NextResponse } from \"next/server\";\nimport { cookies } from \"next/headers\";\nimport altogic from \"@/utils/altogic\";\n\nexport async function POST(req: Request) {\n  const { priceId } = await req.json();\n  const cookieStore = cookies();\n  const session = cookieStore.get(\"sessionToken\");\n\n  if (!session) {\n    return NextResponse.json(\n      { error: \"You must be logged in to purchase this product\" },\n      { status: 401 },\n    );\n  }\n\n  // @ts-ignore\n  altogic.auth.setSession({\n    token: session.value,\n  });\n\n  const path = process.env.NEXT_PUBLIC_CREATE_SESSION_PATH as string;\n  const { errors, data } = await altogic.endpoint.post(path, {\n    priceId,\n  });\n\n  if (errors) {\n    return NextResponse.json({ errors }, { status: errors.status });\n  }\n\n  return NextResponse.json({ url: data.url });\n}\n"
  },
  {
    "path": "app/api/domain/check-domain/route.ts",
    "content": "import { NextResponse } from \"next/server\";\n\nexport async function POST(req: Request) {\n  const { domain } = await req.json();\n\n  const [configResponse, domainResponse] = await Promise.all([\n    fetch(`https://api.vercel.com/v6/domains/${domain}/config`, {\n      method: \"GET\",\n      headers: {\n        Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`,\n        \"Content-Type\": \"application/json\",\n      },\n    }),\n    fetch(\n      `https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}/domains/${domain}`,\n      {\n        method: \"GET\",\n        headers: {\n          Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`,\n          \"Content-Type\": \"application/json\",\n        },\n      },\n    ),\n  ]);\n\n  const configJson = await configResponse.json();\n  const domainJson = await domainResponse.json();\n  if (domainResponse.status !== 200) {\n    return NextResponse.json(domainJson, { status: domainResponse.status });\n  }\n\n  /**\n   * If domain is not verified, we try to verify now\n   */\n  let verificationResponse = null;\n  if (!domainJson.verified) {\n    const verificationRes = await fetch(\n      `https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}/domains/${domain}/verify`,\n      {\n        method: \"POST\",\n        headers: {\n          Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`,\n          \"Content-Type\": \"application/json\",\n        },\n      },\n    );\n    verificationResponse = await verificationRes.json();\n  }\n\n  if (verificationResponse && verificationResponse.verified) {\n    /**\n     * Domain was just verified\n     */\n    return NextResponse.json(\n      {\n        configured: !configJson.misconfigured,\n        ...verificationResponse,\n      },\n      { status: 200 },\n    );\n  }\n\n  return NextResponse.json(\n    {\n      configured: !configJson.misconfigured,\n      ...domainJson,\n      ...(verificationResponse ? { verificationResponse } : {}),\n    },\n    { status: 200 },\n  );\n}\n"
  },
  {
    "path": "app/api/domain/remove-domain/route.ts",
    "content": "import { NextResponse } from \"next/server\";\nimport altogic from \"@/utils/altogic\";\nimport { getSessionCookie } from \"@/utils/auth\";\n\nexport async function POST(req: Request) {\n  const { domain } = await req.json();\n\n  console.log({ domain });\n\n  const response = await fetch(\n    `https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}/domains/${domain}`,\n    {\n      headers: {\n        Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`,\n      },\n      method: \"DELETE\",\n    },\n  );\n\n  const json = await response.json();\n\n  console.log(JSON.stringify(json, null, 2));\n  console.log(\"-----\");\n\n  // @ts-ignore\n  altogic.auth.setSession({\n    token: getSessionCookie() as string,\n  });\n\n  const { errors } = await altogic.db\n    .model(\"messages.domains\")\n    .filter(`domain == '${domain}'`)\n    .delete();\n\n  console.log(JSON.stringify(errors, null, 2));\n\n  if (errors) {\n    return NextResponse.json(errors, { status: errors.status });\n  }\n\n  NextResponse.json(json);\n}\n"
  },
  {
    "path": "app/api/domain/verify-domain/route.ts",
    "content": "import { NextResponse } from \"next/server\";\n\nexport async function POST(req: Request) {\n  const { domain } = await req.json();\n\n  const response = await fetch(\n    `https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}/domains/${domain}/verify`,\n    {\n      headers: {\n        Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`,\n        \"Content-Type\": \"application/json\",\n      },\n      method: \"POST\",\n    },\n  );\n\n  const data = await response.json();\n  return NextResponse.json(data, {\n    status: response.status,\n  });\n}\n"
  },
  {
    "path": "app/api/logout/route.ts",
    "content": "import { NextResponse } from \"next/server\";\nimport { logout } from \"@/utils/auth\";\n\nexport async function GET(req: Request) {\n  return logout(req, NextResponse);\n}\n"
  },
  {
    "path": "app/api/message/route.ts",
    "content": "import altogic from \"@/utils/altogic\";\nimport { NextResponse } from \"next/server\";\nimport { cookies } from \"next/headers\";\nimport { revalidatePath } from \"next/cache\";\n\nexport async function PUT(req: Request) {\n  const cookieStore = cookies();\n  const token = cookieStore.get(\"sessionToken\");\n\n  if (!token) {\n    return NextResponse.json(\n      {\n        message: \"You must be logged in to update your project.\",\n      },\n      { status: 401 },\n    );\n  }\n\n  const body = await req.json();\n  // @ts-ignore\n  altogic.auth.setSession({\n    token: token.value,\n  });\n\n  const { data, errors } = await altogic.endpoint.put(\"/message-content\", body);\n\n  if (errors) return NextResponse.json({ errors }, { status: 500 });\n\n  revalidatePath(\"/profile/projects\");\n  return NextResponse.json({ message: data }, { status: 200 });\n}\n"
  },
  {
    "path": "app/api/og/route.tsx",
    "content": "import { ImageResponse } from \"next/server\";\nimport { useState } from \"react\";\n// App router includes @vercel/og.\n// No need to install it.\n\nexport const runtime = \"edge\";\n\nexport async function GET(request: Request) {\n  const tweetIntents = [\n    \"Just used AI to craft an EPIC landing page in minutes with AIpage.dev ! 🤖 This is the future of web design! Check it out 👉 @aipagedev\",\n    \"Creating a stunning webpage has never been easier thanks to AIpage.dev! 🚀 Give it a try 👉 @aipagedev\",\n    \"Web design will never be the same after you try AIpage.dev! 🛠️ A whole new level of creativity unleashed! Check it out 👉 @aipagedev\",\n    \"Revolutionize your web design process with AIpage.dev. The future is here! 👉 @aipagedev\",\n    \"I just built an amazing webpage with AIpage.dev in minutes! 🌟 You have to try this 👉 @aipagedev\",\n    \"AIpage.dev is a game-changer for web design! Say hello to efficiency 👋 @aipagedev\",\n    \"Why spend hours on web design when AIpage.dev can do it in minutes? 🕒 Check it out! 👉 @aipagedev\",\n    \"Impressed by the power of AI in web design with AIpage.dev! This is incredible 👀 @aipagedev\",\n    \"I used AIpage.dev and it completely transformed how I approach web design. You need to try this! 🎉 @aipagedev\",\n    \"Just when I thought web design couldn’t get any easier, I found AIpage.dev! 🎊 Try it now 👉 @aipagedev\",\n    \"Unleashing my inner designer with the help of AIpage.dev. This is next level! 🚀 Check it out 👉 @aipagedev\",\n    \"With AIpage.dev, I can focus on creativity while AI handles the coding. It’s amazing! 💥 @aipagedev\",\n  ];\n\n  // Function to generate a random index for selecting a tweet text\n  const getRandomIndex = () => {\n    return Math.floor(Math.random() * tweetIntents.length);\n  };\n\n  // Function to generate a random tweet text\n  const getRandomTweet = () => {\n    return tweetIntents[getRandomIndex()];\n  };\n\n  const text = getRandomTweet();\n\n  return new ImageResponse(\n    (\n      <div\n        style={{\n          display: \"flex\",\n          height: \"100%\",\n          width: \"100%\",\n          alignItems: \"center\",\n          justifyContent: \"center\",\n          flexDirection: \"column\",\n          backgroundImage: \"linear-gradient(to bottom, #7149b6, #715dd3)\",\n          fontSize: 40,\n          letterSpacing: -2,\n          fontWeight: 700,\n          textAlign: \"center\",\n        }}\n      >\n        <div\n          style={{\n            height: \"100%\",\n            width: \"100%\",\n            display: \"flex\",\n            textAlign: \"center\",\n            alignItems: \"center\",\n            justifyContent: \"center\",\n            flexDirection: \"column\",\n            flexWrap: \"nowrap\",\n            backgroundColor: \"transparent\",\n            backgroundImage:\n              \"radial-gradient(circle at 25px 25px, lightgray 2%, transparent 0%), radial-gradient(circle at 75px 75px, lightgray 2%, transparent 0%)\",\n            backgroundSize: \"100px 100px\",\n          }}\n        >\n          <div\n            style={{\n              display: \"flex\",\n              alignItems: \"center\",\n              justifyContent: \"center\",\n            }}\n          >\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              width=\"200\"\n              height=\"150\"\n              viewBox=\"0 0 200 150\"\n              style={{ margin: \"0 75px\" }}\n            >\n              <rect\n                x=\"10\"\n                y=\"10\"\n                width=\"180\"\n                height=\"130\"\n                rx=\"10\"\n                ry=\"10\"\n                fill=\"#F0F0F0\"\n                stroke=\"#CCCCCC\"\n              />\n\n              <rect\n                x=\"10\"\n                y=\"10\"\n                width=\"180\"\n                height=\"30\"\n                rx=\"10\"\n                ry=\"10\"\n                fill=\"#333333\"\n              />\n\n              <circle cx=\"25\" cy=\"25\" r=\"4\" fill=\"#FF605C\" />\n              <circle cx=\"40\" cy=\"25\" r=\"4\" fill=\"#FFBD44\" />\n              <circle cx=\"55\" cy=\"25\" r=\"4\" fill=\"#28CA41\" />\n\n              <rect\n                x=\"20\"\n                y=\"50\"\n                width=\"160\"\n                height=\"30\"\n                rx=\"10\"\n                ry=\"10\"\n                fill=\"#FFFFFF\"\n              />\n              <rect\n                x=\"25\"\n                y=\"58\"\n                width=\"120\"\n                rx=\"5\"\n                ry=\"5\"\n                height=\"14\"\n                fill=\"#F0F0F0\"\n              />\n\n              <rect\n                x=\"20\"\n                y=\"90\"\n                width=\"50\"\n                rx=\"10\"\n                ry=\"10\"\n                height=\"40\"\n                fill=\"#ddd\"\n              />\n              <rect\n                x=\"75\"\n                y=\"90\"\n                width=\"50\"\n                rx=\"10\"\n                ry=\"10\"\n                height=\"40\"\n                fill=\"#ddd\"\n              />\n              <rect\n                x=\"130\"\n                y=\"90\"\n                width=\"50\"\n                rx=\"5\"\n                ry=\"10\"\n                height=\"40\"\n                fill=\"#ddd\"\n              />\n\n              <circle cx=\"165\" cy=\"64\" r=\"8\" fill=\"#F0F0F0\" />\n            </svg>\n          </div>\n\n          <div\n            style={{\n              color: \"white\",\n              width: \"70%\",\n              paddingTop: \"24px\",\n              textAlign: \"center\",\n              WebkitBackgroundClip: \"text\",\n            }}\n          >\n            {text}\n          </div>\n        </div>\n      </div>\n    ),\n    {\n      width: 1200,\n      height: 630,\n    }\n  );\n}\n"
  },
  {
    "path": "app/api/project/[id]/domain/route.ts",
    "content": "import { NextResponse } from \"next/server\";\nimport altogic from \"@/utils/altogic\";\nimport { getSessionCookie } from \"@/utils/auth\";\n\nexport async function POST(\n  req: Request,\n  { params }: { params: { id: string } },\n) {\n  const { domain, isPrimary } = await req.json();\n\n  // @ts-ignore\n  altogic.auth.setSession({\n    token: getSessionCookie() as string,\n  });\n\n  try {\n    const { data, errors } = await altogic.endpoint.post(\n      \"/add-domain\",\n      {\n        domain,\n        isPrimary,\n        projectId: params.id,\n      },\n      undefined,\n      {\n        Session: getSessionCookie(),\n      },\n    );\n\n    if (errors) {\n      return NextResponse.json({ errors }, { status: errors.status });\n    }\n\n    const response = await fetch(\n      `https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}/domains`,\n      {\n        body: JSON.stringify({ name: domain }),\n        headers: {\n          Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`,\n          \"Content-Type\": \"application/json\",\n        },\n        method: \"POST\",\n      },\n    );\n\n    const domainData = await response.json();\n\n    if (domainData.error?.code == \"forbidden\") {\n      return NextResponse.json(\n        {\n          error: domainData.error,\n        },\n        { status: 403 },\n      );\n    } else if (domainData.error?.code == \"domain_taken\") {\n      return NextResponse.json(\n        {\n          error: domainData.error,\n        },\n        { status: 409 },\n      );\n    }\n\n    return NextResponse.json({ data }, { status: 200 });\n  } catch (error) {\n    return NextResponse.json({}, { status: 500 });\n  }\n}\n"
  },
  {
    "path": "app/api/project/[id]/route.ts",
    "content": "import { deleteProject } from \"@/utils/auth\";\nimport { NextResponse } from \"next/server\";\nimport { revalidatePath } from \"next/cache\";\n\nexport async function DELETE(\n  req: Request,\n  { params }: { params: { id: string } },\n) {\n  try {\n    const { errors } = await deleteProject(params.id);\n\n    if (errors) {\n      return NextResponse.json({ errors }, { status: 500 });\n    }\n\n    revalidatePath(\"/profile/projects\");\n    return NextResponse.json({ status: \"ok\" }, { status: 200 });\n  } catch (error) {\n    return NextResponse.json({}, { status: 500 });\n  }\n}\n"
  },
  {
    "path": "app/api/project/route.ts",
    "content": "import { deleteProject, updateProjectName } from \"@/utils/auth\";\nimport { NextResponse } from \"next/server\";\n\nexport async function PATCH(req: Request) {\n  const { name, id } = await req.json();\n\n  try {\n    const { project, errors } = await updateProjectName(id, name);\n\n    if (errors) {\n      return NextResponse.json({ errors }, { status: 500 });\n    }\n\n    return NextResponse.json({ project }, { status: 200 });\n  } catch (error) {\n    return NextResponse.json({}, { status: 500 });\n  }\n}\n"
  },
  {
    "path": "app/api/user/route.ts",
    "content": "import { deleteUser, logout, updateUser } from \"@/utils/auth\";\nimport { NextResponse } from \"next/server\";\n\nexport async function PATCH(req: Request) {\n  const { data, errors } = await updateUser(await req.json());\n\n  if (errors) {\n    return NextResponse.json({ errors }, { status: 500 });\n  }\n\n  return NextResponse.json({ user: data }, { status: 200 });\n}\n\nexport async function DELETE(req: Request) {\n  const { errors } = await deleteUser();\n\n  if (errors) {\n    return NextResponse.json({ errors }, { status: 500 });\n  }\n\n  return logout(req, NextResponse);\n}\n"
  },
  {
    "path": "app/auth-redirect/route.ts",
    "content": "import altogic from \"@/utils/altogic\";\nimport { NextResponse } from \"next/server\";\n\nexport async function GET(req: Request) {\n  const url = new URL(req.url);\n  const accessToken = url.searchParams.get(\"access_token\") as string;\n  const status = url.searchParams.get(\"status\");\n  const isOk = status === \"200\";\n\n  const destinationUrl = new URL(\"/\", new URL(req.url).origin);\n  const response = NextResponse.redirect(destinationUrl, { status: 302 });\n\n  if (!isOk) return response;\n\n  const { session, errors } = await altogic.auth.getAuthGrant(accessToken);\n\n  if (errors) {\n    // TODO: handle errors;\n  }\n\n  if (session) {\n    response.cookies.set(\"sessionToken\", session.token, {\n      path: \"/\",\n      httpOnly: true,\n      maxAge: 60 * 60 * 24 * 7, // 1 week\n    });\n  }\n\n  return response;\n}\n"
  },
  {
    "path": "app/layout.tsx",
    "content": "import AuthModal from \"@/components/AuthModal\";\nimport PricesModal from \"@/components/PricesModal\";\nimport { AuthProvider } from \"@/context/AuthContext\";\nimport { Project } from \"@/types\";\nimport { fetchAuthUser, fetchProducts, getProjectByDomain } from \"@/utils/auth\";\nimport { isAipage } from \"@/utils/helpers\";\nimport { Metadata } from \"next\";\nimport { Inter } from \"next/font/google\";\nimport { headers } from \"next/headers\";\nimport { ReactNode } from \"react\";\nimport \"../styles/globals.css\";\nconst inter = Inter({ subsets: [\"latin\"] });\n\nexport async function generateMetadata(): Promise<Metadata> {\n  const isAipageDomain = isAipage(headers().get(\"Host\") as string);\n  if (!isAipageDomain) {\n    const project = await getProjectByDomain(headers().get(\"Host\") as string);\n    if (project) return {};\n  }\n\n  return {\n    title: \"AIPage.dev - An AI-Powered Landing Page Generator | by @zinedkaloc\",\n    description:\n      \"AI-Powered Landing Page Generator. Experience the Open Source Project that Empowers You to Build Stunning Landing Pages Instantly\",\n    openGraph: {\n      title:\n        \"AIPage.dev - An AI-Powered Landing Page Generator | by @zinedkaloc\",\n      description:\n        \"AI-Powered Landing Page Generator. Experience the Open Source Project that Empowers You to Build Stunning Landing Pages Instantly\",\n      type: \"website\",\n      url: \"https://aipage.dev\",\n      images: `${process.env.NEXT_PUBLIC_DOMAIN}/api/og?text=${new Date()\n        .getTime()\n        .toString()}`,\n    },\n  };\n}\n\nexport default async function RootLayout({\n  children,\n}: {\n  children: ReactNode;\n}) {\n  const isAipageDomain = isAipage(headers().get(\"Host\") as string);\n  const user = await fetchAuthUser();\n  const products = await fetchProducts();\n  let project: Project | null = null;\n\n  if (!isAipageDomain) {\n    project = await getProjectByDomain(headers().get(\"Host\") as string);\n  }\n\n  if (!isAipageDomain && project?.result) {\n    return require(\"html-react-parser\")(project?.result);\n  }\n\n  return (\n    <AuthProvider user={user ?? null}>\n      <html lang=\"en\">\n        <body className={inter.className}>\n          {children}\n          <AuthModal />\n          <PricesModal products={products} />\n        </body>\n      </html>\n    </AuthProvider>\n  );\n}\n"
  },
  {
    "path": "app/not-found.tsx",
    "content": "import Link from \"next/link\";\nimport Button from \"@/components/Button\";\n\nexport default async function NotFound() {\n  return (\n    <section className=\"bg-white fixed inset-0\">\n      <div className=\"container flex items-center min-h-screen px-6 py-12 mx-auto\">\n        <div>\n          <p className=\"text-2xl font-medium text-blue-500\">404 NOT FOUND</p>\n          <h1 className=\"mt-3 text-3xl font-semibold text-gray-800 md:text-4xl\">\n            We can’t find that page\n          </h1>\n          <p className=\"mt-4 text-gray-500\">\n            Sorry, the page you are looking for doesn't exist or has been moved.\n          </p>\n\n          <div className=\"flex items-center mt-6 gap-x-3\">\n            <Link href=\"/\">\n              <Button variant=\"pill\">Take me home</Button>\n            </Link>\n          </div>\n        </div>\n      </div>\n    </section>\n  );\n}\n"
  },
  {
    "path": "components/AddDomainModal.tsx",
    "content": "\"use client\";\nimport useSearchParams from \"@/hooks/useSearchParams\";\nimport Modal from \"@/components/Modal\";\nimport { FormEvent, useState } from \"react\";\nimport Button from \"@/components/Button\";\nimport LoadingSpinner from \"@/components/loadingSpinner\";\nimport Image from \"next/image\";\nimport Switch from \"@/components/Switch\";\nimport { useParams, useRouter } from \"next/navigation\";\nimport useProject from \"@/hooks/useProject\";\nimport { APIError } from \"altogic\";\n\nexport default function AddDomainModal() {\n  const { deleteByKey, has } = useSearchParams();\n  const [loading, setLoading] = useState(false);\n  const [hasError, setHasError] = useState(false);\n  const [isPrimary, setIsPrimary] = useState(false);\n  const [domain, setDomain] = useState(\"\");\n  const [error, setError] = useState<APIError | null>(null);\n  const { id } = useParams();\n  const { addDomain } = useProject();\n  const { refresh } = useRouter();\n\n  function close() {\n    setIsPrimary(false);\n    setDomain(\"\");\n    deleteByKey(\"domainModal\");\n    setError(null);\n    setHasError(false);\n  }\n\n  async function onSubmit(e: FormEvent) {\n    e.preventDefault();\n\n    const data = {\n      domain,\n      isPrimary,\n    };\n\n    setLoading(true);\n    setHasError(false);\n    try {\n      const res = await fetch(`/api/project/${id}/domain`, {\n        method: \"POST\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n        },\n        body: JSON.stringify(data),\n      });\n\n      const { errors, data: domainFromAPI } = await res.json();\n\n      if (errors) {\n        setHasError(true);\n        setError(errors);\n      } else {\n        close();\n        addDomain(domainFromAPI);\n        refresh();\n      }\n    } catch {\n      setHasError(true);\n    } finally {\n      setLoading(false);\n    }\n  }\n\n  return (\n    <Modal\n      close={close}\n      isOpen={has(\"domainModal\")}\n      className=\"p-0 sm:w-[400px] w-[96%]\"\n    >\n      <form onSubmit={onSubmit}>\n        <div className=\"flex flex-col items-center justify-center space-y-3 border-gray-200 bg-white px-4 py-6 pt-8 text-center sm:px-10\">\n          <Image\n            src=\"/logoa.png\"\n            alt=\"AIPage.dev logo\"\n            width={150}\n            height={150}\n            draggable={false}\n            className=\"mx-auto h-20 w-20\"\n          />\n          <h3 className=\"text-lg font-semibold\">Add Domain</h3>\n        </div>\n        <div className=\"bg-gray-50 p-4 space-y-4 border-t\">\n          <div className=\"space-y-2\">\n            <label\n              htmlFor=\"domain\"\n              className=\"block text-sm font-medium text-gray-700\"\n            >\n              Domain\n            </label>\n            <div className=\"relative rounded-md shadow-sm\">\n              <input\n                type=\"text\"\n                name=\"domain\"\n                value={domain}\n                onChange={(e) => setDomain(e.target.value)}\n                autoComplete=\"off\"\n                pattern=\"[a-z0-9]+\\.[a-zA-Z]{2,}\"\n                className=\"border-gray-300 text-gray-900 placeholder-gray-300 focus:border-gray-500 focus:ring-gray-500 block w-full rounded-md focus:outline-none sm:text-sm\"\n                placeholder=\"aipage.dev\"\n                required\n              />\n            </div>\n            <div className=\"text-[11px] text-gray-400\">\n              Please enter a valid domain name (e.g. aipage.dev)\n            </div>\n          </div>\n          {/*\n          <div className=\"flex items-center justify-between bg-gray-50 my-4\">\n            <p className=\"text-sm font-medium text-gray-900\">Primary Domain</p>\n            <Switch fn={setIsPrimary} checked={isPrimary} />\n          </div>\n          */}\n          {hasError && (\n            <div className=\"py-2\">\n              {error && error.items.length > 0 ? (\n                error?.items.map((item) => (\n                  <h2 className=\"text-red-500 text-sm\">{item.message}</h2>\n                ))\n              ) : (\n                <h2 className=\"text-red-500 text-sm\">\n                  There was an error adding your domain. Please try again later.\n                </h2>\n              )}\n            </div>\n          )}\n          <div className=\"flex justify-end gap-2\">\n            <Button type=\"button\" variant=\"light\" onClick={close}>\n              Cancel\n            </Button>\n            <Button\n              className=\"gap-2\"\n              type=\"submit\"\n              disabled={loading}\n              variant=\"default\"\n            >\n              {loading && <LoadingSpinner className=\"h-4 w-4\" />}\n              Add Domain\n            </Button>\n          </div>\n        </div>\n      </form>\n    </Modal>\n  );\n}\n"
  },
  {
    "path": "components/AlertCircleFill.tsx",
    "content": "export default function AlertCircleFill({ className }: { className: string }) {\n  return (\n    <svg\n      fill=\"none\"\n      shapeRendering=\"geometricPrecision\"\n      stroke=\"currentColor\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      strokeWidth=\"1.5\"\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      height=\"24\"\n      className={className}\n    >\n      <circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"currentColor\" />\n      <path d=\"M12 8v4\" stroke=\"white\" />\n      <path d=\"M12 16h.01\" stroke=\"white\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "components/AlertDialog.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as AlertDialogPrimitive from \"@radix-ui/react-alert-dialog\";\n\nimport { cn } from \"@/utils/helpers\";\n\nconst AlertDialog = AlertDialogPrimitive.Root;\n\nconst AlertDialogTrigger = AlertDialogPrimitive.Trigger;\n\nconst AlertDialogPortal = ({\n  className,\n  ...props\n}: AlertDialogPrimitive.AlertDialogPortalProps) => (\n  <AlertDialogPrimitive.Portal className={cn(className)} {...props} />\n);\nAlertDialogPortal.displayName = AlertDialogPrimitive.Portal.displayName;\n\nconst AlertDialogOverlay = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>\n>(({ className, children, ...props }, ref) => (\n  <AlertDialogPrimitive.Overlay\n    className={cn(\n      \"fixed inset-0 z-50 bg-white/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n      className,\n    )}\n    {...props}\n    ref={ref}\n  />\n));\nAlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;\n\nconst AlertDialogContent = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Content>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPortal>\n    <AlertDialogOverlay />\n    <AlertDialogPrimitive.Content\n      ref={ref}\n      className={cn(\n        \"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full\",\n        className,\n      )}\n      {...props}\n    />\n  </AlertDialogPortal>\n));\nAlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;\n\nconst AlertDialogHeader = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col space-y-2 text-center sm:text-left\",\n      className,\n    )}\n    {...props}\n  />\n);\nAlertDialogHeader.displayName = \"AlertDialogHeader\";\n\nconst AlertDialogFooter = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\",\n      className,\n    )}\n    {...props}\n  />\n);\nAlertDialogFooter.displayName = \"AlertDialogFooter\";\n\nconst AlertDialogTitle = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Title\n    ref={ref}\n    className={cn(\"text-lg font-semibold\", className)}\n    {...props}\n  />\n));\nAlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;\n\nconst AlertDialogDescription = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Description\n    ref={ref}\n    className={cn(\"text-sm text-muted-foreground\", className)}\n    {...props}\n  />\n));\nAlertDialogDescription.displayName =\n  AlertDialogPrimitive.Description.displayName;\n\nconst AlertDialogAction = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Action>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Action ref={ref} className={cn(className)} {...props} />\n));\nAlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;\n\nconst AlertDialogCancel = React.forwardRef<\n  React.ElementRef<typeof AlertDialogPrimitive.Cancel>,\n  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>\n>(({ className, ...props }, ref) => (\n  <AlertDialogPrimitive.Cancel\n    ref={ref}\n    className={cn(\"mt-2 sm:mt-0\", className)}\n    {...props}\n  />\n));\nAlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;\n\nexport {\n  AlertDialog,\n  AlertDialogTrigger,\n  AlertDialogContent,\n  AlertDialogHeader,\n  AlertDialogFooter,\n  AlertDialogTitle,\n  AlertDialogDescription,\n  AlertDialogAction,\n  AlertDialogCancel,\n};\n"
  },
  {
    "path": "components/AuthModal.tsx",
    "content": "\"use client\";\nimport { useState } from \"react\";\nimport Link from \"next/link\";\nimport altogic from \"@/utils/altogic\";\nimport useSearchParams from \"@/hooks/useSearchParams\";\nimport Image from \"next/image\";\nimport Modal from \"@/components/Modal\";\nimport LoadingSpinner from \"@/components/loadingSpinner\";\nimport Button, { ButtonVariant } from \"@/components/Button\";\n\nexport default function AuthModal() {\n  const { deleteByKey, has } = useSearchParams();\n\n  const [selected, setSelected] = useState<number>();\n\n  const loginMethods = [\n    {\n      name: \"Google\",\n      variant: \"default\",\n      icon: GoogleIcon,\n      handler: () => altogic.auth.signInWithProvider(\"google\"),\n    },\n    {\n      name: \"Github\",\n      variant: \"light\",\n      icon: GithubIcon,\n      handler: () => altogic.auth.signInWithProvider(\"github\"),\n    },\n  ];\n\n  function close() {\n    deleteByKey(\"authModal\");\n  }\n\n  return (\n    <Modal close={close} isOpen={has(\"authModal\")} className=\"p-0\">\n      <div className=\"flex flex-col items-center justify-center space-y-3 border-b border-gray-200 bg-white px-4 py-6 pt-8 text-center sm:px-10\">\n        <Link href=\"/\">\n          <Image\n            src=\"/logoa.png\"\n            alt=\"AIPage.dev logo\"\n            width={150}\n            height={150}\n            draggable={false}\n            className=\"mx-auto h-24 w-24\"\n          />\n        </Link>\n        <h3 className=\"text-xl font-semibold\">Sign in to AIPage</h3>\n        <p className=\"text-sm text-gray-500\">\n          Powered by AI, Perfected for You\n        </p>\n      </div>\n      <div className=\"flex flex-col space-y-3 bg-gray-50 px-4 py-8 sm:px-10\">\n        {loginMethods.map((method, index) => (\n          <Button\n            key={method.name}\n            variant={method.variant as ButtonVariant}\n            // this is a hack to prevent the button from being clicked twice\n            disabled={selected !== undefined}\n            onClick={() => {\n              setSelected(index);\n              method.handler();\n            }}\n            className=\"h-10\"\n          >\n            {index === selected ? (\n              <LoadingSpinner className=\"h-4 w-4\" />\n            ) : (\n              <method.icon />\n            )}\n            <p>Continue with {method.name}</p>\n          </Button>\n        ))}\n        <p className=\"text-center text-sm text-gray-500\">\n          Enterprise login?{\" \"}\n          <a\n            target=\"_blank\"\n            className=\"font-semibold text-gray-500 transition-colors hover:text-black\"\n            href=\"mailto:deniz@altogic.com\"\n          >\n            Contact\n          </a>\n        </p>\n      </div>\n    </Modal>\n  );\n}\n\nconst GoogleIcon = () => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    viewBox=\"0 0 488 512\"\n    fill=\"currentColor\"\n    className=\"h-4 w-4\"\n  >\n    <path d=\"M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z\"></path>\n  </svg>\n);\n\nconst GithubIcon = () => (\n  <svg aria-label=\"github\" viewBox=\"0 0 14 14\" className=\"h-4 w-4\">\n    <path\n      d=\"M7 .175c-3.872 0-7 3.128-7 7 0 3.084 2.013 5.71 4.79 6.65.35.066.482-.153.482-.328v-1.181c-1.947.415-2.363-.941-2.363-.941-.328-.81-.787-1.028-.787-1.028-.634-.438.044-.416.044-.416.7.044 1.071.722 1.071.722.635 1.072 1.641.766 2.035.59.066-.459.24-.765.437-.94-1.553-.175-3.193-.787-3.193-3.456 0-.766.262-1.378.721-1.881-.065-.175-.306-.897.066-1.86 0 0 .59-.197 1.925.722a6.754 6.754 0 0 1 1.75-.24c.59 0 1.203.087 1.75.24 1.335-.897 1.925-.722 1.925-.722.372.963.131 1.685.066 1.86.46.48.722 1.115.722 1.88 0 2.691-1.641 3.282-3.194 3.457.24.219.481.634.481 1.29v1.926c0 .197.131.415.481.328C11.988 12.884 14 10.259 14 7.175c0-3.872-3.128-7-7-7z\"\n      fill=\"currentColor\"\n      fillRule=\"nonzero\"\n    />\n  </svg>\n);\n"
  },
  {
    "path": "components/Badge.tsx",
    "content": "import { cn } from \"@/utils/helpers\";\n\nexport type BadgeVariant = \"yellow\" | \"gray\" | \"red\" | \"black\" | \"green\";\n\nexport default function Badge({\n  text,\n  variant,\n  className,\n}: {\n  text: string;\n  variant?: BadgeVariant;\n  className?: string;\n}) {\n  return (\n    <span\n      className={cn(\n        \"rounded-full border px-2 py-px text-xs font-medium capitalize tabular-nums\",\n        {\n          \"border-gray-400 bg-gray-400 text-white\": variant === \"gray\",\n          \"border-red-500 bg-red-500 text-white\": variant === \"red\",\n          \"border-green-500 bg-green-500 text-white\": variant === \"green\",\n          \"border-black bg-black text-white\": variant === \"black\",\n          \"border-yellow-400 bg-yellow-400 text-stone-700\":\n            variant === \"yellow\",\n        },\n        className,\n      )}\n    >\n      {text}\n    </span>\n  );\n}\n"
  },
  {
    "path": "components/BrowserWindow.tsx",
    "content": "import { ReactNode } from \"react\";\nimport { cn } from \"@/utils/helpers\";\n\ninterface BrowserWindowProps {\n  children: ReactNode;\n  className?: string;\n}\nexport default function BrowserWindow({\n  children,\n  className,\n}: BrowserWindowProps) {\n  return (\n    <div\n      className={cn(\n        \"flex flex-col items-center w-full overflow-hidden browser-window\",\n        className,\n      )}\n    >\n      <div className=\"border flex-1 flex flex-col rounded-xl w-full overflow-hidden\">\n        <div className=\"bg-white flex items-center justify-between px-3 py-4 border-b\">\n          <div className=\"flex items-center space-x-2\">\n            <div className=\"w-3 h-3 bg-red-500 rounded-full\" />\n            <div className=\"w-3 h-3 bg-yellow-500 rounded-full\" />\n            <div className=\"w-3 h-3 bg-green-500 rounded-full\" />\n          </div>\n        </div>\n        <div className=\"flex-1 overflow-hidden\">{children}</div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/Button.tsx",
    "content": "import { cn } from \"@/utils/helpers\";\nimport { ComponentPropsWithoutRef } from \"react\";\nimport * as React from \"react\";\nexport type ButtonVariant = \"default\" | \"light\" | \"pill\" | \"danger\";\n\ninterface ButtonProps extends ComponentPropsWithoutRef<\"button\"> {\n  variant?: ButtonVariant;\n}\n\nconst Button = React.forwardRef<HTMLButtonElement, ButtonProps>(\n  ({ className, children, variant, ...props }, ref) => (\n    <button\n      ref={ref}\n      className={cn(\n        \"flex items-center justify-center space-x-2 rounded-md border text-sm transition-all focus:outline-none px-4 py-1.5 \",\n        {\n          \"border-black bg-black text-white active:bg-white enabled:hover:bg-white active:text-black enabled:hover:text-black disabled:opacity-50 disabled:cursor-not-allowed\":\n            variant === \"default\",\n          \"border-gray-200 bg-white text-gray-500 active:border-black enabled:hover:border-black active:text-black enabled:hover:text-black disabled:opacity-50 disabled:cursor-not-allowed\":\n            variant === \"light\",\n          \"rounded-full border border-black bg-black text-sm text-white transition-all enabled:hover:bg-white enabled:hover:text-black !disabled:text-black !disabled:bg-white\":\n            variant === \"pill\",\n          \"flex items-center justify-center space-x-2 rounded-md border px-4 text-sm transition-all focus:outline-none border-red-500 bg-red-500 text-white enabled:hover:bg-white enabled:hover:text-red-500 disabled:opacity-50 disabled:cursor-not-allowed\":\n            variant === \"danger\",\n        },\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </button>\n  ),\n);\n\nexport default Button;\n"
  },
  {
    "path": "components/Chart.tsx",
    "content": "export default function Chart({ className }: { className: string }) {\n  return (\n    <svg\n      fill=\"none\"\n      shapeRendering=\"geometricPrecision\"\n      stroke=\"currentColor\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      strokeWidth=\"1.5\"\n      viewBox=\"0 0 24 24\"\n      width=\"14\"\n      height=\"14\"\n      className={className}\n    >\n      <path d=\"M12 20V10\" />\n      <path d=\"M18 20V4\" />\n      <path d=\"M6 20v-4\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "components/CheckCircleFill.tsx",
    "content": "export default function CheckCircleFill({ className }: { className?: string }) {\n  return (\n    <svg\n      className={className}\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      height=\"24\"\n      fill=\"none\"\n      shapeRendering=\"geometricPrecision\"\n    >\n      <path\n        d=\"M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2Z\"\n        fill=\"currentColor\"\n      />\n      <path d=\"M8 11.8571L10.5 14.3572L15.8572 9\" stroke=\"white\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "components/ConfiguredSection.tsx",
    "content": "import { useState } from \"react\";\nimport { DomainInfo } from \"@/types\";\n\ninterface ConfiguredSectionProps {\n  domainInfo?: DomainInfo | null;\n}\nconst ConfiguredSection = ({ domainInfo }: ConfiguredSectionProps) => {\n  const [recordType, setRecordType] = useState(\"A\");\n\n  if (!domainInfo?.verified) {\n    const txtVerification = domainInfo?.verification?.find(\n      (x: any) => x.type === \"TXT\",\n    );\n    return (\n      <>\n        <div className=\"flex items-center space-x-3 my-3 px-2 sm:px-10\">\n          <svg\n            viewBox=\"0 0 24 24\"\n            width=\"24\"\n            height=\"24\"\n            strokeWidth=\"1.5\"\n            strokeLinecap=\"round\"\n            strokeLinejoin=\"round\"\n            shapeRendering=\"geometricPrecision\"\n          >\n            <circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"#EAB308\" />\n            <path d=\"M12 8v4\" stroke=\"white\" />\n            <path d=\"M12 16h.01\" stroke=\"white\" />\n          </svg>\n          <p className=\"text-yellow-600 font-medium text-sm\">\n            Domain is pending verification\n          </p>\n        </div>\n\n        <div className=\"w-full border-t border-gray-100 mt-5 mb-8\" />\n        <div className=\"px-2 sm:px-10\">\n          <div className=\"flex justify-start space-x-4\">\n            <div\n              onClick={() => setRecordType(\"CNAME\")}\n              className={`${\n                recordType == \"CNAME\"\n                  ? \"text-black border-black\"\n                  : \"text-gray-400 border-white\"\n              } text-sm border-b-2 pb-1 transition-all ease duration-150`}\n            >\n              Verify Domain Ownership\n            </div>\n          </div>\n          <div className=\"my-3 text-left\">\n            <p className=\"my-5 text-sm\">\n              Please set the following TXT record on {domainInfo?.apexName} to\n              prove ownership of {domainInfo?.name}:\n            </p>\n            <div className=\"flex justify-start items-start space-x-10 bg-gray-50 p-2 rounded-md\">\n              <div>\n                <p className=\"text-sm font-bold\">Type</p>\n                <p className=\"text-sm font-mono mt-2\">\n                  {txtVerification?.type}\n                </p>\n              </div>\n              <div>\n                <p className=\"text-sm font-bold\">Name</p>\n                <p className=\"text-sm font-mono mt-2\">\n                  {txtVerification?.domain &&\n                    domainInfo?.apexName &&\n                    txtVerification?.domain?.slice(\n                      0,\n                      txtVerification?.domain?.length -\n                        domainInfo?.apexName?.length -\n                        1,\n                    )}\n                </p>\n              </div>\n              <div>\n                <p className=\"text-sm font-bold\">Value</p>\n                <p className=\"text-sm font-mono mt-2\">\n                  <span className=\"text-ellipsis\">\n                    {txtVerification?.value}\n                  </span>\n                </p>\n              </div>\n            </div>\n          </div>\n        </div>\n      </>\n    );\n  }\n\n  return (\n    <>\n      <div className=\"flex items-center space-x-3 my-3 px-2 sm:px-10\">\n        <svg\n          viewBox=\"0 0 24 24\"\n          width=\"24\"\n          height=\"24\"\n          strokeWidth=\"1.5\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          shapeRendering=\"geometricPrecision\"\n        >\n          <circle\n            cx=\"12\"\n            cy=\"12\"\n            r=\"10\"\n            fill={domainInfo.configured ? \"#1976d2\" : \"#d32f2f\"}\n          />\n          {domainInfo.configured ? (\n            <>\n              <path\n                d=\"M8 11.8571L10.5 14.3572L15.8572 9\"\n                fill=\"none\"\n                stroke=\"white\"\n              />\n            </>\n          ) : (\n            <>\n              <path d=\"M15 9l-6 6\" stroke=\"white\" />\n              <path d=\"M9 9l6 6\" stroke=\"white\" />\n            </>\n          )}\n        </svg>\n        <p\n          className={`${\n            domainInfo.configured\n              ? \"text-black font-normal\"\n              : \"text-red-700 font-medium\"\n          } text-sm`}\n        >\n          {domainInfo.configured ? \"Valid\" : \"Invalid\"} Configuration\n        </p>\n      </div>\n\n      {!domainInfo.configured && (\n        <>\n          <div className=\"w-full border-t border-gray-100 mt-5 mb-8\" />\n          <div className=\"px-2 sm:px-10\">\n            <div className=\"flex justify-start space-x-4\">\n              <button\n                onClick={() => setRecordType(\"A\")}\n                className={`${\n                  recordType == \"A\"\n                    ? \"text-black border-black\"\n                    : \"text-gray-400 border-white\"\n                } text-sm border-b-2 pb-1 transition-all ease duration-150`}\n              >\n                A Record (Recommended)\n              </button>\n              <button\n                onClick={() => setRecordType(\"CNAME\")}\n                className={`${\n                  recordType == \"CNAME\"\n                    ? \"text-black border-black\"\n                    : \"text-gray-400 border-white\"\n                } text-sm border-b-2 pb-1 transition-all ease duration-150`}\n              >\n                CNAME Record (subdomains)\n              </button>\n            </div>\n            <div className=\"my-3 text-left\">\n              <p className=\"my-5 text-sm\">\n                Set the following record on your DNS provider to continue:\n              </p>\n              <div className=\"flex justify-start items-center space-x-10 bg-gray-50 p-2 rounded-md\">\n                <div>\n                  <p className=\"text-sm font-bold\">Type</p>\n                  <p className=\"text-sm font-mono mt-2\">{recordType}</p>\n                </div>\n                <div>\n                  <p className=\"text-sm font-bold\">Name</p>\n                  <p className=\"text-sm font-mono mt-2\">\n                    {recordType == \"CNAME\" ? \"www\" : \"@\"}\n                  </p>\n                </div>\n                <div>\n                  <p className=\"text-sm font-bold\">Value</p>\n                  <p className=\"text-sm font-mono mt-2\">\n                    {recordType == \"CNAME\" ? `cname.aipage.dev` : `76.76.21.21`}\n                  </p>\n                </div>\n              </div>\n            </div>\n          </div>\n        </>\n      )}\n    </>\n  );\n};\n\nexport default ConfiguredSection;\n"
  },
  {
    "path": "components/ConfirmDialog.tsx",
    "content": "\"use client\";\n\nimport {\n  AlertDialog,\n  AlertDialogAction,\n  AlertDialogCancel,\n  AlertDialogContent,\n  AlertDialogTrigger,\n} from \"@/components/AlertDialog\";\nimport Button from \"@/components/Button\";\nimport { XIcon } from \"lucide-react\";\nimport { useAuth } from \"@/context/AuthContext\";\nimport { ReactNode, useState } from \"react\";\n\ninterface ConfirmDialogProps {\n  text: string;\n  trigger: ReactNode;\n  onConfirm: () => void;\n  children?: ReactNode;\n}\n\nexport default function ConfirmDialog({\n  trigger,\n  text,\n  onConfirm,\n  children,\n}: ConfirmDialogProps) {\n  const [confirmText, setConfirmText] = useState(\"\");\n\n  return (\n    <AlertDialog>\n      <AlertDialogTrigger asChild>{trigger}</AlertDialogTrigger>\n      <AlertDialogContent className=\"overflow-hidden gap-0 w-[95%] max-w-[450px] p-0 border border-gray-100 rounded-2xl shadow-xl\">\n        <AlertDialogCancel className=\"absolute top-4 z-50 right-4 text-gray-500 hover:text-black transition-colors focus:outline-none\">\n          <XIcon />\n        </AlertDialogCancel>\n        {children && (\n          <div className=\"flex flex-col items-center justify-center relative space-y-3 border-b border-gray-200 px-4 py-4 pt-8 sm:px-8\">\n            {children}\n          </div>\n        )}\n        <div className=\"flex flex-col space-y-6 bg-gray-50 px-4 py-8 text-left sm:px-8\">\n          <div>\n            <label\n              htmlFor=\"verification\"\n              className=\"block text-sm text-gray-700\"\n            >\n              To verify, type{\" \"}\n              <span className=\"font-semibold text-black\">{text}</span> below\n            </label>\n            <div className=\"relative mt-1 rounded-md shadow-sm\">\n              <input\n                type=\"text\"\n                name=\"verification\"\n                id=\"verification\"\n                pattern=\"confirm delete account\"\n                required\n                autoFocus={false}\n                autoComplete=\"off\"\n                value={confirmText}\n                onChange={(e) => setConfirmText(e.target.value)}\n                className=\"block w-full rounded-md border-gray-300 pr-10 text-gray-900 placeholder-gray-300 focus:border-gray-500 focus:outline-none focus:ring-gray-500 sm:text-sm\"\n              />\n            </div>\n          </div>\n\n          <AlertDialogAction asChild>\n            <Button\n              disabled={confirmText !== text}\n              variant=\"danger\"\n              onClick={() => {\n                setConfirmText(\"\");\n                onConfirm();\n              }}\n            >\n              {text.toUpperCase()}\n            </Button>\n          </AlertDialogAction>\n        </div>\n      </AlertDialogContent>\n    </AlertDialog>\n  );\n}\n"
  },
  {
    "path": "components/DeleteAccountConfirmDialog.tsx",
    "content": "\"use client\";\n\nimport Button from \"@/components/Button\";\nimport { useState } from \"react\";\nimport ConfirmDialog from \"@/components/ConfirmDialog\";\nimport LoadingSpinner from \"@/components/loadingSpinner\";\nimport { useAuth } from \"@/context/AuthContext\";\n\nexport default function DeleteAccountConfirmDialog() {\n  const [deleting, setDeleting] = useState(false);\n  const { user } = useAuth();\n\n  async function deleteAccountHandler() {\n    setDeleting(true);\n    const res = await fetch(\"/api/user\", {\n      method: \"DELETE\",\n    });\n    if (res.ok && res.redirected) {\n      window.location.href = res.url;\n    } else {\n      setDeleting(false);\n    }\n  }\n\n  return (\n    <ConfirmDialog\n      text=\"confirm delete account\"\n      trigger={\n        <Button variant=\"danger\" disabled={deleting} type=\"button\">\n          {deleting && <LoadingSpinner />}\n          <p>Delete Account</p>\n        </Button>\n      }\n      onConfirm={deleteAccountHandler}\n    >\n      <img\n        alt={user?.email || \"Avatar for logged in user\"}\n        src={\n          user?.profilePicture ||\n          `https://avatars.dicebear.com/api/micah/${user?.email}.svg`\n        }\n        className=\"h-20 shrink-0 w-20 rounded-full border border-gray-300 transition-all duration-75 group-focus:outline-none group-active:scale-95 sm:h-20 sm:w-20\"\n      />\n      <h3 className=\"text-lg font-medium\">Delete Account</h3>\n      <p className=\"text-center text-sm text-gray-500\">\n        Warning: This will permanently delete your account and all your data.\n      </p>\n    </ConfirmDialog>\n  );\n}\n"
  },
  {
    "path": "components/DeleteProjectConfirmDialog.tsx",
    "content": "\"use client\";\n\nimport Button from \"@/components/Button\";\nimport { useState } from \"react\";\nimport ConfirmDialog from \"@/components/ConfirmDialog\";\nimport LoadingSpinner from \"@/components/loadingSpinner\";\nimport { Project } from \"@/types\";\nimport { useRouter } from \"next/navigation\";\nimport useProjectList from \"@/hooks/useProjectList\";\n\nexport default function DeleteProjectConfirmDialog({\n  project,\n}: {\n  project: Project | null;\n}) {\n  const [deleting, setDeleting] = useState(false);\n  const { push, prefetch, refresh } = useRouter();\n  const { deleteProject } = useProjectList();\n\n  async function deleteProjectHandler() {\n    if (!project) return;\n    setDeleting(true);\n    const res = await fetch(\"/api/project/\" + project._id, {\n      method: \"DELETE\",\n    });\n    const { errors } = await res.json();\n\n    if (!errors) {\n      deleteProject(project._id);\n      refresh();\n      push(\"/profile/projects\");\n    } else setDeleting(false);\n  }\n\n  return (\n    <ConfirmDialog\n      text=\"confirm delete project\"\n      trigger={\n        <Button variant=\"danger\" disabled={deleting} type=\"button\">\n          {deleting && <LoadingSpinner />}\n          <p>Delete project</p>\n        </Button>\n      }\n      onConfirm={deleteProjectHandler}\n    />\n  );\n}\n"
  },
  {
    "path": "components/Divider.tsx",
    "content": "export default function Divider({ className }: { className?: string }) {\n  return (\n    <svg\n      fill=\"none\"\n      shapeRendering=\"geometricPrecision\"\n      stroke=\"currentColor\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      strokeWidth=\"1\"\n      viewBox=\"0 0 24 24\"\n      width=\"14\"\n      height=\"14\"\n      className={className}\n    >\n      <path d=\"M16.88 3.549L7.12 20.451\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "components/DomainCard.tsx",
    "content": "\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport LoadingSpinner from \"@/components/loadingSpinner\";\nimport ConfiguredSection from \"@/components/ConfiguredSection\";\nimport { Domain } from \"@/types\";\nimport Button from \"@/components/Button\";\nimport { useRouter } from \"next/navigation\";\nimport useProject from \"@/hooks/useProject\";\n\ninterface DomainCardProps {\n  domain: Domain;\n}\n\nconst DomainCard = ({ domain }: DomainCardProps) => {\n  const removeDomainFromState = useProject((state) => state.removeDomain);\n  const [domainInfo, setDomainInfo] = useState(null);\n  const [isValidating, setIsValidating] = useState(false);\n  const [removing, setRemoving] = useState(false);\n\n  const { refresh } = useRouter();\n\n  useEffect(() => {\n    checkDomain();\n    const interval = setInterval(checkDomain, 10000);\n    return () => clearInterval(interval);\n  }, []);\n\n  async function checkDomain() {\n    setIsValidating(true);\n    const res = await fetch(\"/api/domain/check-domain\", {\n      method: \"POST\",\n      body: JSON.stringify({ domain: domain.domain }),\n    });\n    const data = await res.json();\n    setDomainInfo(data);\n    setIsValidating(false);\n  }\n\n  async function removeDomain() {\n    setRemoving(true);\n    try {\n      await fetch(\"/api/domain/remove-domain\", {\n        method: \"POST\",\n        body: JSON.stringify({ domain: domain.domain }),\n      });\n      refresh();\n      removeDomainFromState(domain._id);\n    } catch {\n      setRemoving(false);\n    }\n  }\n\n  return (\n    <div className=\"w-full bg-white border-y sm:border border-black sm:border-gray-50 sm:rounded-lg py-10\">\n      <div className=\"flex justify-between space-x-4 px-2 sm:px-10\">\n        <a\n          href={`http://${domain.domain}`}\n          target=\"_blank\"\n          rel=\"noreferrer\"\n          className=\"text-xl text-left font-semibold flex items-center\"\n        >\n          {domain.domain}\n          <span className=\"inline-block ml-2\">\n            <svg\n              viewBox=\"0 0 24 24\"\n              width=\"20\"\n              height=\"20\"\n              stroke=\"currentColor\"\n              strokeWidth=\"1.5\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n              fill=\"none\"\n              shapeRendering=\"geometricPrecision\"\n            >\n              <path d=\"M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6\" />\n              <path d=\"M15 3h6v6\" />\n              <path d=\"M10 14L21 3\" />\n            </svg>\n          </span>\n        </a>\n        <div className=\"flex space-x-3\">\n          <Button onClick={checkDomain} disabled={isValidating}>\n            {isValidating ? <LoadingSpinner /> : \"Refresh\"}\n          </Button>\n          <Button variant=\"danger\" onClick={removeDomain} disabled={removing}>\n            {removing ? <LoadingSpinner /> : \"Remove\"}\n          </Button>\n        </div>\n      </div>\n\n      <ConfiguredSection domainInfo={domainInfo} />\n    </div>\n  );\n};\n\nexport default DomainCard;\n"
  },
  {
    "path": "components/DomainConfiguration.tsx",
    "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { getSubdomain } from \"@/utils/helpers\";\nimport { DomainVerificationStatusProps } from \"@/types\";\n\nexport const InlineSnippet = ({ children }: { children: string }) => {\n  return (\n    <span className=\"inline-block rounded-md bg-blue-100 px-1 py-0.5 font-mono text-blue-900\">\n      {children}\n    </span>\n  );\n};\n\nexport default function DomainConfiguration({\n  data,\n}: {\n  data: { status: DomainVerificationStatusProps; response: any };\n}) {\n  const { domainJson } = data.response;\n  console.log();\n  const subdomain = getSubdomain(domainJson.name, domainJson.apexName);\n  const [recordType, setRecordType] = useState(!!subdomain ? \"CNAME\" : \"A\");\n\n  if (data.status === \"Pending Verification\") {\n    const txtVerification = domainJson.verification.find(\n      (x: any) => x.type === \"TXT\",\n    );\n    return (\n      <div className=\"border-t border-gray-200 pt-5\">\n        <p className=\"text-sm\">\n          Please set the following TXT record on{\" \"}\n          <InlineSnippet>{domainJson.apexName}</InlineSnippet> to prove\n          ownership of <InlineSnippet>{domainJson.name}</InlineSnippet>:\n        </p>\n        <div className=\"my-5 flex items-start justify-start space-x-10 rounded-md bg-gray-50 p-2\">\n          <div>\n            <p className=\"text-sm font-bold\">Type</p>\n            <p className=\"mt-2 font-mono text-sm\">{txtVerification.type}</p>\n          </div>\n          <div>\n            <p className=\"text-sm font-bold\">Name</p>\n            <p className=\"mt-2 font-mono text-sm\">\n              {txtVerification.domain.slice(\n                0,\n                txtVerification.domain.length - domainJson.apexName.length - 1,\n              )}\n            </p>\n          </div>\n          <div>\n            <p className=\"text-sm font-bold\">Value</p>\n            <p className=\"mt-2 font-mono text-sm\">\n              <span className=\"text-ellipsis\">{txtVerification.value}</span>\n            </p>\n          </div>\n        </div>\n        <p className=\"text-sm\">\n          Warning: if you are using this domain for another site, setting this\n          TXT record will transfer domain ownership away from that site and\n          break it. Please exercise caution when setting this record.\n        </p>\n      </div>\n    );\n  }\n\n  if (data.status === \"Unknown Error\") {\n    return (\n      <div className=\"border-t border-gray-200 pt-5\">\n        <p className=\"mb-5 text-sm\">{data.response.domainJson.error.message}</p>\n      </div>\n    );\n  }\n\n  return (\n    <div className=\"border-t border-gray-200 pt-5\">\n      <div className=\"flex justify-start space-x-4\">\n        <button\n          onClick={() => setRecordType(\"A\")}\n          className={`${\n            recordType == \"A\"\n              ? \"border-black text-black\"\n              : \"border-white text-gray-400\"\n          } ease border-b-2 pb-1 text-sm transition-all duration-150`}\n        >\n          A Record{!subdomain && \" (recommended)\"}\n        </button>\n        <button\n          onClick={() => setRecordType(\"CNAME\")}\n          className={`${\n            recordType == \"CNAME\"\n              ? \"border-black text-black\"\n              : \"border-white text-gray-400\"\n          } ease border-b-2 pb-1 text-sm transition-all duration-150`}\n        >\n          CNAME Record{subdomain && \" (recommended)\"}\n        </button>\n      </div>\n      <div className=\"my-3 text-left\">\n        <p className=\"my-5 text-sm\">\n          To configure your {recordType === \"A\" ? \"apex domain\" : \"subdomain\"} (\n          <InlineSnippet>\n            {recordType === \"A\" ? domainJson.apexName : domainJson.name}\n          </InlineSnippet>\n          ), set the following {recordType} record on your DNS provider to\n          continue:\n        </p>\n        <div className=\"flex items-center justify-start space-x-10 rounded-md bg-gray-50 p-2\">\n          <div>\n            <p className=\"text-sm font-bold\">Type</p>\n            <p className=\"mt-2 font-mono text-sm\">{recordType}</p>\n          </div>\n          <div>\n            <p className=\"text-sm font-bold\">Name</p>\n            <p className=\"mt-2 font-mono text-sm\">\n              {recordType === \"A\" ? \"@\" : subdomain ?? \"www\"}\n            </p>\n          </div>\n          <div>\n            <p className=\"text-sm font-bold\">Value</p>\n            <p className=\"mt-2 font-mono text-sm\">\n              {recordType === \"A\" ? `76.76.21.21` : `cname.dub.co`}\n            </p>\n          </div>\n          <div>\n            <p className=\"text-sm font-bold\">TTL</p>\n            <p className=\"mt-2 font-mono text-sm\">86400</p>\n          </div>\n        </div>\n        <p className=\"mt-5 text-sm\">\n          Note: for TTL, if <InlineSnippet>86400</InlineSnippet> is not\n          available, set the highest value possible. Also, domain propagation\n          can take anywhere between 1 hour to 12 hours.\n        </p>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/Drawer.tsx",
    "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as SheetPrimitive from \"@radix-ui/react-dialog\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { cn } from \"@/utils/helpers\";\nimport { XIcon } from \"lucide-react\";\n\nconst Sheet = SheetPrimitive.Root;\n\nconst SheetTrigger = SheetPrimitive.Trigger;\n\nconst SheetClose = SheetPrimitive.Close;\n\nconst SheetPortal = ({\n  className,\n  ...props\n}: SheetPrimitive.DialogPortalProps) => (\n  <SheetPrimitive.Portal className={cn(className)} {...props} />\n);\nSheetPortal.displayName = SheetPrimitive.Portal.displayName;\n\nconst SheetOverlay = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Overlay>,\n  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>\n>(({ className, ...props }, ref) => (\n  <SheetPrimitive.Overlay\n    className={cn(\n      \"fixed inset-0 z-50 bg-white/70 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0\",\n      className,\n    )}\n    {...props}\n    ref={ref}\n  />\n));\nSheetOverlay.displayName = SheetPrimitive.Overlay.displayName;\n\nconst sheetVariants = cva(\n  \"fixed z-50 gap-4 bg-white px-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500\",\n  {\n    variants: {\n      side: {\n        top: \"inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top\",\n        bottom:\n          \"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom\",\n        left: \"inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm\",\n        right:\n          \"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm\",\n      },\n    },\n    defaultVariants: {\n      side: \"right\",\n    },\n  },\n);\n\ninterface SheetContentProps\n  extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,\n    VariantProps<typeof sheetVariants> {}\n\nconst SheetContent = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Content>,\n  SheetContentProps\n>(({ side = \"right\", className, children, ...props }, ref) => (\n  <SheetPortal>\n    <SheetOverlay />\n    <SheetPrimitive.Content\n      ref={ref}\n      className={cn(sheetVariants({ side }), className)}\n      {...props}\n    >\n      {children}\n      <SheetPrimitive.Close className=\"absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none data-[state=open]:bg-gray-200\">\n        <XIcon className=\"h-4 w-4\" />\n        <span className=\"sr-only\">Close</span>\n      </SheetPrimitive.Close>\n    </SheetPrimitive.Content>\n  </SheetPortal>\n));\nSheetContent.displayName = SheetPrimitive.Content.displayName;\n\nconst SheetHeader = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col space-y-2 pt-6 pb-2 text-center sm:text-left\",\n      className,\n    )}\n    {...props}\n  />\n);\nSheetHeader.displayName = \"SheetHeader\";\n\nconst SheetFooter = ({\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) => (\n  <div\n    className={cn(\n      \"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2\",\n      className,\n    )}\n    {...props}\n  />\n);\nSheetFooter.displayName = \"SheetFooter\";\n\nconst SheetTitle = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Title>,\n  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>\n>(({ className, ...props }, ref) => (\n  <SheetPrimitive.Title\n    ref={ref}\n    className={cn(\"text-lg font-semibold text-black\", className)}\n    {...props}\n  />\n));\nSheetTitle.displayName = SheetPrimitive.Title.displayName;\n\nconst SheetDescription = React.forwardRef<\n  React.ElementRef<typeof SheetPrimitive.Description>,\n  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>\n>(({ className, ...props }, ref) => (\n  <SheetPrimitive.Description\n    ref={ref}\n    className={cn(\"text-sm text-slate-800\", className)}\n    {...props}\n  />\n));\nSheetDescription.displayName = SheetPrimitive.Description.displayName;\n\nexport {\n  Sheet,\n  SheetTrigger,\n  SheetClose,\n  SheetContent,\n  SheetHeader,\n  SheetFooter,\n  SheetTitle,\n  SheetDescription,\n};\n"
  },
  {
    "path": "components/HTMLPreview.tsx",
    "content": "interface HTMLPreviewProps {\n  html: string;\n}\n\nexport default function HTMLPreview({ html }: HTMLPreviewProps) {\n  return (\n    <iframe className=\"w-full min-h-screen cursor-pointer\" srcDoc={html} />\n  );\n}\n"
  },
  {
    "path": "components/Header.tsx",
    "content": "\"use client\";\nimport useSearchParams from \"@/hooks/useSearchParams\";\nimport { useAuth } from \"@/context/AuthContext\";\nimport UserDropdown from \"@/components/UserDropdown\";\nimport Logo from \"@/components/Logo\";\nimport Button from \"@/components/Button\";\nimport ProjectSelect from \"@/components/ProjectSelect\";\nimport Divider from \"@/components/Divider\";\nimport { useParams, usePathname } from \"next/navigation\";\n\nexport default function Header() {\n  const { set } = useSearchParams();\n  const { user } = useAuth();\n  const pathname = usePathname();\n  const { id } = useParams();\n\n  const isProjectPage = pathname.startsWith(`/profile/projects/${id}`);\n\n  function openAuthModal() {\n    set(\"authModal\", \"true\");\n  }\n\n  function openPricesModal() {\n    set(\"pricesModal\", \"true\");\n  }\n\n  return (\n    <header className=\"w-full px-6 py-4 absolute top-0\">\n      <div className=\"flex justify-between items-center h-10\">\n        <div className=\"flex items-center gap-2\">\n          <Logo href={isProjectPage ? `/profile/projects` : \"/\"} />\n          {isProjectPage && (\n            <>\n              <Divider className=\"h-8 w-8 text-gray-200\" />\n              <ProjectSelect />\n            </>\n          )}\n        </div>\n        <div\n          className={`flex items-center justify-center gap-3 sm:gap-4 ${\n            user ? \"flex-row-reverse\" : \"\"\n          }`}\n        >\n          {user ? (\n            <UserDropdown />\n          ) : (\n            <div>\n              <button\n                className=\"auth-btn rounded-full px-4 py-1.5 text-sm font-medium text-gray-500 transition-colors ease-out hover:text-black\"\n                onClick={openAuthModal}\n              >\n                Log in\n              </button>\n              <button\n                className=\"auth-btn rounded-full border border-black bg-black px-4 py-1.5 text-sm text-white transition-all hover:bg-white hover:text-black\"\n                onClick={openAuthModal}\n              >\n                Sign Up\n              </button>\n            </div>\n          )}\n          <a\n            href=\"https://twitter.com/aipagedev\"\n            className=\"flex items-center text-gray-900 hover:text-blue-500 transition-colors\"\n            target=\"_blank\"\n            rel=\"noreferrer\"\n          >\n            <svg\n              className=\"mr-1 h-5 w-5 fill-current\"\n              xmlns=\"http://www.w3.org/2000/svg\"\n              viewBox=\"0 0 24 24\"\n              stroke=\"currentColor\"\n              strokeWidth=\"2\"\n              strokeLinecap=\"round\"\n              strokeLinejoin=\"round\"\n            >\n              <path d=\"M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z\"></path>\n            </svg>\n          </a>\n          {user && (\n            <Button\n              className=\"auth-btn\"\n              variant=\"pill\"\n              onClick={openPricesModal}\n            >\n              Buy Credits 🎉\n            </Button>\n          )}\n        </div>\n      </div>\n    </header>\n  );\n}\n"
  },
  {
    "path": "components/IconMenu.tsx",
    "content": "import { ReactNode } from \"react\";\n\ninterface MenuIconProps {\n  icon: ReactNode;\n  text: string;\n}\n\nexport default function IconMenu({ icon, text }: MenuIconProps) {\n  return (\n    <div className=\"flex items-center justify-start space-x-2\">\n      {icon}\n      <p className=\"text-sm\">{text}</p>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/ListProjects.tsx",
    "content": "\"use client\";\nimport { useAuth } from \"@/context/AuthContext\";\nimport Link from \"next/link\";\nimport Badge, { BadgeVariant } from \"@/components/Badge\";\nimport { Project } from \"@/types\";\nimport ProjectIcon from \"@/components/ProjectIcon\";\nimport { useEffect } from \"react\";\nimport useProjectList from \"@/hooks/useProjectList\";\nimport useProject from \"@/hooks/useProject\";\n\nconst mails = [\n  \"ozgurozalp1999@gmail.com\",\n  \"mail@ozgurozalp.com\",\n  \"denizlevregi7@gmail.com\",\n  \"umit@altogic.com\",\n  \"umit.cakmak@altogic.com\",\n];\n\ninterface ListProjectsProps {\n  projects?: Project[] | null;\n}\n\nexport default function ListProjects({ projects }: ListProjectsProps) {\n  const { user } = useAuth();\n  const { setProject } = useProject();\n  const setProjects = useProjectList((state) => state.setProjects);\n  const _projects = useProjectList((state) => state.projects);\n\n  const hasPermission = mails.includes(user?.email as string);\n  const Component = hasPermission ? Link : \"div\";\n\n  useEffect(() => {\n    setProjects(projects ?? []);\n  }, []);\n\n  return (\n    <>\n      {_projects?.map((project) => (\n        <Component\n          key={project._id}\n          className=\"flex h-full flex-col space-y-10 rounded-lg border border-gray-100 bg-white p-4 sm:p-6 transition-all\"\n          href={`/profile/projects/${project._id}`}\n          onClick={() => setProject(project)}\n        >\n          <div className=\"flex flex-1 items-start justify-between gap-1\">\n            <div className=\"flex space-x-3 flex-1\">\n              <ProjectIcon className=\"shrink-0 self-start\" />\n              <div className=\"flex-1\">\n                <h2 className=\"text-lg leading-[1.2] font-medium text-gray-700 truncate max-w-[15ch] md:max-w-[10ch] lg:max-w-[15ch] xl:max-w-[15ch]\">\n                  {project?.name ?? project?.content}\n                </h2>\n                <div className=\"flex items-center\">\n                  <p className=\"text-gray-500 text-sm leading-[1]\">\n                    example.com\n                  </p>\n                </div>\n              </div>\n            </div>\n            <Badge\n              variant={badgeMap[project.status ?? \"draft\"]}\n              text={project.status ?? \"draft\"}\n            />\n          </div>\n          <div className=\"flex mt-auto items-center space-x-4\">\n            <div className=\"flex items-center space-x-2 text-gray-500\">\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width={24}\n                height={24}\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth={2}\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                className=\"h-4 w-4\"\n              >\n                <circle cx={12} cy={12} r={10} />\n                <line x1={2} x2={22} y1={12} y2={12} />\n                <path d=\"M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z\" />\n              </svg>\n              <h2 className=\"whitespace-nowrap text-sm\">\n                {project.domains ? project.domains.length : 0}{\" \"}\n                {project.domains?.length > 1 ? \"domains\" : \"domain\"}\n              </h2>\n            </div>\n            <div className=\"flex items-center space-x-2 text-gray-500\">\n              <svg\n                xmlns=\"http://www.w3.org/2000/svg\"\n                width={24}\n                height={24}\n                viewBox=\"0 0 24 24\"\n                fill=\"none\"\n                stroke=\"currentColor\"\n                strokeWidth={2}\n                strokeLinecap=\"round\"\n                strokeLinejoin=\"round\"\n                className=\"h-4 w-4\"\n              >\n                <line x1={18} x2={18} y1={20} y2={10} />\n                <line x1={12} x2={12} y1={20} y2={4} />\n                <line x1={6} x2={6} y1={20} y2={14} />\n              </svg>\n              <h2 className=\"whitespace-nowrap text-sm\">\n                {project.click ?? 0}\n                {project.click > 1 ? \" clicks\" : \" click\"}\n              </h2>\n            </div>\n          </div>\n        </Component>\n      ))}\n    </>\n  );\n}\n\nconst badgeMap: Record<string, BadgeVariant> = {\n  draft: \"black\",\n  live: \"green\",\n};\n"
  },
  {
    "path": "components/LoadingIcon.tsx",
    "content": "export default function LoadingIcon({ className }: { className?: string }) {\n  return (\n    <svg\n      version=\"1.1\"\n      id=\"L9\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n      xmlnsXlink=\"http://www.w3.org/1999/xlink\"\n      x=\"0px\"\n      y=\"0px\"\n      viewBox=\"0 0 100 100\"\n      enableBackground=\"new 0 0 0 0\"\n      xmlSpace=\"preserve\"\n      className={className}\n    >\n      <path\n        fill=\"currentColor\"\n        d=\"M73,50c0-12.7-10.3-23-23-23S27,37.3,27,50 M30.9,50c0-10.5,8.5-19.1,19.1-19.1S69.1,39.5,69.1,50\"\n      >\n        <animateTransform\n          attributeName=\"transform\"\n          attributeType=\"XML\"\n          type=\"rotate\"\n          dur=\"1s\"\n          from=\"0 50 50\"\n          to=\"360 50 50\"\n          repeatCount=\"indefinite\"\n        />\n      </path>\n    </svg>\n  );\n}\n"
  },
  {
    "path": "components/Logo.tsx",
    "content": "import Link from \"next/link\";\n\ninterface LogoProps {\n  href?: string;\n}\n\nexport default function Logo({ href }: LogoProps) {\n  const Component = href ? Link : \"span\";\n  return (\n    <Component\n      href={href as string}\n      className=\"flex items-center tracking-tight\"\n    >\n      <strong className=\"font-bold text-xl\">ai</strong>\n      <span className=\"text-xl\">page.dev</span>\n    </Component>\n  );\n}\n"
  },
  {
    "path": "components/LogoutIcon.tsx",
    "content": "export default function LogoutIcon({ className }: { className: string }) {\n  return (\n    <svg\n      fill=\"none\"\n      height=\"24\"\n      shapeRendering=\"geometricPrecision\"\n      stroke=\"currentColor\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      strokeWidth=\"1.5\"\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      className={className}\n    >\n      <path d=\"M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4\" />\n      <path d=\"M16 17l5-5-5-5\" />\n      <path d=\"M21 12H9\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "components/Modal.tsx",
    "content": "import { XIcon } from \"lucide-react\";\nimport { MouseEvent, ReactNode, useRef } from \"react\";\nimport { cn } from \"@/utils/helpers\";\n\ninterface ModalProps {\n  close: () => void;\n  isOpen: boolean;\n  className?: string;\n  children: ReactNode;\n}\nexport default function Modal({\n  close,\n  children,\n  isOpen,\n  className,\n}: ModalProps) {\n  const modalWrapper = useRef<HTMLDivElement>(null);\n  function modalWrapperClickHandler(event: MouseEvent) {\n    if (!modalWrapper.current || event.target !== modalWrapper.current) return;\n    close();\n  }\n\n  if (!isOpen) return null;\n  return (\n    <div\n      ref={modalWrapper}\n      className=\"fixed modal inset-0 bg-white/60 backdrop-blur-sm flex items-center justify-center z-50\"\n      onClick={modalWrapperClickHandler}\n    >\n      <div\n        className={cn(\n          \"relative overflow-auto max-h-full bg-white w-full sm:w-[400px] border border-gray-100 rounded-2xl shadow-xl p-4\",\n          className,\n        )}\n      >\n        <button\n          type=\"button\"\n          className=\"absolute top-4 right-4 text-gray-500 hover:text-black transition-colors focus:outline-none\"\n          onClick={close}\n        >\n          <XIcon />\n        </button>\n        {children}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/NavLink.tsx",
    "content": "\"use client\";\n\nimport { usePathname } from \"next/navigation\";\nimport Link, { LinkProps } from \"next/link\";\nimport { ReactNode } from \"react\";\nimport { cn } from \"@/utils/helpers\";\n\ninterface NavLinkProps extends LinkProps {\n  children: ReactNode;\n  className?: string;\n  target?: \"_blank\" | \"_self\" | \"_parent\" | \"_top\" | undefined;\n}\nexport default function NavLink({\n  className,\n  href,\n  children,\n  target,\n  ...props\n}: NavLinkProps) {\n  const path = usePathname();\n  return (\n    <Link\n      href={href}\n      data-active={path === href}\n      className={cn(className)}\n      target={target}\n      {...props}\n    >\n      {children}\n    </Link>\n  );\n}\n"
  },
  {
    "path": "components/Popover.tsx",
    "content": "import * as PopoverPrimitive from \"@radix-ui/react-dropdown-menu\";\nimport { Dispatch, ReactNode, SetStateAction } from \"react\";\n\nexport default function Popover({\n  children,\n  content,\n  align = \"center\",\n}: {\n  children: ReactNode;\n  content: ReactNode | string;\n  align?: \"center\" | \"start\" | \"end\";\n}) {\n  return (\n    <PopoverPrimitive.Root>\n      <PopoverPrimitive.Trigger asChild>{children}</PopoverPrimitive.Trigger>\n      <PopoverPrimitive.Content\n        sideOffset={8}\n        align={align}\n        className=\"z-50 animate-slide-up-fade items-center rounded-md border border-gray-200 bg-white drop-shadow-lg block\"\n      >\n        {content}\n      </PopoverPrimitive.Content>\n    </PopoverPrimitive.Root>\n  );\n}\nPopover.Item = PopoverPrimitive.Item;\n"
  },
  {
    "path": "components/PricesModal.tsx",
    "content": "\"use client\";\nimport useSearchParams from \"@/hooks/useSearchParams\";\nimport Modal from \"@/components/Modal\";\nimport { Product } from \"@/types\";\nimport Products from \"@/components/Products\";\n\nexport default function PricesModal({ products }: { products: Product[] }) {\n  const { deleteByKey, has } = useSearchParams();\n\n  function close() {\n    deleteByKey(\"pricesModal\");\n  }\n\n  return (\n    <Modal\n      className=\"sm:w-[850px] p-6\"\n      close={close}\n      isOpen={has(\"pricesModal\")}\n    >\n      <Products products={products} />\n    </Modal>\n  );\n}\n"
  },
  {
    "path": "components/Product.tsx",
    "content": "\"use client\";\nimport { cn, moneyFormat, stripePrice } from \"@/utils/helpers\";\nimport { useAuth } from \"@/context/AuthContext\";\nimport useSearchParams from \"@/hooks/useSearchParams\";\nimport { useState } from \"react\";\nimport { Product as ProductType } from \"@/types\";\nimport LoadingSpinner from \"@/components/loadingSpinner\";\nimport Button from \"@/components/Button\";\n\ninterface ProductProps {\n  product: ProductType;\n  className?: string;\n}\nexport default function Product({ product, className }: ProductProps) {\n  const [loading, setLoading] = useState(false);\n  const { user } = useAuth();\n  const { set } = useSearchParams();\n\n  async function getPaymentLink(priceId: string) {\n    if (!user) return set(\"authModal\", \"true\");\n\n    try {\n      setLoading(true);\n      const res = await fetch(\"/api/create-checkout-session\", {\n        method: \"POST\",\n        body: JSON.stringify({ priceId }),\n      });\n\n      const { url } = await res.json();\n      location.href = url;\n    } catch (e) {\n      setLoading(false);\n    }\n  }\n\n  return (\n    <div\n      className={cn(\n        \"border border-gray-200 rounded-lg shadow-sm divide-y divide-gray-200\",\n        className\n      )}\n    >\n      <div className=\"flex h-72 gap-5 flex-col\">\n        <div className=\"px-4 pt-4\">\n          <div className=\"flex flex-col gap-2 items-center justify-center text-center\">\n            <p className=\"text-gray-600 text-xs text-center py-2\">\n              {product.metadata.description}\n            </p>\n            <span className=\"font-display text-4xl font-semibold text-gray-900\">\n              {moneyFormat(stripePrice(product.unit_amount))}\n            </span>\n            <span className=\"text-xs text-gray-500\">\n              {product.nickname} Credits\n            </span>\n          </div>\n        </div>\n        <div className=\"flex-1 flex flex-col\">\n          <div className=\"flex w-full h-14 items-center justify-center border-b border-t border-gray-200 bg-gray-50\">\n            <p className=\"text-gray-600 text-xs text-center\">\n              {product.metadata.info}\n            </p>\n          </div>\n          <div className=\"px-4 flex-1 flex items-center w-full justify-center\">\n            <Button\n              disabled={loading}\n              className=\"auth-btn w-full\"\n              variant=\"pill\"\n              onClick={() => getPaymentLink(product.id)}\n            >\n              {loading ? <LoadingSpinner /> : \"Buy\"}\n            </Button>\n          </div>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/Products.tsx",
    "content": "\"use client\";\nimport { Product as ProductType } from \"@/types\";\nimport Product from \"@/components/Product\";\nimport { cn } from \"@/utils/helpers\";\n\nexport default function Products({ products }: { products: ProductType[] }) {\n  return (\n    <div className=\"space-y-10 py-10 px-4 sm:px-6 lg:px-8\">\n      <div className=\"sm:flex sm:flex-col sm:align-center\">\n        <h1 className=\"text-4xl text-center font-extrabold text-gray-900 sm:text-center\">\n          Support Our Mission\n        </h1>\n        <p className=\"text-center mt-5 text-lg text-gray-500 sm:text-center\">\n          By choosing a plan, you’re not just building beautiful landing pages —\n          you’re becoming a cherished part of our journey and mission. Fuel our\n          work so we can continue empowering your digital dreams. Every credit\n          counts!\n        </p>\n      </div>\n      <div className=\"grid sm:grid-cols-3 gap-6\">\n        {products\n          .sort((a, b) => a.unit_amount - b.unit_amount)\n          .map((product, index) => (\n            <Product\n              className={cn(index === 1 && \"scale-110\")}\n              key={product.id}\n              product={product}\n            />\n          ))}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/ProfileLayout.tsx",
    "content": "\"use client\";\nimport { ReactNode } from \"react\";\nimport ProfileMenu, { MenuItem } from \"@/components/ProfileMenu\";\nimport { useParams } from \"next/navigation\";\n\nexport default function ProfileLayout({ children }: { children: ReactNode }) {\n  const { id } = useParams();\n\n  const hasIdMenu: MenuItem[] = [\n    {\n      id: 1,\n      name: \"Design\",\n      href: `/profile/projects/${id}`,\n    },\n    {\n      id: 2,\n      name: \"Domains\",\n      href: `/profile/projects/${id}/domains`,\n    },\n    {\n      id: 3,\n      name: \"Preview\",\n      href: `/profile/projects/${id}/preview`,\n      target: \"_blank\",\n    },\n    {\n      id: 4,\n      name: \"Integrations\",\n      href: `/profile/projects/${id}/integrations`,\n    },\n    {\n      id: 5,\n      name: \"Settings\",\n      href: `/profile/projects/${id}/settings`,\n    },\n  ];\n  const hasNoIdMenu: MenuItem[] = [\n    {\n      id: 1,\n      name: \"Projects\",\n      href: \"/profile/projects\",\n    },\n    {\n      id: 2,\n      name: \"Invoices\",\n      href: \"/profile/invoices\",\n    },\n    {\n      id: 3,\n      name: \"Settings\",\n      href: \"/profile/settings\",\n    },\n  ];\n\n  return (\n    <div className=\"pt-[72px] profile-page flex flex-col\">\n      <ProfileMenu menuItems={id ? hasIdMenu : hasNoIdMenu} />\n      {children}\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/ProfileMenu.tsx",
    "content": "\"use client\";\n\nimport { cn } from \"@/utils/helpers\";\nimport { useAuth } from \"@/context/AuthContext\";\nimport NavLink from \"@/components/NavLink\";\n\nconst mails = [\n  \"ozgurozalp1999@gmail.com\",\n  \"mail@ozgurozalp.com\",\n  \"denizlevregi7@gmail.com\",\n];\n\nexport interface MenuItem {\n  id: number;\n  name: string;\n  href: string;\n  target?: \"_blank\" | \"_self\" | \"_parent\" | \"_top\" | undefined;\n}\n\ninterface ProfileMenuProps {\n  menuItems: MenuItem[];\n  className?: string;\n}\n\nexport default function ProfileMenu({\n  menuItems,\n  className,\n}: ProfileMenuProps) {\n  const { user } = useAuth();\n\n  return (\n    <div\n      className={cn(\n        \"flex border-b h-12 items-center justify-start space-x-2 overflow-x-auto scrollbar-hide px-6\",\n        className,\n      )}\n    >\n      {menuItems\n        .filter(\n          (link) =>\n            mails.includes(user?.email as string) || link.name !== \"Projects\",\n        )\n        .map((link) => (\n          <NavLink\n            className={cn(\n              \"border-b-2 p-1 border-transparent text-black\",\n              \"data-[active=true]:border-black\",\n            )}\n            href={link.href}\n            key={link.href}\n            target={link.target}\n          >\n            <div className=\"rounded-md px-3 py-2 transition-all duration-75 hover:bg-gray-100 active:bg-gray-200\">\n              <p className=\"text-sm\">{link.name}</p>\n            </div>\n          </NavLink>\n        ))}\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/ProjectDesign.tsx",
    "content": "\"use client\";\n\nimport { Project } from \"@/types\";\nimport BrowserWindow from \"@/components/BrowserWindow\";\nimport { FormEvent, useEffect, useRef, useState } from \"react\";\nimport Button from \"@/components/Button\";\nimport LoadingSpinner from \"@/components/loadingSpinner\";\nimport { updateProject } from \"@/utils/helpers\";\n\ninterface ProjectDesignProps {\n  project: Project;\n}\n\nconst includedKeys = [\n  \"padding\",\n  \"color\",\n  \"width\",\n  \"height\",\n  \"margin\",\n  \"fontSize\",\n  \"display\",\n  \"position\",\n  \"top\",\n  \"left\",\n  \"right\",\n  \"bottom\",\n  \"border\",\n  \"background\",\n  \"transform\",\n];\n\nexport default function ProjectDesign(props: ProjectDesignProps) {\n  const iframe = useRef<HTMLIFrameElement>(null);\n  const [selected, setSelected] = useState<HTMLElement | null>(null);\n  const [saving, setSaving] = useState(false);\n\n  useEffect(() => {\n    const element = iframe.current;\n    if (!element) return;\n\n    let eventElements: NodeListOf<HTMLElement> | undefined;\n    let aElements: NodeListOf<HTMLAnchorElement> | undefined;\n\n    setTimeout(() => {\n      eventElements = element.contentDocument?.querySelectorAll(\"body *\");\n      eventElements?.forEach((element) => {\n        element.addEventListener(\"mouseover\", mouseoverListener);\n        element.addEventListener(\"click\", clickHandler);\n        element.addEventListener(\"mouseout\", mouseoutListener);\n      });\n      aElements = element.contentDocument?.querySelectorAll(\"a\");\n      aElements?.forEach((element) => {\n        element.addEventListener(\"click\", aElementClickHandler);\n      });\n    }, 2000);\n\n    return () => {\n      eventElements?.forEach((element) => {\n        element.removeEventListener(\"mouseover\", mouseoverListener);\n        element.removeEventListener(\"click\", clickHandler);\n        element.removeEventListener(\"mouseout\", mouseoutListener);\n      });\n      aElements?.forEach((element) => {\n        element.removeEventListener(\"click\", aElementClickHandler);\n      });\n    };\n  }, []);\n\n  function aElementClickHandler(e: Event) {\n    e.preventDefault();\n  }\n\n  function mouseoverListener(e: Event) {\n    setOutline(e.target as HTMLElement);\n  }\n\n  function clickHandler(e: Event) {\n    removeOutline(e.target as HTMLElement);\n    setSelected(e.target as HTMLElement);\n  }\n\n  function mouseoutListener(e: Event) {\n    removeOutline(e.target as HTMLElement);\n  }\n\n  function setOutline(target: HTMLElement) {\n    target.style.outline = \"2px solid #79155B\";\n    target.style.outlineOffset = \"3px\";\n  }\n\n  function removeOutline(target: HTMLElement) {\n    target.style.outline = \"\";\n    target.style.outlineOffset = \"\";\n  }\n\n  async function onSubmit(e: FormEvent) {\n    e.preventDefault();\n    if (!selected) return;\n    const formData = new FormData(e.target as HTMLFormElement);\n    const values = Array.from(formData.entries()) as [string, string][];\n\n    values\n      .filter(([, value]) => !!value)\n      .forEach(([key, value]) => {\n        if (key === \"textContent\") {\n          selected.innerText = value;\n        } else {\n          if (selected.style.getPropertyValue(key) !== value)\n            selected.style.setProperty(key, value);\n        }\n      });\n\n    try {\n      setSaving(true);\n      const html = iframe.current?.contentDocument?.documentElement\n        .outerHTML as string;\n      await updateProject(\n        {\n          result: html,\n        },\n        props.project._id,\n      );\n      setSaving(false);\n    } catch {\n      alert(\"Failed to save changes\");\n      setSaving(false);\n    }\n  }\n\n  const items = !selected\n    ? []\n    : Object.entries(getComputedStyle(selected)).filter(\n        ([key]) =>\n          !key.match(/\\d/) &&\n          includedKeys.includes(key) &&\n          !key.startsWith(\"webkit\"),\n      );\n\n  const innerText = selected?.innerText;\n\n  return (\n    <div className=\"w-full h-full max-h-[calc(100vh-168px)] grid items-start gap-4 sm:grid-cols-[200px_1fr] xl:grid-cols-[300px_1fr]\">\n      <div className=\"border rounded-xl overflow-y-auto h-full max-h-[calc(100vh-168px)]\">\n        {selected && (\n          <form\n            onSubmit={onSubmit}\n            className=\"grid h-full grid-rows-[1fr_60px] gap-2 overflow-y-auto\"\n          >\n            <div className=\"overflow-y-auto max-h-full pt-4 px-4\">\n              <div>\n                <label>Text Content</label>\n                <input\n                  name=\"textContent\"\n                  className=\"border-gray-200 rounded w-full\"\n                  type=\"text\"\n                  key={selected.innerText}\n                  defaultValue={innerText}\n                />\n              </div>\n              {items.map(([key, value]) => (\n                <div key={key}>\n                  <label>{key}</label>\n                  <input\n                    name={key}\n                    className=\"border-gray-200 rounded w-full\"\n                    type=\"text\"\n                    key={value}\n                    defaultValue={value}\n                  />\n                </div>\n              ))}\n            </div>\n            <div className=\"bg-white flex items-center px-4\">\n              <Button className=\"w-full\" type=\"submit\">\n                {saving ? <LoadingSpinner /> : \"Save changes\"}\n              </Button>\n            </div>\n          </form>\n        )}\n      </div>\n      <BrowserWindow className=\"w-full h-full max-h-[calc(100vh-168px)]\">\n        <iframe\n          ref={iframe}\n          className=\"w-full h-full\"\n          srcDoc={props.project?.result}\n        />\n      </BrowserWindow>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/ProjectIcon.tsx",
    "content": "import { cn } from \"@/utils/helpers\";\n\nexport default function ProjectIcon({ className }: { className?: string }) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"50\"\n      viewBox=\"0 0 200 150\"\n      className={cn(className)}\n    >\n      <rect\n        x=\"10\"\n        y=\"10\"\n        width=\"180\"\n        height=\"130\"\n        rx=\"10\"\n        ry=\"10\"\n        fill=\"#F0F0F0\"\n        stroke=\"#CCCCCC\"\n      />\n\n      <rect\n        x=\"10\"\n        y=\"10\"\n        width=\"180\"\n        height=\"30\"\n        rx=\"10\"\n        ry=\"10\"\n        fill=\"#f0f0f0\"\n      />\n\n      <circle cx=\"25\" cy=\"25\" r=\"4\" fill=\"#FF605C\" />\n      <circle cx=\"40\" cy=\"25\" r=\"4\" fill=\"#FFBD44\" />\n      <circle cx=\"55\" cy=\"25\" r=\"4\" fill=\"#28CA41\" />\n\n      <rect\n        x=\"20\"\n        y=\"50\"\n        width=\"160\"\n        height=\"30\"\n        rx=\"10\"\n        ry=\"10\"\n        fill=\"#FFFFFF\"\n      />\n      <rect\n        x=\"25\"\n        y=\"58\"\n        width=\"120\"\n        rx=\"5\"\n        ry=\"5\"\n        height=\"14\"\n        fill=\"#F0F0F0\"\n      />\n\n      <rect x=\"20\" y=\"90\" width=\"50\" rx=\"10\" ry=\"10\" height=\"40\" fill=\"#ddd\" />\n      <rect x=\"75\" y=\"90\" width=\"50\" rx=\"10\" ry=\"10\" height=\"40\" fill=\"#ddd\" />\n      <rect x=\"130\" y=\"90\" width=\"50\" rx=\"5\" ry=\"10\" height=\"40\" fill=\"#ddd\" />\n\n      <circle cx=\"165\" cy=\"64\" r=\"8\" fill=\"#F0F0F0\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "components/ProjectSelect.tsx",
    "content": "import { ChevronsUpDown, PlusCircle, Check } from \"lucide-react\";\nimport Link from \"next/link\";\nimport Popover from \"@/components/Popover\";\nimport ProjectIcon from \"@/components/ProjectIcon\";\nimport { usePathname } from \"next/navigation\";\nimport useProjectList from \"@/hooks/useProjectList\";\nimport useProject from \"@/hooks/useProject\";\nimport { useEffect } from \"react\";\n\nexport default function ProjectSelect() {\n  const { project } = useProject();\n\n  return (\n    <div>\n      <Popover content={<ProjectList />}>\n        <button className=\"flex items-center justify-between rounded-lg bg-white p-1.5 text-left text-sm transition-all duration-75 hover:bg-gray-100 focus:outline-none active:bg-gray-200\">\n          <div className=\"flex items-center space-x-3 pr-2\">\n            <ProjectIcon className=\"h-7 w-7 shrink-0\" />\n            <div className=\"flex items-center space-x-3 sm:flex\">\n              <span className=\"inline-block truncate text-sm font-medium max-w-[15ch]\">\n                {project?.name ?? project?.content}\n              </span>\n            </div>\n          </div>\n          <ChevronsUpDown\n            className=\"h-4 w-4 text-gray-400\"\n            aria-hidden=\"true\"\n          />\n        </button>\n      </Popover>\n    </div>\n  );\n}\n\nfunction ProjectList() {\n  const { projects } = useProjectList();\n  const { setProject } = useProject();\n  const pathname = usePathname();\n\n  return (\n    <div className=\"relative mt-1 max-h-72 w-full space-y-0.5 rounded-md bg-white p-2 text-base sm:w-60 sm:text-sm sm:shadow-lg\">\n      <div className=\"p-2 text-xs text-gray-500\">Projects</div>\n      <div className=\"overflow-y-auto scrollbar-hide max-h-[200px]\">\n        {projects.map((project) => (\n          <Popover.Item asChild key={project._id}>\n            <Link\n              className=\"relative flex w-full items-center space-x-2 rounded-md px-2 py-1.5 hover:bg-gray-100 active:bg-gray-200 transition-all duration-75 outline-none focus:outline-none\"\n              href={`/profile/projects/${project._id}`}\n              onClick={() => setProject(project)}\n              shallow={false}\n            >\n              <ProjectIcon className=\"h-7 w-7 shrink-0\" />\n              <span\n                title={project.content}\n                className=\"block truncate text-sm max-w-[15ch]\"\n              >\n                {project?.name ?? project?.content}\n              </span>\n              {pathname.includes(`/profile/projects/${project._id}`) && (\n                <span className=\"absolute inset-y-0 right-0 flex items-center pr-3 text-black\">\n                  <Check className=\"h-5 w-5\" aria-hidden=\"true\" />\n                </span>\n              )}\n            </Link>\n          </Popover.Item>\n        ))}\n      </div>\n      <Link\n        href=\"/\"\n        className=\"flex w-full cursor-pointer items-center space-x-2 rounded-md p-2 transition-all duration-75 hover:bg-gray-100\"\n      >\n        <PlusCircle className=\"h-6 w-6 text-gray-500\" />\n        <span className=\"block truncate\">Add a new project</span>\n      </Link>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/RateModal.tsx",
    "content": "\"use client\";\nimport useSearchParams from \"@/hooks/useSearchParams\";\nimport Modal from \"@/components/Modal\";\nimport { Rating as ReactRating } from \"@smastrom/react-rating\";\nimport { FormEvent, useState } from \"react\";\nimport Button from \"@/components/Button\";\nimport { Star } from \"@smastrom/react-rating\";\nimport LoadingSpinner from \"@/components/loadingSpinner\";\n\nexport default function RateModal({ show }: { show: boolean }) {\n  const { deleteByKey, has } = useSearchParams();\n  const [rating, setRating] = useState(0);\n  const [ratingText, setRatingText] = useState(\"\");\n  const [loading, setLoading] = useState(false);\n  const [hasError, setHasError] = useState(false);\n\n  function close() {\n    deleteByKey(\"rateModal\");\n    setRating(0);\n    setRatingText(\"\");\n  }\n\n  async function submitHandler(event: FormEvent) {\n    event.preventDefault();\n    setLoading(true);\n    setHasError(false);\n    try {\n      const res = await fetch(\"/api/message\", {\n        method: \"PUT\",\n        headers: {\n          \"Content-Type\": \"application/json\",\n        },\n        body: JSON.stringify({\n          rating,\n          ratingText,\n        }),\n      });\n      if (res.ok) close();\n      else {\n        setHasError(true);\n      }\n    } catch (e) {\n      setHasError(true);\n    } finally {\n      setLoading(false);\n    }\n  }\n\n  return (\n    <Modal\n      close={close}\n      isOpen={has(\"rateModal\")}\n      className=\"p-0 sm:w-[500px] w-[96%]\"\n    >\n      <form onSubmit={submitHandler}>\n        <div className=\"p-4 space-y-4\">\n          <h2 className=\"text-2xl font-medium text-center pt-6\">\n            <span className=\"text-6xl pb-3 block\">🎉</span>\n            Rate your experience\n          </h2>\n          <div className=\"flex items-center justify-center\">\n            <ReactRating\n              itemStyles={{\n                itemShapes: Star,\n                activeFillColor: \"#ffdc17\",\n                activeBoxBorderColor: \"#faaf00\",\n                itemStrokeWidth: 1,\n                activeStrokeColor: \"transparent\",\n                inactiveFillColor: \"#d7d7d7\",\n                inactiveStrokeColor: \"transparent\",\n              }}\n              className=\"!w-3/5 mb-1\"\n              value={rating}\n              onChange={setRating}\n            />\n          </div>\n        </div>\n        <div className=\"bg-gray-50 p-4 border-t space-y-4\">\n          <p className=\"text-sm text-gray-500\">\n            Your feedback matters! Let us know how your experience was while\n            crafting your landing page with AI\n          </p>\n          <textarea\n            rows={3}\n            name=\"feedback\"\n            value={ratingText}\n            onChange={(e) => setRatingText(e.target.value)}\n            className=\"w-full p-2 border !border-gray-200 resize-none rounded !outline-none focus:ring-2 focus:ring-gray-500\"\n            placeholder=\"Tell us more about your experience...\"\n          />\n          {hasError && (\n            <h2 className=\"text-red-500 text-center text-sm\">\n              There was an error submitting your feedback. Please try again\n              later.\n            </h2>\n          )}\n          <div className=\"flex justify-end gap-2\">\n            <Button type=\"button\" variant=\"light\" onClick={close}>\n              Cancel\n            </Button>\n            <Button\n              className=\"gap-2\"\n              type=\"submit\"\n              disabled={loading}\n              variant=\"default\"\n            >\n              {loading && <LoadingSpinner className=\"h-4 w-4\" />}\n              Submit\n            </Button>\n          </div>\n        </div>\n      </form>\n    </Modal>\n  );\n}\n"
  },
  {
    "path": "components/ShowRate.tsx",
    "content": "\"use client\";\nimport { Rating, Star } from \"@smastrom/react-rating\";\n\ninterface ShowRateProps {\n  rate: number;\n}\nexport default function ShowRate({ rate }: ShowRateProps) {\n  return (\n    <Rating\n      itemStyles={{\n        itemShapes: Star,\n        activeFillColor: \"#faaf00\",\n        activeBoxBorderColor: \"#faaf00\",\n        itemStrokeWidth: 1,\n        activeStrokeColor: \"transparent\",\n        inactiveStrokeColor: \"#faaf00\",\n      }}\n      readOnly\n      className=\"!w-1/3 grid grid-cols-5\"\n      value={rate}\n    />\n  );\n}\n"
  },
  {
    "path": "components/Switch.tsx",
    "content": "\"use client\";\n\nimport { Dispatch, SetStateAction } from \"react\";\n// @ts-ignore\nimport * as SwitchPrimitive from \"@radix-ui/react-switch\";\nimport { cn } from \"@/utils/helpers\";\n\nconst Switch = ({\n  fn,\n  checked = false,\n  disabled = false,\n}: {\n  fn: Dispatch<SetStateAction<boolean>> | (() => void);\n  checked?: boolean;\n  disabled?: boolean;\n}) => {\n  return (\n    <SwitchPrimitive.Root\n      checked={checked}\n      name=\"switch\"\n      onCheckedChange={(checked: boolean) => fn(checked)}\n      disabled={disabled}\n      className={cn(\n        disabled\n          ? \"cursor-not-allowed data-[state=checked]:bg-gray-300\"\n          : \"cursor-pointer focus:outline-none focus-visible:ring focus-visible:ring-blue-500 focus-visible:ring-opacity-75 data-[state=checked]:bg-blue-500 data-[state=unchecked]:bg-gray-200\",\n        `relative inline-flex h-4 w-8 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out`,\n      )}\n    >\n      <SwitchPrimitive.Thumb\n        className={cn(\n          \"data-[state=unchecked]:translate-x-0\",\n          `pointer-events-none h-3 w-3 translate-x-4 transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out`,\n        )}\n      />\n    </SwitchPrimitive.Root>\n  );\n};\n\nexport default Switch;\n"
  },
  {
    "path": "components/UserDropdown.tsx",
    "content": "\"use client\";\nimport { Gem, ReceiptIcon, Settings } from \"lucide-react\";\nimport Popover from \"@/components/Popover\";\nimport Badge from \"@/components/Badge\";\nimport { useAuth } from \"@/context/AuthContext\";\nimport IconMenu from \"@/components/IconMenu\";\nimport LogoutIcon from \"@/components/LogoutIcon\";\nimport Link from \"next/link\";\n\nexport default function UserDropdown() {\n  const { user } = useAuth();\n  return (\n    <Popover\n      content={\n        <div className=\"flex w-full flex-col space-y-px rounded-md bg-white p-3 sm:w-56\">\n          <div className=\"p-2\">\n            {user?.name && (\n              <p className=\"truncate text-sm font-medium text-gray-900\">\n                {user?.name}\n              </p>\n            )}\n            <p className=\"truncate text-sm text-gray-500\">{user?.email}</p>\n          </div>\n          <div className=\"w-full rounded-md p-2 text-sm transition-all duration-75 hover:bg-gray-100 active:bg-gray-200 flex justify-between\">\n            <IconMenu text=\"Credits\" icon={<Gem className=\"h-4 w-4\" />} />\n            <Badge\n              text={user?.credits.toString() as string}\n              variant={user?.credits === 0 ? \"red\" : \"yellow\"}\n            />\n          </div>\n          <Popover.Item asChild>\n            <Link\n              href=\"/profile/invoices\"\n              className=\"block !outline-none w-full rounded-md p-2 text-sm transition-all duration-75 hover:bg-gray-100 active:bg-gray-200\"\n            >\n              <IconMenu\n                text=\"Invoices\"\n                icon={<ReceiptIcon className=\"h-4 w-4\" />}\n              />\n            </Link>\n          </Popover.Item>\n          <Popover.Item asChild>\n            <Link\n              href=\"/profile/settings\"\n              className=\"block !outline-none w-full rounded-md p-2 text-sm transition-all duration-75 hover:bg-gray-100 active:bg-gray-200\"\n            >\n              <IconMenu\n                text=\"Settings\"\n                icon={<Settings className=\"h-4 w-4\" />}\n              />\n            </Link>\n          </Popover.Item>\n          <Popover.Item asChild>\n            <a\n              href=\"/api/logout\"\n              className=\"block w-full !outline-none rounded-md p-2 text-sm transition-all duration-75 hover:bg-gray-100 active:bg-gray-200\"\n            >\n              <IconMenu\n                text=\"Logout\"\n                icon={<LogoutIcon className=\"h-4 w-4\" />}\n              />\n            </a>\n          </Popover.Item>\n        </div>\n      }\n      align=\"end\"\n    >\n      <button className=\"group relative shrink-0 !outline-none\">\n        {user ? (\n          <img\n            alt={user?.email || \"Avatar for logged in user\"}\n            src={\n              user.profilePicture ||\n              `https://avatars.dicebear.com/api/micah/${user?.email}.svg`\n            }\n            className=\"h-9 shrink-0 w-9 rounded-full border border-gray-300 transition-all duration-75 group-focus:outline-none group-active:scale-95 sm:h-10 sm:w-10\"\n          />\n        ) : (\n          <div className=\"h-9 w-9 animate-pulse rounded-full border border-gray-300 bg-gray-100 sm:h-10 sm:w-10\" />\n        )}\n        {user?.credits === 0 && (\n          <div className=\"absolute -bottom-0.5 -right-0.5 h-4 w-4 rounded-full border-2 border-white bg-red-500\" />\n        )}\n      </button>\n    </Popover>\n  );\n}\n"
  },
  {
    "path": "components/XCircleFill.tsx",
    "content": "export default function XCircleFill({ className }: { className?: string }) {\n  return (\n    <svg\n      className={className}\n      viewBox=\"0 0 24 24\"\n      width=\"24\"\n      height=\"24\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      fill=\"none\"\n      shapeRendering=\"geometricPrecision\"\n    >\n      <circle cx=\"12\" cy=\"12\" r=\"10\" fill=\"currentColor\" />\n      <path d=\"M15 9l-6 6\" stroke=\"white\" />\n      <path d=\"M9 9l6 6\" stroke=\"white\" />\n    </svg>\n  );\n}\n"
  },
  {
    "path": "components/customDropdown.tsx",
    "content": "import Image from \"next/image\";\nimport { useState } from \"react\";\n\ntype TechIcon = {\n  name: string;\n  url: string;\n};\n\ntype Stack = {\n  tech: TechIcon[];\n  eta: string;\n};\n\ntype CustomDropdownProps = {\n  stacks: Stack[];\n  onSelect: (stack: Stack) => void;\n};\n\nexport const CustomDropdown: React.FC<CustomDropdownProps> = ({\n  stacks,\n  onSelect,\n}) => {\n  const [isLoading, setIsLoading] = useState(false);\n\n  const handleSelect = (stack: Stack) => {\n    setIsLoading(true);\n    onSelect(stack);\n    setTimeout(() => {\n      setIsLoading(false);\n    }, parseDuration(stack.eta));\n  };\n\n  const parseDuration = (duration: string) => {\n    const [value, unit] = duration.split(\" \");\n    return parseInt(value) * (unit === \"min\" ? 60000 : 1000);\n  };\n\n  return (\n    <div className=\"origin-bottom-left relative right-16 mt-8 w-48 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5\">\n      <div\n        className=\"py-1\"\n        role=\"menu\"\n        aria-orientation=\"vertical\"\n        aria-labelledby=\"options-menu\"\n      >\n        {stacks.map((stack, index) => (\n          <a\n            href=\"#\"\n            key={index}\n            className=\"block px-4 py-2 text-xs text-gray-700 hover:bg-gray-100 hover:text-gray-900\"\n            role=\"menuitem\"\n            onClick={() => handleSelect(stack)}\n          >\n            {stack.tech.map((tech, idx, arr) => (\n              <span key={idx}>\n                <Image\n                  src={tech.url}\n                  alt={tech.name}\n                  width={16}\n                  height={16}\n                  className=\"inline-block h-4 w-4 mr-1\"\n                />\n                {idx < arr.length - 1 && <span className=\"mx-1\">+</span>}\n              </span>\n            ))}\n            / {stack.eta}\n          </a>\n        ))}\n        {isLoading && (\n          <div className=\"flex justify-center items-center py-2\"></div>\n        )}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "components/loadingSpinner.module.css",
    "content": ".spinner {\n  --spinner-color: currentColor;\n  position: relative;\n  top: 50%;\n  left: 50%;\n}\n.spinner div {\n  animation: spinner 1.2s linear infinite;\n  background: var(--spinner-color, gray);\n  position: absolute;\n  border-radius: 1rem;\n  width: 30%;\n  height: 8%;\n  left: -10%;\n  top: -4%;\n}\n\n.spinner div:nth-child(1) {\n  animation-delay: -1.2s;\n  transform: rotate(1deg) translate(120%);\n}\n.spinner div:nth-child(2) {\n  animation-delay: -1.1s;\n  transform: rotate(30deg) translate(120%);\n}\n.spinner div:nth-child(3) {\n  animation-delay: -1s;\n  transform: rotate(60deg) translate(120%);\n}\n.spinner div:nth-child(4) {\n  animation-delay: -0.9s;\n  transform: rotate(90deg) translate(120%);\n}\n.spinner div:nth-child(5) {\n  animation-delay: -0.8s;\n  transform: rotate(120deg) translate(120%);\n}\n.spinner div:nth-child(6) {\n  animation-delay: -0.7s;\n  transform: rotate(150deg) translate(120%);\n}\n.spinner div:nth-child(7) {\n  animation-delay: -0.6s;\n  transform: rotate(180deg) translate(120%);\n}\n.spinner div:nth-child(8) {\n  animation-delay: -0.5s;\n  transform: rotate(210deg) translate(120%);\n}\n.spinner div:nth-child(9) {\n  animation-delay: -0.4s;\n  transform: rotate(240deg) translate(120%);\n}\n.spinner div:nth-child(10) {\n  animation-delay: -0.3s;\n  transform: rotate(270deg) translate(120%);\n}\n.spinner div:nth-child(11) {\n  animation-delay: -0.2s;\n  transform: rotate(300deg) translate(120%);\n}\n.spinner div:nth-child(12) {\n  animation-delay: -0.1s;\n  transform: rotate(330deg) translate(120%);\n}\n\n@keyframes spinner {\n  0% {\n    opacity: 1;\n  }\n  100% {\n    opacity: 0;\n  }\n}\n"
  },
  {
    "path": "components/loadingSpinner.tsx",
    "content": "import { cn } from \"@/utils/helpers\";\nimport styles from \"./loadingSpinner.module.css\";\n\nexport default function LoadingSpinner({\n  className,\n  style,\n}: {\n  className?: string;\n  style?: Record<string, any>;\n}) {\n  return (\n    <div className={cn(\"h-5 w-5\", className)}>\n      <div className={cn(styles.spinner, \"h-5 w-5\", className)} style={style}>\n        {[...Array(12)].map((_, i) => (\n          <div key={i} />\n        ))}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "components/tweetButton.tsx",
    "content": "import { useState } from \"react\";\nimport Head from \"next/head\";\n\nconst TweetButton = () => {\n  // Array of possible tweet texts\n  const tweetIntents = [\n    \"Just used AI to craft an EPIC landing page in minutes with AIpage.dev ! 🤖 This is the future of web design! Check it out 👉 @aipagedev\",\n    \"Creating a stunning webpage has never been easier thanks to AIpage.dev! 🚀 Give it a try 👉 @aipagedev\",\n    \"Web design will never be the same after you try AIpage.dev! 🛠️ A whole new level of creativity unleashed! Check it out 👉 @aipagedev\",\n    \"Revolutionize your web design process with AIpage.dev. The future is here! 👉 @aipagedev\",\n    \"I just built an amazing webpage with AIpage.dev in minutes! 🌟 You have to try this 👉 @aipagedev\",\n    \"AIpage.dev is a game-changer for web design! Say hello to efficiency 👋 @aipagedev\",\n    \"Why spend hours on web design when AIpage.dev can do it in minutes? 🕒 Check it out! 👉 @aipagedev\",\n    \"Impressed by the power of AI in web design with AIpage.dev! This is incredible 👀 @aipagedev\",\n    \"I used AIpage.dev and it completely transformed how I approach web design. You need to try this! 🎉 @aipagedev\",\n    \"Just when I thought web design couldn’t get any easier, I found AIpage.dev! 🎊 Try it now 👉 @aipagedev\",\n    \"Unleashing my inner designer with the help of AIpage.dev. This is next level! 🚀 Check it out 👉 @aipagedev\",\n    \"With AIpage.dev, I can focus on creativity while AI handles the coding. It’s amazing! 💥 @aipagedev\",\n  ];\n\n  // Function to generate a random index for selecting a tweet text\n  const getRandomIndex = () => {\n    return Math.floor(Math.random() * tweetIntents.length);\n  };\n\n  // Initial tweet text state\n  const [tweet, setTweet] = useState(tweetIntents[getRandomIndex()]);\n\n  // Function to capture iframe content (assuming you want this function)\n  const handleClick = () => {\n    // Randomize the tweet text after capturing the iframe content\n    setTweet(tweetIntents[getRandomIndex()]);\n  };\n\n  // Randomly select a tweet text from the tweetIntents array\n\n  return (\n    <>\n      <a\n        onClick={handleClick}\n        href={`https://twitter.com/intent/tweet?text=${encodeURIComponent(\n          tweet\n        )}`}\n        target=\"_blank\"\n        rel=\"noreferrer\"\n        className=\"text-2xl animate-blink\"\n      >\n        🐦\n      </a>\n    </>\n  );\n};\n\nexport default TweetButton;\n"
  },
  {
    "path": "context/AuthContext.tsx",
    "content": "\"use client\";\n\nimport { createContext, ReactNode, useContext, useState } from \"react\";\nimport { User } from \"@/types\";\n\ninterface AuthContext {\n  user: User | null;\n  setUser: (user: User | null) => void;\n}\n\nexport const AuthContext = createContext<AuthContext>({\n  user: null,\n  setUser: () => {},\n});\n\ninterface AuthProviderProps {\n  children: ReactNode;\n  user: User | null;\n}\n\nexport const AuthProvider = ({ user, children }: AuthProviderProps) => {\n  const [_user, setUser] = useState<User | null>(user);\n\n  function setAuthUser(user: User | null) {\n    setUser(user);\n  }\n\n  return (\n    <AuthContext.Provider value={{ user: _user, setUser: setAuthUser }}>\n      {children}\n    </AuthContext.Provider>\n  );\n};\n\nexport const useAuth = () => {\n  const context = useContext(AuthContext);\n  if (context === undefined) {\n    throw new Error(\"useAuth must be used within a AuthProvider\");\n  }\n  return context;\n};\n"
  },
  {
    "path": "custom.css",
    "content": "@keyframes spin {\n  0% {\n    transform: rotate(0deg);\n  }\n  100% {\n    transform: rotate(360deg);\n  }\n}\n\n@keyframes blink {\n  0% {\n    opacity: 1;\n  }\n  50% {\n    opacity: 0.5;\n  }\n  100% {\n    opacity: 1;\n  }\n}\n"
  },
  {
    "path": "hooks/useProject.tsx",
    "content": "\"use client\";\n\nimport { create } from \"zustand\";\nimport { devtools } from \"zustand/middleware\";\nimport { Domain, Project } from \"@/types\";\nimport { useEffect } from \"react\";\nimport useProjectList from \"@/hooks/useProjectList\";\n\ninterface ProjectStore {\n  project: Project | null;\n  setProject: (project: Project | null) => void;\n  addDomain: (domain: Domain) => void;\n  removeDomain: (id: string) => void;\n}\n\nconst useProject = create<ProjectStore>()(\n  devtools(\n    (set) => ({\n      project: null,\n      setProject: (project) => {\n        set({ project });\n        useProjectList.setState((prev) => {\n          const projects = prev.projects.map((p) =>\n            p._id === project?._id ? project : p,\n          );\n          return { projects };\n        });\n      },\n      addDomain: (domain) => {\n        set((prev) => {\n          if (!prev.project) return prev;\n          const project = {\n            ...prev.project,\n            domains: [...prev.project.domains, domain],\n          };\n          return { project };\n        });\n      },\n      removeDomain: (id) => {\n        set((prev) => {\n          if (!prev.project) return prev;\n          const project = {\n            ...prev.project,\n            domains: prev.project.domains.filter((d) => d._id !== id),\n          };\n          return { project };\n        });\n      },\n    }),\n    {\n      name: \"project-storage\",\n    },\n  ),\n);\n\nexport function SetProject({ project }: { project: Project | null }) {\n  useEffect(() => {\n    useProject.setState({ project });\n  }, []);\n\n  return <></>;\n}\n\nexport default useProject;\n"
  },
  {
    "path": "hooks/useProjectList.tsx",
    "content": "\"use client\";\n\nimport { create } from \"zustand\";\nimport { devtools } from \"zustand/middleware\";\nimport { Project } from \"@/types\";\nimport { useEffect } from \"react\";\n\ninterface ProjectList {\n  projects: Project[];\n  setProjects: (projects: Project[]) => void;\n  updateProject: (id: string, project: Project) => void;\n  deleteProject: (id: string) => void;\n}\n\nconst useProjectList = create<ProjectList>()(\n  devtools(\n    (set) => ({\n      projects: [],\n      setProjects: (projects) => set({ projects }),\n      updateProject: (id, project) =>\n        set((state) => ({\n          projects: state.projects.map((p) => (p._id === id ? project : p)),\n        })),\n      deleteProject: (id) =>\n        set((state) => ({\n          projects: state.projects.filter((p) => p._id !== id),\n        })),\n    }),\n    {\n      name: \"project-list-storage\",\n    },\n  ),\n);\n\nexport function SetProjects({ projects }: { projects: Project[] }) {\n  useEffect(() => {\n    useProjectList.setState({ projects });\n  }, []);\n\n  return <></>;\n}\n\nexport default useProjectList;\n"
  },
  {
    "path": "hooks/useSearchParams.ts",
    "content": "\"use client\";\nimport { useSearchParams as useNextSearchParams } from \"next/navigation\";\nimport { usePathname, useRouter } from \"next/navigation\";\n\nexport default function useSearchParams() {\n  const searchParams = useNextSearchParams();\n  const router = useRouter();\n  const path = usePathname();\n\n  function getURL() {\n    return new URL(path, window.location.origin);\n  }\n\n  function get(key: string) {\n    return searchParams.get(key);\n  }\n\n  function set(key: string, value: string) {\n    const params = getURL();\n    params.searchParams.set(key, value);\n    router.push(params.toString());\n  }\n\n  function deleteByKey(key: string) {\n    const params = getURL();\n    params.searchParams.delete(key);\n    router.push(params.toString());\n  }\n\n  function has(key: string) {\n    return searchParams.has(key);\n  }\n\n  return {\n    set,\n    has,\n    get,\n    deleteByKey,\n  };\n}\n"
  },
  {
    "path": "next.config.js",
    "content": "/** @type {import('next').NextConfig} */\nconst nextConfig = {}\n\nmodule.exports = nextConfig\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"next-openai\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev -H 0.0.0.0\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"lint\": \"next lint\"\n  },\n  \"dependencies\": {\n    \"@radix-ui/react-alert-dialog\": \"^1.0.4\",\n    \"@radix-ui/react-dialog\": \"^1.0.4\",\n    \"@radix-ui/react-dropdown-menu\": \"^2.0.5\",\n    \"@radix-ui/react-popover\": \"^1.0.6\",\n    \"@radix-ui/react-switch\": \"^1.0.3\",\n    \"@smastrom/react-rating\": \"^1.3.2\",\n    \"@upstash/ratelimit\": \"^0.4.3\",\n    \"@upstash/redis\": \"^1.22.0\",\n    \"ai\": \"^2.1.34\",\n    \"altogic\": \"^2.3.9\",\n    \"class-variance-authority\": \"^0.7.0\",\n    \"clsx\": \"^2.0.0\",\n    \"html-react-parser\": \"^4.2.2\",\n    \"html2canvas\": \"^1.4.1\",\n    \"lucide-react\": \"^0.268.0\",\n    \"monaco-editor\": \"^0.40.0\",\n    \"next\": \"13.4.19\",\n    \"openai-edge\": \"^1.2.2\",\n    \"re-resizable\": \"^6.9.9\",\n    \"react\": \"18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-frame-component\": \"^5.2.6\",\n    \"react-html-parser\": \"^2.0.2\",\n    \"react-resizable\": \"^3.0.5\",\n    \"react-select\": \"^5.7.3\",\n    \"tailwind-merge\": \"^1.14.0\",\n    \"tailwind-scrollbar-hide\": \"^1.1.7\",\n    \"tailwindcss-animate\": \"^1.0.7\",\n    \"zustand\": \"^4.4.1\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/forms\": \"^0.5.4\",\n    \"@types/node\": \"^17.0.12\",\n    \"@types/react\": \"18.2.7\",\n    \"@types/react-dom\": \"18.2.4\",\n    \"@types/react-html-parser\": \"^2.0.2\",\n    \"@types/react-resizable\": \"^3.0.4\",\n    \"autoprefixer\": \"^10.4.14\",\n    \"eslint\": \"^7.32.0\",\n    \"eslint-config-next\": \"13.4.4-canary.11\",\n    \"postcss\": \"^8.4.23\",\n    \"prettier\": \"^3.0.1\",\n    \"tailwindcss\": \"^3.3.2\",\n    \"typescript\": \"5.0.4\"\n  }\n}\n"
  },
  {
    "path": "postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {}\n  }\n}\n"
  },
  {
    "path": "public/site.webmanifest",
    "content": "{\n  \"name\": \"\",\n  \"short_name\": \"\",\n  \"icons\": [\n    {\n      \"src\": \"assets/android-chrome-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"assets/android-chrome-512x512.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\"\n    }\n  ],\n  \"theme_color\": \"#ffffff\",\n  \"background_color\": \"#ffffff\",\n  \"display\": \"standalone\"\n}\n"
  },
  {
    "path": "styles/custom.css",
    "content": "@keyframes spin {\n  0% {\n    transform: rotate(0deg);\n  }\n  100% {\n    transform: rotate(360deg);\n  }\n}\n\n@keyframes blink {\n  0% {\n    opacity: 1;\n  }\n  50% {\n    opacity: 0.5;\n  }\n  100% {\n    opacity: 1;\n  }\n}\n\n.blinking {\n  animation: blink 1s infinite;\n}\n"
  },
  {
    "path": "styles/globals.css",
    "content": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\nhtml,\nbody,\n.profile-page {\n  min-height: 100dvh;\n}\n\n@keyframes spin {\n  0% {\n    transform: rotate(0deg);\n  }\n  100% {\n    transform: rotate(360deg);\n  }\n}\n\n@keyframes blink {\n  0% {\n    opacity: 1;\n  }\n  50% {\n    opacity: 0.5;\n  }\n  100% {\n    opacity: 1;\n  }\n}\n\n.blinking {\n  animation: blink 1s infinite;\n}\n\nbody:has(.modal) {\n  overflow: hidden;\n}\n\n.auth-btn {\n  @apply relative overflow-hidden;\n}\n\n.auth-btn::before {\n  -webkit-animation: authBtnAnimation 3.5s;\n  animation: authBtnAnimation 3.5s;\n  -webkit-animation-delay: 1.8s;\n  animation-delay: 1.8s;\n  -webkit-animation-iteration-count: infinite;\n  animation-iteration-count: infinite;\n  -webkit-animation-timing-function: ease-out;\n  animation-timing-function: ease-out;\n  background: linear-gradient(\n    180deg,\n    rgba(255, 255, 255, 0) 0,\n    rgba(255, 255, 255, 0.18) 25%,\n    rgba(255, 255, 255, 0.3) 50%,\n    rgba(255, 255, 255, 0.18) 75%,\n    rgba(255, 255, 255, 0)\n  );\n  content: \"\";\n  display: block;\n  height: 90px;\n  left: -60%;\n  position: absolute;\n  top: -150px;\n  transform: rotate(-25deg);\n  width: 200px;\n}\n\n@keyframes authBtnAnimation {\n  0% {\n    left: -100%;\n    top: -150px;\n  }\n\n  50% {\n    left: 100%;\n    top: 150px;\n  }\n\n  to {\n    left: 100%;\n    top: 150px;\n  }\n}\n"
  },
  {
    "path": "tailwind.config.js",
    "content": "/** @type {import('tailwindcss').Config} */\nmodule.exports = {\n  content: [\n    \"./pages/**/*.{js,ts,jsx,tsx,mdx}\",\n    \"./components/**/*.{js,ts,jsx,tsx,mdx}\",\n    \"./app/**/*.{js,ts,jsx,tsx,mdx}\",\n  ],\n  theme: {\n    extend: {\n      backgroundImage: {\n        \"gradient-radial\": \"radial-gradient(var(--tw-gradient-stops))\",\n        \"gradient-conic\":\n          \"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))\",\n      },\n\n      outline: {\n        blue: \"2px solid #007BFF\",\n      },\n      animation: {\n        blink: \"blink 1s infinite\",\n      },\n    },\n  },\n  plugins: [\n    require(\"@tailwindcss/forms\"),\n    require(\"tailwind-scrollbar-hide\"),\n    require(\"tailwindcss-animate\"),\n  ],\n};\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"preserve\",\n    \"incremental\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"paths\": {\n      \"@/*\": [\"./*\"]\n    }\n  },\n  \"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "types/index.ts",
    "content": "import type { User as AltogicUser } from \"altogic\";\n\nexport interface User extends AltogicUser {\n  credits: number;\n}\n\nexport interface Product {\n  id: string;\n  object: string;\n  active: boolean;\n  billing_scheme: string;\n  created: number;\n  currency: \"usd\" | string;\n  custom_unit_amount: null;\n  livemode: boolean;\n  lookup_key: string | null;\n  metadata: {\n    description: string;\n    info: string;\n  };\n  nickname: string;\n  product: string;\n  recurring: string | null;\n  tax_behavior: string;\n  tiers_mode: string | null;\n  transform_quantity: string | null;\n  type: string;\n  unit_amount: number;\n  unit_amount_decimal: string;\n}\n\nexport interface Project {\n  _id: string;\n  content: string;\n  name: string;\n  deletedAt: string;\n  result: string;\n  rating: number;\n  role: string;\n  ratingText: string;\n  user: string | User;\n  createdAt: string;\n  status: \"draft\" | \"live\";\n  updatedAt: string;\n  click: number;\n  domains: Domain[];\n}\n\nexport interface Domain {\n  _id: string;\n  updatedAt: string;\n  createdAt: string;\n  _parent: string;\n  isPrimary: boolean;\n  clickCount: number;\n  status: DomainVerificationStatusProps;\n  domain: string;\n}\n\nexport interface Invoice {\n  id: string;\n  object: string;\n  account_country: string;\n  account_name: string;\n  account_tax_ids: null | [];\n  created: number;\n  currency: string;\n  livemode: boolean;\n  paid: boolean;\n  status: string;\n  total: number;\n  lines: {\n    object: \"list\";\n    data: {\n      price: {\n        id: \"price_1NfeLpFctreK8fHPFa5RIeKt\";\n        object: \"price\";\n        active: true;\n        billing_scheme: \"per_unit\";\n        created: number;\n        currency: \"usd\" | string;\n        livemode: false;\n        lookup_key: null;\n        metadata: {\n          description: string;\n          info: string;\n        };\n        nickname: \"100\";\n      };\n      quantity: 1;\n    }[];\n  };\n  hosted_invoice_url: string;\n  invoice_pdf: string;\n}\n\nexport type DomainVerificationStatusProps =\n  | \"Valid Configuration\"\n  | \"Invalid Configuration\"\n  | \"Pending Verification\"\n  | \"Domain Not Found\"\n  | \"Unknown Error\";\n\nexport interface DomainResponse {\n  name: string;\n  apexName: string;\n  projectId: string;\n  redirect?: string | null;\n  redirectStatusCode?: (307 | 301 | 302 | 308) | null;\n  gitBranch?: string | null;\n  updatedAt?: number;\n  createdAt?: number;\n  verified: boolean;\n  verification?: {\n    type: string;\n    domain: string;\n    value: string;\n    reason: string;\n  }[];\n}\n\nexport interface DomainInfo {\n  configured: boolean;\n  name: string;\n  apexName: string;\n  projectId: string;\n  redirect?: string | null;\n  redirectStatusCode?: (307 | 301 | 302 | 308) | null;\n  gitBranch?: string | null;\n  updatedAt?: number;\n  createdAt?: number;\n  verified: boolean;\n  verification?: {\n    type: string;\n    domain: string;\n    value: string;\n    reason: string;\n  }[];\n}\n"
  },
  {
    "path": "utils/altogic.ts",
    "content": "import { createClient } from \"altogic\";\n\nconst clientKey = process.env.NEXT_PUBLIC_ALTOGIC_CLIENT_KEY;\nconst apiBaseURL = process.env.NEXT_PUBLIC_ALTOGIC_API_BASE_URL;\n\nif (!clientKey || !apiBaseURL) {\n  throw new Error(\n    \"Please define the NEXT_PUBLIC_ALTOGIC_CLIENT_KEY and NEXT_PUBLIC_ALTOGIC_API_BASE_URL environment variables inside .env file\",\n  );\n}\n\nconst altogic = createClient(apiBaseURL, clientKey, {\n  signInRedirect: \"/\",\n});\n\nexport default altogic;\n"
  },
  {
    "path": "utils/auth.ts",
    "content": "import { cookies, headers } from \"next/headers\";\nimport { Invoice, Product, Project, User } from \"@/types\";\nimport altogic from \"@/utils/altogic\";\nimport { NextResponse } from \"next/server\";\n\nconst isDev = process.env.NODE_ENV === \"development\";\n\nexport function getSessionCookie() {\n  const cookieStore = cookies();\n  const token = cookieStore.get(\"sessionToken\");\n  return token?.value;\n}\n\nexport async function fetchAuthUser() {\n  const token = getSessionCookie();\n  if (!token) return;\n\n  // @ts-ignore\n  altogic.auth.setSession({\n    token,\n  });\n\n  const { user } = await altogic.auth.getUserFromDB();\n  return user as User;\n}\n\nexport async function fetchProducts(): Promise<Product[]> {\n  const path = process.env.NEXT_PUBLIC_GET_PRICES_PATH as string;\n  const { data, errors } = await altogic.endpoint.get(path);\n  if (errors) throw new Error(\"Failed to fetch Products\");\n  return data.data;\n}\n\nexport async function fetchInvoices(): Promise<Invoice[]> {\n  const path = process.env.NEXT_PUBLIC_GET_INVOICES_PATH as string;\n  const { data } = await altogic.endpoint.get(path, undefined, {\n    Session: getSessionCookie(),\n  });\n  return data?.data ?? [];\n}\n\nexport async function updateUser(data: Partial<User>) {\n  const token = getSessionCookie();\n  if (!token) throw new Error(\"No token found\");\n\n  // @ts-ignore\n  altogic.auth.setSession({\n    token,\n  });\n\n  const { user, errors } = await altogic.auth.getUserFromDB();\n\n  if (!user || errors) throw new Error(\"Failed to fetch User\");\n\n  return await altogic.db\n    .model(\"users\")\n    .object(user?._id)\n    .update(data);\n}\n\nexport async function deleteUser() {\n  const token = getSessionCookie();\n  if (!token) throw new Error(\"No token found\");\n\n  // @ts-ignore\n  altogic.auth.setSession({\n    token,\n  });\n\n  const { user, errors } = await altogic.auth.getUserFromDB();\n\n  if (!user || errors) throw new Error(\"Failed to fetch User\");\n\n  return await altogic.db\n    .model(\"users\")\n    .object(user?._id)\n    .delete();\n}\n\nexport function logout(req: Request, nextResponse: typeof NextResponse) {\n  /*altogic.auth\n    .signOut(token?.value)\n    .then(console.log)\n    .catch(console.error);*/\n\n  const destinationUrl = new URL(\"/\", new URL(req.url).origin);\n  const response = nextResponse.redirect(destinationUrl);\n  response.cookies.delete(\"sessionToken\");\n  return response;\n}\n\nexport async function fetchProjects(): Promise<Project[] | null> {\n  const { data, errors } = await altogic.endpoint.get(\"/projects\", undefined, {\n    Session: getSessionCookie(),\n  });\n  if (errors) throw new Error(\"Failed to fetch Projects\");\n  return data.result;\n}\n\nexport async function fetchProjectById(id: string): Promise<Project | null> {\n  const regex = /^[a-fA-F0-9]{24}$/g; // mongo id regex\n  if (!regex.test(id)) return null;\n\n  const { data, errors } = await altogic.endpoint.get(\n    \"/project/\" + id,\n    undefined,\n    {\n      Session: getSessionCookie(),\n    },\n  );\n  if (errors) {\n    console.log(JSON.stringify(errors, null, 4));\n    return null;\n  }\n  return data as Project;\n}\n\nexport async function updateProjectName(id: string, name: string) {\n  const token = getSessionCookie();\n\n  const { data: project, errors } = await altogic.endpoint.put(\n    `/project/name/${id}`,\n    {\n      name,\n    },\n    undefined,\n    {\n      Session: getSessionCookie(),\n    },\n  );\n\n  return { project, errors };\n}\n\nexport async function deleteProject(id: string) {\n  const { errors } = await altogic.endpoint.delete(\n    `/project`,\n    {\n      id,\n    },\n    undefined,\n    {\n      Session: getSessionCookie(),\n    },\n  );\n\n  return { errors };\n}\n\nexport async function getProjectByDomain(\n  domain: string,\n): Promise<Project | null> {\n  const res = await fetch(\n    `${process.env.NEXT_PUBLIC_ALTOGIC_API_BASE_URL}/project/domain`,\n    {\n      method: \"POST\",\n      headers: {\n        \"Content-Type\": \"application/json\",\n      },\n      body: JSON.stringify({\n        domain: domain.replace(\"www.\", \"\"),\n      }),\n    },\n  );\n\n  try {\n    const data = await res.json();\n    if (data.errors) return null;\n    return data;\n  } catch {\n    return null;\n  }\n}\n"
  },
  {
    "path": "utils/helpers.ts",
    "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\nimport { Project } from \"@/types\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n\nexport function stripePrice(price: number) {\n  return price / 100;\n}\n\nexport function moneyFormat(price: number) {\n  return new Intl.NumberFormat(\"en-US\", {\n    style: \"currency\",\n    currency: \"USD\",\n  }).format(price);\n}\n\nexport const getSubdomain = (name: string, apexName: string) => {\n  if (name === apexName) return null;\n  return name.slice(0, name.length - apexName.length - 1);\n};\n\nexport function capitalize(str: string) {\n  if (!str || typeof str !== \"string\") return str;\n  return str.charAt(0).toUpperCase() + str.slice(1);\n}\n\nexport const truncate = (str: string | null, length: number) => {\n  if (!str || str.length <= length) return str;\n  return `${str.slice(0, length - 3)}...`;\n};\n\nexport function toReversed<T>(arr: T[]) {\n  const array = [...arr];\n  return array.reverse();\n}\n\nexport async function updateProject(\n  data: Omit<Partial<Project>, \"_id\">,\n  id?: string,\n) {\n  const body = {\n    ...data,\n    ...(id && { _id: id }),\n  };\n  const res = await fetch(\"/api/message\", {\n    headers: {\n      \"Content-Type\": \"application/json\",\n    },\n    method: \"PUT\",\n    body: JSON.stringify(body),\n  });\n\n  return res.json();\n}\n\nexport function wait(ms: number) {\n  return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport function isAipage(host: string) {\n  const hosts = [\n    \"localhost\",\n    \"localhost:3000\",\n    \"localhost:3000\",\n    \"ozgurozalp.test\",\n    \"aipage-dev.vercel.app\",\n  ];\n  return hosts.includes(host);\n}\n"
  },
  {
    "path": "utils/redis.ts",
    "content": "import { Redis } from \"@upstash/redis\";\n\nconst redis =\n  !!process.env.UPSTASH_REDIS_REST_URL && !!process.env.UPSTASH_REDIS_REST_TOKEN\n    ? new Redis({\n        url: process.env.UPSTASH_REDIS_REST_URL,\n        token: process.env.UPSTASH_REDIS_REST_TOKEN,\n      })\n    : undefined;\n\nexport default redis;\n"
  }
]