Repository: zinedkaloc/aipage.dev Branch: master Commit: 508cff651c7c Files: 105 Total size: 179.8 KB Directory structure: gitextract_yf13pds3/ ├── .github/ │ └── ISSUE_TEMPLATE/ │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app/ │ ├── (aipage)/ │ │ ├── layout.tsx │ │ ├── loading.tsx │ │ ├── not-found.tsx │ │ ├── page.tsx │ │ └── profile/ │ │ ├── invoices/ │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── projects/ │ │ │ ├── [id]/ │ │ │ │ ├── domains/ │ │ │ │ │ ├── layout.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── integrations/ │ │ │ │ │ └── page.tsx │ │ │ │ ├── layout.tsx │ │ │ │ ├── loading.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── settings/ │ │ │ │ └── page.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ └── settings/ │ │ ├── loading.tsx │ │ └── page.tsx │ ├── (preview)/ │ │ ├── not-found.tsx │ │ └── profile/ │ │ └── projects/ │ │ └── [id]/ │ │ └── preview/ │ │ ├── loading.tsx │ │ └── page.tsx │ ├── api/ │ │ ├── chat/ │ │ │ └── route.ts │ │ ├── create-checkout-session/ │ │ │ └── route.ts │ │ ├── domain/ │ │ │ ├── check-domain/ │ │ │ │ └── route.ts │ │ │ ├── remove-domain/ │ │ │ │ └── route.ts │ │ │ └── verify-domain/ │ │ │ └── route.ts │ │ ├── logout/ │ │ │ └── route.ts │ │ ├── message/ │ │ │ └── route.ts │ │ ├── og/ │ │ │ └── route.tsx │ │ ├── project/ │ │ │ ├── [id]/ │ │ │ │ ├── domain/ │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ └── route.ts │ │ └── user/ │ │ └── route.ts │ ├── auth-redirect/ │ │ └── route.ts │ ├── layout.tsx │ └── not-found.tsx ├── components/ │ ├── AddDomainModal.tsx │ ├── AlertCircleFill.tsx │ ├── AlertDialog.tsx │ ├── AuthModal.tsx │ ├── Badge.tsx │ ├── BrowserWindow.tsx │ ├── Button.tsx │ ├── Chart.tsx │ ├── CheckCircleFill.tsx │ ├── ConfiguredSection.tsx │ ├── ConfirmDialog.tsx │ ├── DeleteAccountConfirmDialog.tsx │ ├── DeleteProjectConfirmDialog.tsx │ ├── Divider.tsx │ ├── DomainCard.tsx │ ├── DomainConfiguration.tsx │ ├── Drawer.tsx │ ├── HTMLPreview.tsx │ ├── Header.tsx │ ├── IconMenu.tsx │ ├── ListProjects.tsx │ ├── LoadingIcon.tsx │ ├── Logo.tsx │ ├── LogoutIcon.tsx │ ├── Modal.tsx │ ├── NavLink.tsx │ ├── Popover.tsx │ ├── PricesModal.tsx │ ├── Product.tsx │ ├── Products.tsx │ ├── ProfileLayout.tsx │ ├── ProfileMenu.tsx │ ├── ProjectDesign.tsx │ ├── ProjectIcon.tsx │ ├── ProjectSelect.tsx │ ├── RateModal.tsx │ ├── ShowRate.tsx │ ├── Switch.tsx │ ├── UserDropdown.tsx │ ├── XCircleFill.tsx │ ├── customDropdown.tsx │ ├── loadingSpinner.module.css │ ├── loadingSpinner.tsx │ └── tweetButton.tsx ├── context/ │ └── AuthContext.tsx ├── custom.css ├── hooks/ │ ├── useProject.tsx │ ├── useProjectList.tsx │ └── useSearchParams.ts ├── next.config.js ├── package.json ├── postcss.config.js ├── public/ │ └── site.webmanifest ├── styles/ │ ├── custom.css │ └── globals.css ├── tailwind.config.js ├── tsconfig.json ├── types/ │ └── index.ts └── utils/ ├── altogic.ts ├── auth.ts ├── helpers.ts └── redis.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a bug report for AI landing page generator title: "[BUG]" labels: '' assignees: '' --- # Bug Report **Description** ⚠️ Please provide a clear and concise description of the bug. **Steps to Reproduce** ⚠️ Please provide step-by-step instructions to reproduce the bug. **Expected Behavior** ⚠️ Please describe what you expected to happen. **Actual Behavior** ⚠️ Please describe what actually happened. **Additional Information** ⚠️ Add any additional information or context about the bug here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- # Feature Request **Description** Please provide a clear and concise description of the feature request. **Proposed Solution** Please describe the proposed solution or new feature in detail. **Alternatives Considered** Please describe any alternative solutions or features you've considered. **Additional Information** Add any additional information or context about the feature request here. ================================================ FILE: .gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies .env /node_modules /.pnp .pnp.js # testing /coverage # next.js /.next/ /out/ /.idea # production /build # misc .DS_Store *.pem # debug npm-debug.log* yarn-debug.log* yarn-error.log* # local env files .env*.local # vercel .vercel # typescript *.tsbuildinfo next-env.d.ts ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Code of Conduct As 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. ## Our Standards Examples of behavior that contributes to creating a positive environment include: - Being respectful and considerate of others' opinions and ideas. - Using inclusive language and being mindful of our words and their impact. - Being open to constructive feedback and providing feedback in a respectful manner. - Showing empathy and kindness towards others. - Focusing on collaboration and fostering a supportive community. Examples of unacceptable behavior include: - Harassment, discrimination, or derogatory comments and personal attacks. - Any form of offensive or inappropriate language or imagery. - Trolling, flaming, or insulting/derogatory comments. - Intimidating or bullying behavior. - Any other conduct that could be considered inappropriate in a professional setting. ## Scope This Code of Conduct applies to all project contributors, both online and offline, as well as in all project-related spaces. ## Enforcement Instances 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. ## Consequences Any 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. ## Attribution This 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). ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to AI Landing Page Generator We welcome and appreciate contributions from the community! By contributing to the AI Landing Page Generator project, you can help us make it even better. ## Ways to Contribute There are several ways you can contribute to this project: - 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. - 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. - 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. ## Guidelines for Pull Requests To ensure smooth collaboration and maintain code quality, please follow these guidelines when submitting a pull request: 1. Fork the repository and create a new branch for your changes. 2. Before making changes, make sure your branch is up to date with the master repository. 3. Follow the coding style and conventions used in the project. 4. Include clear and concise commit messages that describe the purpose of your changes. 5. Provide a detailed description of the changes you've made in the pull request. 6. Test your changes thoroughly to ensure they work as intended. 7. Make sure your code is properly documented. 8. Ensure that your changes do not introduce any breaking changes to the existing functionality. 9. Be responsive to any feedback or questions regarding your pull request. 10. Once your changes are approved, they will be merged into the master repository. ## Code of Conduct Please 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. ## Questions or Concerns If you have any questions or concerns regarding the project or the contribution process, please feel free to [zinedkaloc](https://twitter.com/zinedkaloc). Thank you for your interest in contributing to the AI Landing Page Generator project! ================================================ FILE: LICENSE ================================================ MIT License Permission 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE 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. ================================================ FILE: README.md ================================================ # AI Landing Page Generator [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) ## Description ![A landing page generator](/public/logo.png) The 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. ## Installation 1. Clone the repository: `git clone https://github.com/zinedkaloc/aipage.dev.git` 2. Install the required dependencies: `pnpm install` 3. Navigate to the project directory `cd aipage.dev` 4. Copy the `.env.example` file to `.env` and fill in the required information. > 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. ## Usage 1. Run the generator: `pnpm dev` 2. Open the browser and go to `http://localhost:3000` 3. Describe your website and click on the "Enter". 4. Wait for the genie to generate the landing page. ### Test prompts > 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. > 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. > 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. ## Roadmap - [x] Create a basic landing page generator. - [x] Display the generated landing page in the main page with an iframe. - [ ] Update text input to textarea and add submit button. - [ ] Add support to download the generated landing page as a zip file. - [ ] Add support to deploy the generated landing page to Vercel or Netlify with a single click. - [ ] Add support for dark mode. - [ ] Add support for multiple pages. - [ ] Add support for multiple languages. This 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. ## Contributing Thank you for considering contributing to this project! To contribute, follow these steps: 1. Fork the repository. 2. Create a new branch for your feature/fix: `git checkout -b feature/your-feature-name`. 3. Add your changes with: `git add .`. 4. Make your changes and commit them: `git commit -m "Add your feature description"`. 5. Push to the branch: `git push origin feature/your-feature-name`. 6. 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. 7. Wait for the maintainers to review your pull request. Make any necessary changes or address any feedback provided. 8. Once your pull request is approved, it will be merged into the `master` branch. Congratulations on your contribution! ## License This project is licensed under the [MIT License](LICENSE). ## Contact For any inquiries or feedback, please reach out to us at [zinedkaloc](https://twitter.com/zinedkaloc). ================================================ FILE: app/(aipage)/layout.tsx ================================================ import "@smastrom/react-rating/style.css"; import { ReactNode } from "react"; import Header from "@/components/Header"; export default async function AipageLayout({ children, }: { children: ReactNode; }) { return ( <>
{children} ); } ================================================ FILE: app/(aipage)/loading.tsx ================================================ import LoadingSpinner from "@/components/loadingSpinner"; export default function RootLoading() { return (
); } ================================================ FILE: app/(aipage)/not-found.tsx ================================================ import Link from "next/link"; import Button from "@/components/Button"; export default async function NotFound() { return (

404 NOT FOUND

We can’t find that page

Sorry, the page you are looking for doesn't exist or has been moved.

); } ================================================ FILE: app/(aipage)/page.tsx ================================================ "use client"; import { useChat } from "ai/react"; import { useEffect, useRef, useState } from "react"; import Frame from "react-frame-component"; import Image from "next/image"; import html2canvas from "html2canvas"; import TweetButton from "@/components/tweetButton"; import { useAuth } from "@/context/AuthContext"; import useSearchParams from "@/hooks/useSearchParams"; import RateModal from "@/components/RateModal"; import { cn, updateProject } from "@/utils/helpers"; import Link from "next/link"; enum DeviceSize { Mobile = "w-1/2", Tablet = "w-3/4", Desktop = "w-full", } export default function Chat() { const { user, setUser } = useAuth(); const [lastMessageId, setLastMessageId] = useState(null); const [hasNoCreditsError, setHasNoCreditsError] = useState(false); const { set } = useSearchParams(); const { messages, input, handleInputChange, handleSubmit, isLoading, stop } = useChat({ onResponse: (message) => { setHasNoCreditsError(false); setLastMessageId(null); decreaseCredit(); }, onFinish: async (message) => { try { const res = JSON.parse(message.content) as { credits: number }; setCredits(res.credits); setHasNoCreditsError(res.credits === 0); } catch { await saveResult(message.content); } }, }); function decreaseCredit(by: number = 1) { if (user) { setUser({ ...user, credits: user.credits - by }); } } function setCredits(credits: number) { if (user) { setUser({ ...user, credits }); } } async function saveResult(result: string) { const { _id } = await updateProject({ result, }); setLastMessageId(_id); set("rateModal", "true"); } const [iframeContent, setIframeContent] = useState(""); const [imageSrc, setImageSrc] = useState(""); const [deviceSize, setDeviceSize] = useState(DeviceSize.Desktop); const iframeRef = useRef(null); const [fileName, setFileName] = useState(""); const [selectedElement, setSelectedElement] = useState(null); const [editedContent, setEditedContent] = useState(""); const [editingMode, setEditingMode] = useState(false); const [codeViewActive, setCodeViewActive] = useState(false); const [isStopped, setIsStopped] = useState(false); const appendToIframe = (content: any) => { if (iframeRef.current) { const iframeDocument = (iframeRef.current as HTMLIFrameElement) .contentDocument; if (iframeDocument) { const newNode = iframeDocument.createElement("div"); newNode.innerHTML = content; newNode.querySelectorAll("*").forEach((element) => { element.addEventListener("mouseover", () => { element.classList.add("outline-blue"); // Blue border }); element.addEventListener("mouseout", () => { element.style.outline = "none"; }); element.addEventListener("click", () => { setSelectedElement(element); setEditedContent(element.innerHTML); }); }); requestAnimationFrame(() => { iframeDocument.body.appendChild(newNode); }); } } }; const captureIframeContent = async () => { if (iframeRef.current) { const iframeDocument = (iframeRef.current as HTMLIFrameElement) .contentDocument; if (iframeDocument) { const canvas = await html2canvas(iframeDocument.body); const imgURL = canvas.toDataURL(); // You can use imgURL as the src for an image tag to display the image representation of the iframe content // For simplicity, let's just set it to a state variable setImageSrc(imgURL); } } }; useEffect(() => { const stream = new EventSource("/api/chat"); stream.onmessage = (event) => { appendToIframe(event.data); }; return () => stream.close(); }, []); useEffect(() => { const lastMessage = messages[messages.length - 1]; if (lastMessage && lastMessage.role !== "user") { setIframeContent(lastMessage.content); } }, [messages]); const handleSave = () => { const element = document.createElement("a"); const file = new Blob([iframeContent], { type: "text/html" }); element.href = URL.createObjectURL(file); element.download = fileName || "index.html"; document.body.appendChild(element); element.click(); document.body.removeChild(element); const completionInput = iframeContent; }; const listenersMap = useRef< Map void; mouseout: () => void }> >(new Map()); // Create a map to store the listeners for each element const handleEdit = () => { if (editingMode) { // Save the updated iframe content if (iframeRef.current) { const iframeDocument = (iframeRef.current as HTMLIFrameElement) .contentDocument; if (iframeDocument) { setIframeContent(iframeDocument.documentElement.innerHTML); } } // Disable editing mode by setting the contentEditable property of all elements to false and remove the event listeners if (iframeRef.current) { const iframeDocument = (iframeRef.current as HTMLIFrameElement) .contentDocument; if (iframeDocument) { iframeDocument .querySelectorAll("*") .forEach((element) => { element.contentEditable = "false"; // Get the listeners for the element from the map const listeners = listenersMap.current.get(element); if (listeners) { // Remove the listeners element.removeEventListener("mouseover", listeners.mouseover); element.removeEventListener("mouseout", listeners.mouseout); // Remove the element from the map listenersMap.current.delete(element); } }); } } } else { // Enable editing mode by setting the contentEditable property of all elements to true and add event listeners if (iframeRef.current) { const iframeDocument = (iframeRef.current as HTMLIFrameElement) .contentDocument; if (iframeDocument) { iframeDocument .querySelectorAll("*") .forEach((element) => { element.contentEditable = "true"; // Create the event listeners const mouseoverListener = () => { element.classList.add("outline-blue"); console.log("Mouseover event fired"); }; const mouseoutListener = () => { console.log("Mouseout event fired"); element.classList.remove("outline-blue"); }; // Add the listeners to the element element.addEventListener("mouseover", mouseoverListener); element.addEventListener("mouseout", mouseoutListener); // Store the listeners in the map listenersMap.current.set(element, { mouseover: mouseoverListener, mouseout: mouseoutListener, }); }); } } } setEditingMode(!editingMode); }; const handleUpdate = () => { if (selectedElement) { selectedElement.innerHTML = editedContent; setSelectedElement(null); setEditedContent(""); if (iframeRef.current) { const iframeDocument = (iframeRef.current as HTMLIFrameElement) .contentDocument; if (iframeDocument) { setIframeContent(iframeDocument.documentElement.innerHTML); } } } }; function onFocusHandler() { if (!user) { set("authModal", "true"); } } const handleStop = async () => { stop(); setIsStopped(true); await saveResult(iframeContent); }; return ( <>
Help spread the word! 📢 Post a tweet of your creation on Twitter and tag @aipagedev for early access to our exclusive beta—packed with stunning features. 🚀
{/* Display the image if imageSrc is set */}
Star us on Github to show your support
⭐️
{isLoading ? null : (
AIPage.dev logo

🚧 Under Renovation 🚧

A refreshed experience is on the horizon.
Follow us on X {" "} to stay updated!

)} {editingMode && selectedElement && (

Edit the selected element:

setEditedContent(e.target.value)} />
)} {hasNoCreditsError ? (
!

No Credits

You do not have enough credits to proceed with this request today. Please try again tomorrow.

) : ( iframeContent && (
acme.co {isLoading && ( 🟠 )}
{/* Clear and Stop buttons */}
{!isLoading && iframeContent && ( )}
{codeViewActive ? (
{iframeContent}
) : (
)}
) )}
); } ================================================ FILE: app/(aipage)/profile/invoices/loading.tsx ================================================ import LoadingSpinner from "@/components/loadingSpinner"; export default function InvoicesLoading() { return (
); } ================================================ FILE: app/(aipage)/profile/invoices/page.tsx ================================================ import { fetchInvoices } from "@/utils/auth"; import { moneyFormat, stripePrice } from "@/utils/helpers"; import Button from "@/components/Button"; import { FileText } from "lucide-react"; import { Invoice } from "@/types"; export default async function InvoicesPage() { const invoices = await fetchInvoices(); const hasInvoices = invoices?.length > 0; return (
{hasInvoices && (

Invoices

Check the invoices for your purchases.

)} {!hasInvoices ? (

You don't have any invoices yet!

No domains yet
) : ( <>
)}
); } function DesktopTable({ invoices }: { invoices: Invoice[] }) { return (
{invoices.map((invoice, index) => ( ))}
Product name Order date Status Total amount
{invoice.lines.data[0]?.price?.nickname ?? "Unknown"}{" "} Credits {new Date(invoice.created * 1000).toLocaleDateString()} {invoice.status.toUpperCase()} {moneyFormat(stripePrice(invoice.total))}
); } function MobileTable({ invoices }: { invoices: Invoice[] }) { return (
{invoices?.map((invoice) => (
Product Name
{invoice.lines.data[0]?.price?.nickname ?? "Unknown"} Credits
Order date
Order status
{invoice.status}
Total amount
{moneyFormat(stripePrice(invoice.total))}
))}
); } ================================================ FILE: app/(aipage)/profile/layout.tsx ================================================ import { ReactNode } from "react"; import ProfileLayout from "@/components/ProfileLayout"; export default async function ProfileBaseLayout({ children, }: { children: ReactNode; }) { return {children}; } ================================================ FILE: app/(aipage)/profile/projects/[id]/domains/layout.tsx ================================================ "use client"; import AddDomainModal from "@/components/AddDomainModal"; import Button from "@/components/Button"; import DomainCard from "@/components/DomainCard"; import useSearchParams from "@/hooks/useSearchParams"; import { ReactNode } from "react"; export default function DomainLayout({ children }: { children: ReactNode }) { const { set } = useSearchParams(); return ( <>

Domains

{children}
); } ================================================ FILE: app/(aipage)/profile/projects/[id]/domains/page.tsx ================================================ import DomainCard from "@/components/DomainCard"; import { toReversed } from "@/utils/helpers"; import { fetchProjectById } from "@/utils/auth"; import { SetProject } from "@/hooks/useProject"; export default async function ProjectDomains({ params, }: { params: { id: string }; }) { const project = await fetchProjectById(params.id); const hasDomains = !!(project && project?.domains.length > 0); return (
{hasDomains ? ( toReversed(project?.domains).map((domain) => ( )) ) : (

No domains yet

No links yet
)}
); } ================================================ FILE: app/(aipage)/profile/projects/[id]/integrations/page.tsx ================================================ export default function ProjectIntegrations({ params, }: { params: { id: string; }; }) { return
; } ================================================ FILE: app/(aipage)/profile/projects/[id]/layout.tsx ================================================ import { ReactNode } from "react"; import { fetchProjectById } from "@/utils/auth"; import { notFound } from "next/navigation"; import { SetProject } from "@/hooks/useProject"; export default async function ProjectLayout({ children, params, }: { children: ReactNode; params: { id: string; }; }) { const project = await fetchProjectById(params.id); if (!project || project?.deletedAt) return notFound(); return ( <> {children} ); } ================================================ FILE: app/(aipage)/profile/projects/[id]/loading.tsx ================================================ import LoadingSpinner from "@/components/loadingSpinner"; export default function ProjectByIdLoading() { return (
); } ================================================ FILE: app/(aipage)/profile/projects/[id]/page.tsx ================================================ import { fetchProjectById, fetchProjects } from "@/utils/auth"; import { SetProjects } from "@/hooks/useProjectList"; import { SetProject } from "@/hooks/useProject"; import { FormEvent } from "react"; import { Sheet, SheetClose, SheetContent, SheetFooter, SheetHeader, SheetTitle, } from "@/components/Drawer"; import Button from "@/components/Button"; import ProjectDesign from "@/components/ProjectDesign"; export const revalidate = 0; export default async function ProjectDetail({ params, }: { params: { id: string }; }) { const projects = await fetchProjects(); const project = await fetchProjectById(params.id); return (
{project?.result ? ( ) : (
No HTML to display
)}
); } interface PanelProps { open: boolean; onOpenChange?: (open: boolean) => void; selected?: HTMLElement | null; onSave: (values: [string, string][]) => void; } function Panel(props: PanelProps) { const { open, onOpenChange, selected, onSave } = props; const includedKeys = [ "padding", "color", "width", "height", "margin", "fontSize", "display", "position", "top", "left", "right", "bottom", "border", "background", "transform", ]; function onSubmit(e: FormEvent) { e.preventDefault(); if (!selected) return; const formData = new FormData(e.target as HTMLFormElement); onSave?.(Array.from(formData.entries()) as [string, string][]); } return (
Edit selected section
{selected && Object.entries(getComputedStyle(selected)) .filter( ([key]) => !key.match(/\d/) && includedKeys.includes(key) && !key.startsWith("webkit"), ) .map(([key, value]) => (
))}
); } ================================================ FILE: app/(aipage)/profile/projects/[id]/settings/page.tsx ================================================ "use client"; import useProject from "@/hooks/useProject"; import { FormEvent, useEffect, useState } from "react"; import { cn, updateProject } from "@/utils/helpers"; import NavLink from "@/components/NavLink"; import Button from "@/components/Button"; import DeleteProjectConfirmDialog from "@/components/DeleteProjectConfirmDialog"; import LoadingSpinner from "@/components/loadingSpinner"; import { useRouter } from "next/navigation"; export default function ProjectSettingsPage() { const { setProject, project } = useProject(); const [name, setName] = useState(project?.name); const [loading, setLoading] = useState(false); const { refresh } = useRouter(); async function onNameFormSubmit(event: FormEvent) { event.preventDefault(); if (!name || name === project?.name) return; setLoading(true); const { message: projectFromAPI } = await updateProject( { name, }, project?._id as string, ); setLoading(false); setProject(projectFromAPI); refresh(); } return (
General

Project Name

This will be your project name on AIPage.dev

setName(e.target.value)} />

Max 32 characters.

Delete Project

Permanently delete your AIPage.dev project

); } ================================================ FILE: app/(aipage)/profile/projects/loading.tsx ================================================ import LoadingSpinner from "@/components/loadingSpinner"; export default function ProjectsLoading() { return (
); } ================================================ FILE: app/(aipage)/profile/projects/page.tsx ================================================ import { cn } from "@/utils/helpers"; import Link from "next/link"; import Button from "@/components/Button"; import ListProjects from "@/components/ListProjects"; import { fetchProjects } from "@/utils/auth"; export const revalidate = 0; export default async function ProfileProjects() { const projects = await fetchProjects(); const hasProjects = !!projects && projects.length > 0; return (
{!hasProjects ? (

You don't have any projects yet!

No links yet
) : ( )}
); } ================================================ FILE: app/(aipage)/profile/settings/loading.tsx ================================================ import LoadingSpinner from "@/components/loadingSpinner"; export default function SettingsLoading() { return (
); } ================================================ FILE: app/(aipage)/profile/settings/page.tsx ================================================ "use client"; import { useAuth } from "@/context/AuthContext"; import { useState, FormEvent } from "react"; import LoadingSpinner from "@/components/loadingSpinner"; import Button from "@/components/Button"; import { cn } from "@/utils/helpers"; import DeleteAccountConfirmDialog from "@/components/DeleteAccountConfirmDialog"; import NavLink from "@/components/NavLink"; export default function ProfileSettings() { const { user, setUser } = useAuth(); const [name, setName] = useState(user?.name); const [loading, setLoading] = useState(false); async function onNameFormSubmit(event: FormEvent) { event.preventDefault(); if (!name || name === user?.name) return; setLoading(true); const res = await fetch("/api/user", { method: "PATCH", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ name }), }); const { user: userFromAPI, errors } = await res.json(); setLoading(false); if (!errors) { setUser(userFromAPI); } } return (
General

Your Name

This will be your display name on AIPage.dev

setName(e.target.value)} />

Max 32 characters.

Delete Account

Permanently delete your AIPage.dev account and all of your data. This action cannot be undone - please proceed with caution.

); } ================================================ FILE: app/(preview)/not-found.tsx ================================================ import Link from "next/link"; import Button from "@/components/Button"; export default async function NotFound() { return (

404 NOT FOUND

We can’t find that page

Sorry, the page you are looking for doesn't exist or has been moved.

); } ================================================ FILE: app/(preview)/profile/projects/[id]/preview/loading.tsx ================================================ import LoadingSpinner from "@/components/loadingSpinner"; export default function PreviewLoading() { return (
); } ================================================ FILE: app/(preview)/profile/projects/[id]/preview/page.tsx ================================================ import { fetchProjectById } from "@/utils/auth"; import { notFound } from "next/navigation"; import HTMLPreview from "@/components/HTMLPreview"; export default async function projectPreview({ params, }: { params: { id: string }; }) { const project = await fetchProjectById(params.id); if (!project || !project.result) return notFound(); return ; } ================================================ FILE: app/api/chat/route.ts ================================================ import { Configuration, OpenAIApi } from "openai-edge"; import { Ratelimit } from "@upstash/ratelimit"; import redis from "../../../utils/redis"; import { OpenAIStream, StreamingTextResponse } from "ai"; import { headers, cookies } from "next/headers"; import { NextResponse } from "next/server"; /* // REMOVE THIS IF YOU DON'T WANT RATE LIMITING // START const ratelimit = redis ? new Ratelimit({ redis: redis, limiter: Ratelimit.fixedWindow(5, "1440 m"), analytics: true, }) : undefined; // END */ const config = new Configuration({ apiKey: process.env.OPENAI_API_KEY, }); const openai = new OpenAIApi(config); export const runtime = "edge"; export async function POST(req: Request) { const cookieStore = cookies(); /* // REMOVE THIS IF YOU DON'T WANT RATE LIMITING // START if (ratelimit) { const headersList = headers(); const ipIdentifier = headersList.get("x-real-ip"); const result = await ratelimit.limit(ipIdentifier ?? ""); if (!result.success) { const fakeStream = "Too many requests in 1 day. Please try again in a 24 hours. Thank you. 🙏"; return new Response(fakeStream, { status: 429, headers: { "X-RateLimit-Limit": result.limit, "X-RateLimit-Remaining": result.remaining, } as any, }); } } // END */ const { messages } = await req.json(); // Implemented for to test the API const sessionToken = cookieStore.get("sessionToken")?.value as string; const storeMessage = await fetch( "https://c3-na.altogic.com/e:64d52ccfc66bd54b97bdd78a/test", { method: "POST", headers: { "Content-Type": "application/json", Session: sessionToken, }, body: JSON.stringify({ content: messages[0].content }), }, ); const { credits } = await storeMessage.json(); if (credits === 0) { return NextResponse.json({ code: "no-credits", credits }); } 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: 1. A header Section: Include a logo and a navigation menu. 2. 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). 3. 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. 4. 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. 5. 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. 6. A blog Section: Include a section that displays recent blog posts with a title, short description, and a "Read More" link. 7. An FAQ Section: Add a section for frequently asked questions and answers. 8. A Team Section: Showcase the team with photos, names, roles, and social media links. 9. A Newsletter Subscription: Add a section for users to subscribe to a newsletter. 10. A Contact Form: Create fields for name, email, and message. Apply appropriate CSS animations or transitions using jQuery for smooth interactivity. 11. 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). 12. A footer Section: Add links to social media profiles, utilizing the Fontawesome CDN icon library for social media icons. Please 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. Remember 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. 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. Start with and end with . The code should be formatted for readability.`; const combinedMessages = [ ...messages, { role: "system", content: systemPrompt }, ]; let response; let stream; response = await openai.createChatCompletion({ model: "gpt-3.5-turbo-16k", messages: combinedMessages.map((message: any) => ({ role: message.role, content: message.content, })), stream: true, }); stream = OpenAIStream(response); // Continue generating the response if incomplete // If rate limited, return a fake response return new StreamingTextResponse(stream); } ================================================ FILE: app/api/create-checkout-session/route.ts ================================================ import { NextResponse } from "next/server"; import { cookies } from "next/headers"; import altogic from "@/utils/altogic"; export async function POST(req: Request) { const { priceId } = await req.json(); const cookieStore = cookies(); const session = cookieStore.get("sessionToken"); if (!session) { return NextResponse.json( { error: "You must be logged in to purchase this product" }, { status: 401 }, ); } // @ts-ignore altogic.auth.setSession({ token: session.value, }); const path = process.env.NEXT_PUBLIC_CREATE_SESSION_PATH as string; const { errors, data } = await altogic.endpoint.post(path, { priceId, }); if (errors) { return NextResponse.json({ errors }, { status: errors.status }); } return NextResponse.json({ url: data.url }); } ================================================ FILE: app/api/domain/check-domain/route.ts ================================================ import { NextResponse } from "next/server"; export async function POST(req: Request) { const { domain } = await req.json(); const [configResponse, domainResponse] = await Promise.all([ fetch(`https://api.vercel.com/v6/domains/${domain}/config`, { method: "GET", headers: { Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`, "Content-Type": "application/json", }, }), fetch( `https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}/domains/${domain}`, { method: "GET", headers: { Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`, "Content-Type": "application/json", }, }, ), ]); const configJson = await configResponse.json(); const domainJson = await domainResponse.json(); if (domainResponse.status !== 200) { return NextResponse.json(domainJson, { status: domainResponse.status }); } /** * If domain is not verified, we try to verify now */ let verificationResponse = null; if (!domainJson.verified) { const verificationRes = await fetch( `https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}/domains/${domain}/verify`, { method: "POST", headers: { Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`, "Content-Type": "application/json", }, }, ); verificationResponse = await verificationRes.json(); } if (verificationResponse && verificationResponse.verified) { /** * Domain was just verified */ return NextResponse.json( { configured: !configJson.misconfigured, ...verificationResponse, }, { status: 200 }, ); } return NextResponse.json( { configured: !configJson.misconfigured, ...domainJson, ...(verificationResponse ? { verificationResponse } : {}), }, { status: 200 }, ); } ================================================ FILE: app/api/domain/remove-domain/route.ts ================================================ import { NextResponse } from "next/server"; import altogic from "@/utils/altogic"; import { getSessionCookie } from "@/utils/auth"; export async function POST(req: Request) { const { domain } = await req.json(); console.log({ domain }); const response = await fetch( `https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}/domains/${domain}`, { headers: { Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`, }, method: "DELETE", }, ); const json = await response.json(); console.log(JSON.stringify(json, null, 2)); console.log("-----"); // @ts-ignore altogic.auth.setSession({ token: getSessionCookie() as string, }); const { errors } = await altogic.db .model("messages.domains") .filter(`domain == '${domain}'`) .delete(); console.log(JSON.stringify(errors, null, 2)); if (errors) { return NextResponse.json(errors, { status: errors.status }); } NextResponse.json(json); } ================================================ FILE: app/api/domain/verify-domain/route.ts ================================================ import { NextResponse } from "next/server"; export async function POST(req: Request) { const { domain } = await req.json(); const response = await fetch( `https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}/domains/${domain}/verify`, { headers: { Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`, "Content-Type": "application/json", }, method: "POST", }, ); const data = await response.json(); return NextResponse.json(data, { status: response.status, }); } ================================================ FILE: app/api/logout/route.ts ================================================ import { NextResponse } from "next/server"; import { logout } from "@/utils/auth"; export async function GET(req: Request) { return logout(req, NextResponse); } ================================================ FILE: app/api/message/route.ts ================================================ import altogic from "@/utils/altogic"; import { NextResponse } from "next/server"; import { cookies } from "next/headers"; import { revalidatePath } from "next/cache"; export async function PUT(req: Request) { const cookieStore = cookies(); const token = cookieStore.get("sessionToken"); if (!token) { return NextResponse.json( { message: "You must be logged in to update your project.", }, { status: 401 }, ); } const body = await req.json(); // @ts-ignore altogic.auth.setSession({ token: token.value, }); const { data, errors } = await altogic.endpoint.put("/message-content", body); if (errors) return NextResponse.json({ errors }, { status: 500 }); revalidatePath("/profile/projects"); return NextResponse.json({ message: data }, { status: 200 }); } ================================================ FILE: app/api/og/route.tsx ================================================ import { ImageResponse } from "next/server"; import { useState } from "react"; // App router includes @vercel/og. // No need to install it. export const runtime = "edge"; export async function GET(request: Request) { const tweetIntents = [ "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", "Creating a stunning webpage has never been easier thanks to AIpage.dev! 🚀 Give it a try 👉 @aipagedev", "Web design will never be the same after you try AIpage.dev! 🛠️ A whole new level of creativity unleashed! Check it out 👉 @aipagedev", "Revolutionize your web design process with AIpage.dev. The future is here! 👉 @aipagedev", "I just built an amazing webpage with AIpage.dev in minutes! 🌟 You have to try this 👉 @aipagedev", "AIpage.dev is a game-changer for web design! Say hello to efficiency 👋 @aipagedev", "Why spend hours on web design when AIpage.dev can do it in minutes? 🕒 Check it out! 👉 @aipagedev", "Impressed by the power of AI in web design with AIpage.dev! This is incredible 👀 @aipagedev", "I used AIpage.dev and it completely transformed how I approach web design. You need to try this! 🎉 @aipagedev", "Just when I thought web design couldn’t get any easier, I found AIpage.dev! 🎊 Try it now 👉 @aipagedev", "Unleashing my inner designer with the help of AIpage.dev. This is next level! 🚀 Check it out 👉 @aipagedev", "With AIpage.dev, I can focus on creativity while AI handles the coding. It’s amazing! 💥 @aipagedev", ]; // Function to generate a random index for selecting a tweet text const getRandomIndex = () => { return Math.floor(Math.random() * tweetIntents.length); }; // Function to generate a random tweet text const getRandomTweet = () => { return tweetIntents[getRandomIndex()]; }; const text = getRandomTweet(); return new ImageResponse( (
{text}
), { width: 1200, height: 630, } ); } ================================================ FILE: app/api/project/[id]/domain/route.ts ================================================ import { NextResponse } from "next/server"; import altogic from "@/utils/altogic"; import { getSessionCookie } from "@/utils/auth"; export async function POST( req: Request, { params }: { params: { id: string } }, ) { const { domain, isPrimary } = await req.json(); // @ts-ignore altogic.auth.setSession({ token: getSessionCookie() as string, }); try { const { data, errors } = await altogic.endpoint.post( "/add-domain", { domain, isPrimary, projectId: params.id, }, undefined, { Session: getSessionCookie(), }, ); if (errors) { return NextResponse.json({ errors }, { status: errors.status }); } const response = await fetch( `https://api.vercel.com/v9/projects/${process.env.PROJECT_ID_VERCEL}/domains`, { body: JSON.stringify({ name: domain }), headers: { Authorization: `Bearer ${process.env.AUTH_BEARER_TOKEN_VERCEL}`, "Content-Type": "application/json", }, method: "POST", }, ); const domainData = await response.json(); if (domainData.error?.code == "forbidden") { return NextResponse.json( { error: domainData.error, }, { status: 403 }, ); } else if (domainData.error?.code == "domain_taken") { return NextResponse.json( { error: domainData.error, }, { status: 409 }, ); } return NextResponse.json({ data }, { status: 200 }); } catch (error) { return NextResponse.json({}, { status: 500 }); } } ================================================ FILE: app/api/project/[id]/route.ts ================================================ import { deleteProject } from "@/utils/auth"; import { NextResponse } from "next/server"; import { revalidatePath } from "next/cache"; export async function DELETE( req: Request, { params }: { params: { id: string } }, ) { try { const { errors } = await deleteProject(params.id); if (errors) { return NextResponse.json({ errors }, { status: 500 }); } revalidatePath("/profile/projects"); return NextResponse.json({ status: "ok" }, { status: 200 }); } catch (error) { return NextResponse.json({}, { status: 500 }); } } ================================================ FILE: app/api/project/route.ts ================================================ import { deleteProject, updateProjectName } from "@/utils/auth"; import { NextResponse } from "next/server"; export async function PATCH(req: Request) { const { name, id } = await req.json(); try { const { project, errors } = await updateProjectName(id, name); if (errors) { return NextResponse.json({ errors }, { status: 500 }); } return NextResponse.json({ project }, { status: 200 }); } catch (error) { return NextResponse.json({}, { status: 500 }); } } ================================================ FILE: app/api/user/route.ts ================================================ import { deleteUser, logout, updateUser } from "@/utils/auth"; import { NextResponse } from "next/server"; export async function PATCH(req: Request) { const { data, errors } = await updateUser(await req.json()); if (errors) { return NextResponse.json({ errors }, { status: 500 }); } return NextResponse.json({ user: data }, { status: 200 }); } export async function DELETE(req: Request) { const { errors } = await deleteUser(); if (errors) { return NextResponse.json({ errors }, { status: 500 }); } return logout(req, NextResponse); } ================================================ FILE: app/auth-redirect/route.ts ================================================ import altogic from "@/utils/altogic"; import { NextResponse } from "next/server"; export async function GET(req: Request) { const url = new URL(req.url); const accessToken = url.searchParams.get("access_token") as string; const status = url.searchParams.get("status"); const isOk = status === "200"; const destinationUrl = new URL("/", new URL(req.url).origin); const response = NextResponse.redirect(destinationUrl, { status: 302 }); if (!isOk) return response; const { session, errors } = await altogic.auth.getAuthGrant(accessToken); if (errors) { // TODO: handle errors; } if (session) { response.cookies.set("sessionToken", session.token, { path: "/", httpOnly: true, maxAge: 60 * 60 * 24 * 7, // 1 week }); } return response; } ================================================ FILE: app/layout.tsx ================================================ import AuthModal from "@/components/AuthModal"; import PricesModal from "@/components/PricesModal"; import { AuthProvider } from "@/context/AuthContext"; import { Project } from "@/types"; import { fetchAuthUser, fetchProducts, getProjectByDomain } from "@/utils/auth"; import { isAipage } from "@/utils/helpers"; import { Metadata } from "next"; import { Inter } from "next/font/google"; import { headers } from "next/headers"; import { ReactNode } from "react"; import "../styles/globals.css"; const inter = Inter({ subsets: ["latin"] }); export async function generateMetadata(): Promise { const isAipageDomain = isAipage(headers().get("Host") as string); if (!isAipageDomain) { const project = await getProjectByDomain(headers().get("Host") as string); if (project) return {}; } return { title: "AIPage.dev - An AI-Powered Landing Page Generator | by @zinedkaloc", description: "AI-Powered Landing Page Generator. Experience the Open Source Project that Empowers You to Build Stunning Landing Pages Instantly", openGraph: { title: "AIPage.dev - An AI-Powered Landing Page Generator | by @zinedkaloc", description: "AI-Powered Landing Page Generator. Experience the Open Source Project that Empowers You to Build Stunning Landing Pages Instantly", type: "website", url: "https://aipage.dev", images: `${process.env.NEXT_PUBLIC_DOMAIN}/api/og?text=${new Date() .getTime() .toString()}`, }, }; } export default async function RootLayout({ children, }: { children: ReactNode; }) { const isAipageDomain = isAipage(headers().get("Host") as string); const user = await fetchAuthUser(); const products = await fetchProducts(); let project: Project | null = null; if (!isAipageDomain) { project = await getProjectByDomain(headers().get("Host") as string); } if (!isAipageDomain && project?.result) { return require("html-react-parser")(project?.result); } return ( {children} ); } ================================================ FILE: app/not-found.tsx ================================================ import Link from "next/link"; import Button from "@/components/Button"; export default async function NotFound() { return (

404 NOT FOUND

We can’t find that page

Sorry, the page you are looking for doesn't exist or has been moved.

); } ================================================ FILE: components/AddDomainModal.tsx ================================================ "use client"; import useSearchParams from "@/hooks/useSearchParams"; import Modal from "@/components/Modal"; import { FormEvent, useState } from "react"; import Button from "@/components/Button"; import LoadingSpinner from "@/components/loadingSpinner"; import Image from "next/image"; import Switch from "@/components/Switch"; import { useParams, useRouter } from "next/navigation"; import useProject from "@/hooks/useProject"; import { APIError } from "altogic"; export default function AddDomainModal() { const { deleteByKey, has } = useSearchParams(); const [loading, setLoading] = useState(false); const [hasError, setHasError] = useState(false); const [isPrimary, setIsPrimary] = useState(false); const [domain, setDomain] = useState(""); const [error, setError] = useState(null); const { id } = useParams(); const { addDomain } = useProject(); const { refresh } = useRouter(); function close() { setIsPrimary(false); setDomain(""); deleteByKey("domainModal"); setError(null); setHasError(false); } async function onSubmit(e: FormEvent) { e.preventDefault(); const data = { domain, isPrimary, }; setLoading(true); setHasError(false); try { const res = await fetch(`/api/project/${id}/domain`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(data), }); const { errors, data: domainFromAPI } = await res.json(); if (errors) { setHasError(true); setError(errors); } else { close(); addDomain(domainFromAPI); refresh(); } } catch { setHasError(true); } finally { setLoading(false); } } return (
AIPage.dev logo

Add Domain

setDomain(e.target.value)} autoComplete="off" pattern="[a-z0-9]+\.[a-zA-Z]{2,}" 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" placeholder="aipage.dev" required />
Please enter a valid domain name (e.g. aipage.dev)
{/*

Primary Domain

*/} {hasError && (
{error && error.items.length > 0 ? ( error?.items.map((item) => (

{item.message}

)) ) : (

There was an error adding your domain. Please try again later.

)}
)}
); } ================================================ FILE: components/AlertCircleFill.tsx ================================================ export default function AlertCircleFill({ className }: { className: string }) { return ( ); } ================================================ FILE: components/AlertDialog.tsx ================================================ "use client"; import * as React from "react"; import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; import { cn } from "@/utils/helpers"; const AlertDialog = AlertDialogPrimitive.Root; const AlertDialogTrigger = AlertDialogPrimitive.Trigger; const AlertDialogPortal = ({ className, ...props }: AlertDialogPrimitive.AlertDialogPortalProps) => ( ); AlertDialogPortal.displayName = AlertDialogPrimitive.Portal.displayName; const AlertDialogOverlay = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( )); AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; const AlertDialogContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes) => (
); AlertDialogHeader.displayName = "AlertDialogHeader"; const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes) => (
); AlertDialogFooter.displayName = "AlertDialogFooter"; const AlertDialogTitle = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; const AlertDialogDescription = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName; const AlertDialogAction = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; const AlertDialogCancel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; export { AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, AlertDialogAction, AlertDialogCancel, }; ================================================ FILE: components/AuthModal.tsx ================================================ "use client"; import { useState } from "react"; import Link from "next/link"; import altogic from "@/utils/altogic"; import useSearchParams from "@/hooks/useSearchParams"; import Image from "next/image"; import Modal from "@/components/Modal"; import LoadingSpinner from "@/components/loadingSpinner"; import Button, { ButtonVariant } from "@/components/Button"; export default function AuthModal() { const { deleteByKey, has } = useSearchParams(); const [selected, setSelected] = useState(); const loginMethods = [ { name: "Google", variant: "default", icon: GoogleIcon, handler: () => altogic.auth.signInWithProvider("google"), }, { name: "Github", variant: "light", icon: GithubIcon, handler: () => altogic.auth.signInWithProvider("github"), }, ]; function close() { deleteByKey("authModal"); } return (
AIPage.dev logo

Sign in to AIPage

Powered by AI, Perfected for You

{loginMethods.map((method, index) => ( ))}

Enterprise login?{" "} Contact

); } const GoogleIcon = () => ( ); const GithubIcon = () => ( ); ================================================ FILE: components/Badge.tsx ================================================ import { cn } from "@/utils/helpers"; export type BadgeVariant = "yellow" | "gray" | "red" | "black" | "green"; export default function Badge({ text, variant, className, }: { text: string; variant?: BadgeVariant; className?: string; }) { return ( {text} ); } ================================================ FILE: components/BrowserWindow.tsx ================================================ import { ReactNode } from "react"; import { cn } from "@/utils/helpers"; interface BrowserWindowProps { children: ReactNode; className?: string; } export default function BrowserWindow({ children, className, }: BrowserWindowProps) { return (
{children}
); } ================================================ FILE: components/Button.tsx ================================================ import { cn } from "@/utils/helpers"; import { ComponentPropsWithoutRef } from "react"; import * as React from "react"; export type ButtonVariant = "default" | "light" | "pill" | "danger"; interface ButtonProps extends ComponentPropsWithoutRef<"button"> { variant?: ButtonVariant; } const Button = React.forwardRef( ({ className, children, variant, ...props }, ref) => ( ), ); export default Button; ================================================ FILE: components/Chart.tsx ================================================ export default function Chart({ className }: { className: string }) { return ( ); } ================================================ FILE: components/CheckCircleFill.tsx ================================================ export default function CheckCircleFill({ className }: { className?: string }) { return ( ); } ================================================ FILE: components/ConfiguredSection.tsx ================================================ import { useState } from "react"; import { DomainInfo } from "@/types"; interface ConfiguredSectionProps { domainInfo?: DomainInfo | null; } const ConfiguredSection = ({ domainInfo }: ConfiguredSectionProps) => { const [recordType, setRecordType] = useState("A"); if (!domainInfo?.verified) { const txtVerification = domainInfo?.verification?.find( (x: any) => x.type === "TXT", ); return ( <>

Domain is pending verification

setRecordType("CNAME")} className={`${ recordType == "CNAME" ? "text-black border-black" : "text-gray-400 border-white" } text-sm border-b-2 pb-1 transition-all ease duration-150`} > Verify Domain Ownership

Please set the following TXT record on {domainInfo?.apexName} to prove ownership of {domainInfo?.name}:

Type

{txtVerification?.type}

Name

{txtVerification?.domain && domainInfo?.apexName && txtVerification?.domain?.slice( 0, txtVerification?.domain?.length - domainInfo?.apexName?.length - 1, )}

Value

{txtVerification?.value}

); } return ( <>
{domainInfo.configured ? ( <> ) : ( <> )}

{domainInfo.configured ? "Valid" : "Invalid"} Configuration

{!domainInfo.configured && ( <>

Set the following record on your DNS provider to continue:

Type

{recordType}

Name

{recordType == "CNAME" ? "www" : "@"}

Value

{recordType == "CNAME" ? `cname.aipage.dev` : `76.76.21.21`}

)} ); }; export default ConfiguredSection; ================================================ FILE: components/ConfirmDialog.tsx ================================================ "use client"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogTrigger, } from "@/components/AlertDialog"; import Button from "@/components/Button"; import { XIcon } from "lucide-react"; import { useAuth } from "@/context/AuthContext"; import { ReactNode, useState } from "react"; interface ConfirmDialogProps { text: string; trigger: ReactNode; onConfirm: () => void; children?: ReactNode; } export default function ConfirmDialog({ trigger, text, onConfirm, children, }: ConfirmDialogProps) { const [confirmText, setConfirmText] = useState(""); return ( {trigger} {children && (
{children}
)}
setConfirmText(e.target.value)} 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" />
); } ================================================ FILE: components/DeleteAccountConfirmDialog.tsx ================================================ "use client"; import Button from "@/components/Button"; import { useState } from "react"; import ConfirmDialog from "@/components/ConfirmDialog"; import LoadingSpinner from "@/components/loadingSpinner"; import { useAuth } from "@/context/AuthContext"; export default function DeleteAccountConfirmDialog() { const [deleting, setDeleting] = useState(false); const { user } = useAuth(); async function deleteAccountHandler() { setDeleting(true); const res = await fetch("/api/user", { method: "DELETE", }); if (res.ok && res.redirected) { window.location.href = res.url; } else { setDeleting(false); } } return ( {deleting && }

Delete Account

} onConfirm={deleteAccountHandler} > {user?.email

Delete Account

Warning: This will permanently delete your account and all your data.

); } ================================================ FILE: components/DeleteProjectConfirmDialog.tsx ================================================ "use client"; import Button from "@/components/Button"; import { useState } from "react"; import ConfirmDialog from "@/components/ConfirmDialog"; import LoadingSpinner from "@/components/loadingSpinner"; import { Project } from "@/types"; import { useRouter } from "next/navigation"; import useProjectList from "@/hooks/useProjectList"; export default function DeleteProjectConfirmDialog({ project, }: { project: Project | null; }) { const [deleting, setDeleting] = useState(false); const { push, prefetch, refresh } = useRouter(); const { deleteProject } = useProjectList(); async function deleteProjectHandler() { if (!project) return; setDeleting(true); const res = await fetch("/api/project/" + project._id, { method: "DELETE", }); const { errors } = await res.json(); if (!errors) { deleteProject(project._id); refresh(); push("/profile/projects"); } else setDeleting(false); } return ( {deleting && }

Delete project

} onConfirm={deleteProjectHandler} /> ); } ================================================ FILE: components/Divider.tsx ================================================ export default function Divider({ className }: { className?: string }) { return ( ); } ================================================ FILE: components/DomainCard.tsx ================================================ "use client"; import { useEffect, useState } from "react"; import LoadingSpinner from "@/components/loadingSpinner"; import ConfiguredSection from "@/components/ConfiguredSection"; import { Domain } from "@/types"; import Button from "@/components/Button"; import { useRouter } from "next/navigation"; import useProject from "@/hooks/useProject"; interface DomainCardProps { domain: Domain; } const DomainCard = ({ domain }: DomainCardProps) => { const removeDomainFromState = useProject((state) => state.removeDomain); const [domainInfo, setDomainInfo] = useState(null); const [isValidating, setIsValidating] = useState(false); const [removing, setRemoving] = useState(false); const { refresh } = useRouter(); useEffect(() => { checkDomain(); const interval = setInterval(checkDomain, 10000); return () => clearInterval(interval); }, []); async function checkDomain() { setIsValidating(true); const res = await fetch("/api/domain/check-domain", { method: "POST", body: JSON.stringify({ domain: domain.domain }), }); const data = await res.json(); setDomainInfo(data); setIsValidating(false); } async function removeDomain() { setRemoving(true); try { await fetch("/api/domain/remove-domain", { method: "POST", body: JSON.stringify({ domain: domain.domain }), }); refresh(); removeDomainFromState(domain._id); } catch { setRemoving(false); } } return (
{domain.domain}
); }; export default DomainCard; ================================================ FILE: components/DomainConfiguration.tsx ================================================ "use client"; import { useState } from "react"; import { getSubdomain } from "@/utils/helpers"; import { DomainVerificationStatusProps } from "@/types"; export const InlineSnippet = ({ children }: { children: string }) => { return ( {children} ); }; export default function DomainConfiguration({ data, }: { data: { status: DomainVerificationStatusProps; response: any }; }) { const { domainJson } = data.response; console.log(); const subdomain = getSubdomain(domainJson.name, domainJson.apexName); const [recordType, setRecordType] = useState(!!subdomain ? "CNAME" : "A"); if (data.status === "Pending Verification") { const txtVerification = domainJson.verification.find( (x: any) => x.type === "TXT", ); return (

Please set the following TXT record on{" "} {domainJson.apexName} to prove ownership of {domainJson.name}:

Type

{txtVerification.type}

Name

{txtVerification.domain.slice( 0, txtVerification.domain.length - domainJson.apexName.length - 1, )}

Value

{txtVerification.value}

Warning: if you are using this domain for another site, setting this TXT record will transfer domain ownership away from that site and break it. Please exercise caution when setting this record.

); } if (data.status === "Unknown Error") { return (

{data.response.domainJson.error.message}

); } return (

To configure your {recordType === "A" ? "apex domain" : "subdomain"} ( {recordType === "A" ? domainJson.apexName : domainJson.name} ), set the following {recordType} record on your DNS provider to continue:

Type

{recordType}

Name

{recordType === "A" ? "@" : subdomain ?? "www"}

Value

{recordType === "A" ? `76.76.21.21` : `cname.dub.co`}

TTL

86400

Note: for TTL, if 86400 is not available, set the highest value possible. Also, domain propagation can take anywhere between 1 hour to 12 hours.

); } ================================================ FILE: components/Drawer.tsx ================================================ "use client"; import * as React from "react"; import * as SheetPrimitive from "@radix-ui/react-dialog"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/utils/helpers"; import { XIcon } from "lucide-react"; const Sheet = SheetPrimitive.Root; const SheetTrigger = SheetPrimitive.Trigger; const SheetClose = SheetPrimitive.Close; const SheetPortal = ({ className, ...props }: SheetPrimitive.DialogPortalProps) => ( ); SheetPortal.displayName = SheetPrimitive.Portal.displayName; const SheetOverlay = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; const sheetVariants = cva( "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", { variants: { side: { top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", bottom: "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", 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", right: "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", }, }, defaultVariants: { side: "right", }, }, ); interface SheetContentProps extends React.ComponentPropsWithoutRef, VariantProps {} const SheetContent = React.forwardRef< React.ElementRef, SheetContentProps >(({ side = "right", className, children, ...props }, ref) => ( {children} Close )); SheetContent.displayName = SheetPrimitive.Content.displayName; const SheetHeader = ({ className, ...props }: React.HTMLAttributes) => (
); SheetHeader.displayName = "SheetHeader"; const SheetFooter = ({ className, ...props }: React.HTMLAttributes) => (
); SheetFooter.displayName = "SheetFooter"; const SheetTitle = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); SheetTitle.displayName = SheetPrimitive.Title.displayName; const SheetDescription = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); SheetDescription.displayName = SheetPrimitive.Description.displayName; export { Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription, }; ================================================ FILE: components/HTMLPreview.tsx ================================================ interface HTMLPreviewProps { html: string; } export default function HTMLPreview({ html }: HTMLPreviewProps) { return (