diff --git a/README.md b/README.md index f40e0e7..cdae681 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,6 @@ Example: feat: add new feature ``` - ## Pull Request - The `main` branch is the source of truth and should always reflect the latest stable release. diff --git a/apps/docs/app/docs/[[...slug]]/page.tsx b/apps/docs/app/docs/[[...slug]]/page.tsx index f235418..1527077 100644 --- a/apps/docs/app/docs/[[...slug]]/page.tsx +++ b/apps/docs/app/docs/[[...slug]]/page.tsx @@ -4,114 +4,114 @@ import { baseUrl } from "@/utils/metadata"; import { ImageZoom } from "fumadocs-ui/components/image-zoom"; import defaultMdxComponents from "fumadocs-ui/mdx"; import { - DocsBody, - DocsDescription, - DocsPage, - DocsTitle, + DocsBody, + DocsDescription, + DocsPage, + DocsTitle, } from "fumadocs-ui/page"; import { notFound, permanentRedirect } from "next/navigation"; export default async function Page(props: { - params: Promise<{ slug?: string[] }>; + params: Promise<{ slug?: string[] }>; }) { - const params = await props.params; - const page = source.getPage(params.slug); - if (!page) { - permanentRedirect("/docs/core"); - } + const params = await props.params; + const page = source.getPage(params.slug); + if (!page) { + permanentRedirect("/docs/core"); + } - const MDX = page.data.body; + const MDX = page.data.body; - return ( - - {page.data.title} - {page.data.description} - - , - p: ({ children }) => ( -

- {children} -

- ), - li: ({ children, id }) => ( -
  • - {children} -
  • - ), - APIPage: openapi.APIPage, - }} - /> -
    -
    - ); + return ( + + {page.data.title} + {page.data.description} + + , + p: ({ children }) => ( +

    + {children} +

    + ), + li: ({ children, id }) => ( +
  • + {children} +
  • + ), + APIPage: openapi.APIPage, + }} + /> +
    +
    + ); } export async function generateStaticParams() { - return source.generateParams(); + return source.generateParams(); } export async function generateMetadata(props: { - params: Promise<{ slug?: string[] }>; + params: Promise<{ slug?: string[] }>; }) { - const params = await props.params; - const page = source.getPage(params.slug); - if (!page) notFound(); + const params = await props.params; + const page = source.getPage(params.slug); + if (!page) notFound(); - return { - title: page.data.title, + return { + title: page.data.title, - description: page.data.description, - robots: "index,follow", - alternates: { - canonical: new URL(`${baseUrl}${page.url}`).toString(), - languages: { - en: `${baseUrl}/${page.url}`, - }, - }, - openGraph: { - title: page.data.title, - description: page.data.description, - url: new URL(`${baseUrl}`).toString(), - images: [ - { - url: new URL(`${baseUrl}/logo.png`).toString(), - width: 1200, - height: 630, - alt: page.data.title, - }, - ], - }, - twitter: { - card: "summary_large_image", - creator: "@getdokploy", - title: page.data.title, - description: page.data.description, - images: [ - { - url: new URL(`${baseUrl}/logo.png`).toString(), - width: 1200, - height: 630, - alt: page.data.title, - }, - ], - }, - applicationName: "Dokploy Docs", - keywords: [ - "dokploy", - "vps", - "open source", - "cloud", - "self hosting", - "free", - ], - icons: { - icon: "/icon.svg", - }, - }; + description: page.data.description, + robots: "index,follow", + alternates: { + canonical: new URL(`${baseUrl}${page.url}`).toString(), + languages: { + en: `${baseUrl}/${page.url}`, + }, + }, + openGraph: { + title: page.data.title, + description: page.data.description, + url: new URL(`${baseUrl}`).toString(), + images: [ + { + url: new URL(`${baseUrl}/logo.png`).toString(), + width: 1200, + height: 630, + alt: page.data.title, + }, + ], + }, + twitter: { + card: "summary_large_image", + creator: "@getdokploy", + title: page.data.title, + description: page.data.description, + images: [ + { + url: new URL(`${baseUrl}/logo.png`).toString(), + width: 1200, + height: 630, + alt: page.data.title, + }, + ], + }, + applicationName: "Dokploy Docs", + keywords: [ + "dokploy", + "vps", + "open source", + "cloud", + "self hosting", + "free", + ], + icons: { + icon: "/icon.svg", + }, + }; } diff --git a/apps/docs/app/docs/layout.tsx b/apps/docs/app/docs/layout.tsx index fb8ff11..322a090 100644 --- a/apps/docs/app/docs/layout.tsx +++ b/apps/docs/app/docs/layout.tsx @@ -5,18 +5,18 @@ import { DocsLayout } from "fumadocs-ui/layouts/docs"; import type { ReactNode } from "react"; export const metadata = createMetadata({ - title: { - template: "%s | Dokploy", - default: "Dokploy", - }, - description: "The Open Source Alternative to Vercel, Heroku, and Netlify", - metadataBase: new URL(baseUrl), + title: { + template: "%s | Dokploy", + default: "Dokploy", + }, + description: "The Open Source Alternative to Vercel, Heroku, and Netlify", + metadataBase: new URL(baseUrl), }); export default function Layout({ children }: { children: ReactNode }) { - return ( - - {children} - - ); + return ( + + {children} + + ); } diff --git a/apps/docs/app/layout.config.tsx b/apps/docs/app/layout.config.tsx index d04993b..b643745 100644 --- a/apps/docs/app/layout.config.tsx +++ b/apps/docs/app/layout.config.tsx @@ -1,11 +1,11 @@ import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared"; import { - Github, - GlobeIcon, - HeartIcon, - Rss, - LogIn, - UserPlus, + Github, + GlobeIcon, + HeartIcon, + Rss, + LogIn, + UserPlus, } from "lucide-react"; import Link from "next/link"; /** @@ -17,106 +17,106 @@ import Link from "next/link"; */ export const Logo = () => { - return ( - - + return ( + + - + - - - ); + + + ); }; export const baseOptions: BaseLayoutProps = { - nav: { - // title: "Dokploy", - children: ( - - - Dokploy - - ), - }, - links: [ - { - text: "Login", - url: "https://app.dokploy.com/", - active: "nested-url", - icon: , - }, - { - text: "Sign Up", - url: "https://app.dokploy.com/register", - active: "nested-url", - icon: , - }, - { - text: "Website", - url: "https://dokploy.com", - active: "nested-url", - icon: , - }, - { - text: "Discord", - url: "https://discord.com/invite/2tBnJ3jDJc", - active: "nested-url", - icon: ( - <> - - - - - ), - }, - { - text: "Support", - url: "https://opencollective.com/dokploy", - active: "nested-url", - icon: ( - <> - - - ), - }, - { - text: "Github", - url: "https://github.com/dokploy/dokploy", - active: "nested-url", - icon: ( - <> - - - ), - }, - { - text: "Blog", - url: "https://dokploy.com/blog", - active: "nested-url", - icon: ( - <> - - - ), - }, - ], + nav: { + // title: "Dokploy", + children: ( + + + Dokploy + + ), + }, + links: [ + { + text: "Login", + url: "https://app.dokploy.com/", + active: "nested-url", + icon: , + }, + { + text: "Sign Up", + url: "https://app.dokploy.com/register", + active: "nested-url", + icon: , + }, + { + text: "Website", + url: "https://dokploy.com", + active: "nested-url", + icon: , + }, + { + text: "Discord", + url: "https://discord.com/invite/2tBnJ3jDJc", + active: "nested-url", + icon: ( + <> + + + + + ), + }, + { + text: "Support", + url: "https://opencollective.com/dokploy", + active: "nested-url", + icon: ( + <> + + + ), + }, + { + text: "Github", + url: "https://github.com/dokploy/dokploy", + active: "nested-url", + icon: ( + <> + + + ), + }, + { + text: "Blog", + url: "https://dokploy.com/blog", + active: "nested-url", + icon: ( + <> + + + ), + }, + ], }; diff --git a/apps/docs/app/layout.tsx b/apps/docs/app/layout.tsx index 1c6adc0..d2bd648 100644 --- a/apps/docs/app/layout.tsx +++ b/apps/docs/app/layout.tsx @@ -4,19 +4,21 @@ import { Inter } from "next/font/google"; import type { ReactNode } from "react"; import { GoogleAnalytics } from "@next/third-parties/google"; const inter = Inter({ - subsets: ["latin"], + subsets: ["latin"], }); export default async function Layout({ - children, - ...rest -}: { children: ReactNode }) { - return ( - - - - {children} - - - ); + children, + ...rest +}: { + children: ReactNode; +}) { + return ( + + + + {children} + + + ); } diff --git a/apps/docs/app/robots.ts b/apps/docs/app/robots.ts index 3b00704..1c2ea94 100644 --- a/apps/docs/app/robots.ts +++ b/apps/docs/app/robots.ts @@ -1,11 +1,11 @@ import type { MetadataRoute } from "next"; export default function robots(): MetadataRoute.Robots { - return { - rules: { - userAgent: "*", - allow: "/", - }, - sitemap: "https://docs.dokploy.com/sitemap.xml", - }; + return { + rules: { + userAgent: "*", + allow: "/", + }, + sitemap: "https://docs.dokploy.com/sitemap.xml", + }; } diff --git a/apps/docs/app/sitemap.ts b/apps/docs/app/sitemap.ts index 39ca0aa..49a40f1 100644 --- a/apps/docs/app/sitemap.ts +++ b/apps/docs/app/sitemap.ts @@ -3,17 +3,17 @@ import { url } from "@/utils/metadata"; import type { MetadataRoute } from "next"; export default async function sitemap(): Promise { - return [ - ...(await Promise.all( - source.getPages().map(async (page) => { - const { lastModified } = page.data; - return { - url: url(page.url), - lastModified: lastModified ? new Date(lastModified) : undefined, - changeFrequency: "weekly", - priority: 0.5, - } as MetadataRoute.Sitemap[number]; - }), - )), - ]; + return [ + ...(await Promise.all( + source.getPages().map(async (page) => { + const { lastModified } = page.data; + return { + url: url(page.url), + lastModified: lastModified ? new Date(lastModified) : undefined, + changeFrequency: "weekly", + priority: 0.5, + } as MetadataRoute.Sitemap[number]; + }), + )), + ]; } diff --git a/apps/docs/lib/source.ts b/apps/docs/lib/source.ts index 5593e67..d588396 100644 --- a/apps/docs/lib/source.ts +++ b/apps/docs/lib/source.ts @@ -5,13 +5,13 @@ import { createOpenAPI } from "fumadocs-openapi/server"; import { attachFile } from "fumadocs-openapi/server"; export const source = loader({ - baseUrl: "/docs", - source: createMDXSource(docs, meta), - // pageTree: { - // attachFile, - // }, + baseUrl: "/docs", + source: createMDXSource(docs, meta), + // pageTree: { + // attachFile, + // }, }); export const openapi = createOpenAPI({ - // options + // options }); diff --git a/apps/docs/source.config.ts b/apps/docs/source.config.ts index 62ab25d..443d9d3 100644 --- a/apps/docs/source.config.ts +++ b/apps/docs/source.config.ts @@ -1,7 +1,7 @@ import { defineConfig, defineDocs } from "fumadocs-mdx/config"; export const { docs, meta } = defineDocs({ - dir: "content/docs", + dir: "content/docs", }); export default defineConfig(); diff --git a/apps/docs/utils/metadata.ts b/apps/docs/utils/metadata.ts index c68397d..8bc700d 100644 --- a/apps/docs/utils/metadata.ts +++ b/apps/docs/utils/metadata.ts @@ -1,30 +1,30 @@ import type { Metadata } from "next"; export const baseUrl = - process.env.NODE_ENV === "development" - ? "http://localhost:3000" - : "https://docs.dokploy.com"; + process.env.NODE_ENV === "development" + ? "http://localhost:3000" + : "https://docs.dokploy.com"; export const url = (path: string): string => new URL(path, baseUrl).toString(); export function createMetadata(override: Metadata): Metadata { - return { - ...override, - openGraph: { - title: override.title ?? undefined, - description: override.description ?? undefined, - url: "https://fumadocs.vercel.app", - images: "/og.png", - siteName: "Fumadocs", - ...override.openGraph, - }, - twitter: { - card: "summary_large_image", - creator: "@money_is_shark", - title: override.title ?? undefined, - description: override.description ?? undefined, - images: "/banner.png", - ...override.twitter, - }, - }; + return { + ...override, + openGraph: { + title: override.title ?? undefined, + description: override.description ?? undefined, + url: "https://fumadocs.vercel.app", + images: "/og.png", + siteName: "Fumadocs", + ...override.openGraph, + }, + twitter: { + card: "summary_large_image", + creator: "@money_is_shark", + title: override.title ?? undefined, + description: override.description ?? undefined, + images: "/banner.png", + ...override.twitter, + }, + }; } diff --git a/apps/website/README.md b/apps/website/README.md index 5a7f037..4d88448 100644 --- a/apps/website/README.md +++ b/apps/website/README.md @@ -19,18 +19,21 @@ Open http://localhost:3000 with your browser to see the result. ## Environment Variables ### Required for Contact Form + ``` RESEND_API_KEY=your_resend_api_key_here ``` ### Required for HubSpot Integration (Sales Forms) + ``` HUBSPOT_PORTAL_ID=147033433 HUBSPOT_FORM_GUID=0d788925-ef54-4fda-9b76-741fb5877056 ``` ### Required for Blog Page + ``` GHOST_URL="" GHOST_KEY="" -``` \ No newline at end of file +``` diff --git a/apps/website/app/[locale]/[...rest]/page.tsx b/apps/website/app/[locale]/[...rest]/page.tsx index 4583936..0ff9624 100644 --- a/apps/website/app/[locale]/[...rest]/page.tsx +++ b/apps/website/app/[locale]/[...rest]/page.tsx @@ -1,5 +1,5 @@ -import { notFound } from "next/navigation"; +import { notFound } from 'next/navigation' export default function CatchAll() { - notFound(); + notFound() } diff --git a/apps/website/app/[locale]/_changelog/page.tsx b/apps/website/app/[locale]/_changelog/page.tsx index f6b1b6d..9c1181d 100644 --- a/apps/website/app/[locale]/_changelog/page.tsx +++ b/apps/website/app/[locale]/_changelog/page.tsx @@ -3,59 +3,54 @@ // Datos de ejemplo del changelog const changelogEntries = [ { - date: "2023-11-01", - title: "Versión 2.1.0", + date: '2023-11-01', + title: 'Versión 2.1.0', changes: [ - "Añadido soporte para modo oscuro en todas las páginas", - "Mejorado el rendimiento para grandes conjuntos de datos", - "Corregido error en el flujo de autenticación de usuarios", + 'Añadido soporte para modo oscuro en todas las páginas', + 'Mejorado el rendimiento para grandes conjuntos de datos', + 'Corregido error en el flujo de autenticación de usuarios', ], - image: - "https://g-ndmbkfuhw2w.vusercontent.net/placeholder.svg?height=200&width=400", + image: 'https://g-ndmbkfuhw2w.vusercontent.net/placeholder.svg?height=200&width=400', }, { - date: "2023-10-15", - title: "Versión 2.0.1", + date: '2023-10-15', + title: 'Versión 2.0.1', changes: [ - "Corrección urgente para vulnerabilidad crítica de seguridad", - "Actualizadas las dependencias a las últimas versiones", + 'Corrección urgente para vulnerabilidad crítica de seguridad', + 'Actualizadas las dependencias a las últimas versiones', ], - image: - "https://g-ndmbkfuhw2w.vusercontent.net/placeholder.svg?height=200&width=400", + image: 'https://g-ndmbkfuhw2w.vusercontent.net/placeholder.svg?height=200&width=400', }, { - date: "2023-10-01", - title: "Versión 2.0.0", + date: '2023-10-01', + title: 'Versión 2.0.0', changes: [ - "Rediseño completo de la UI para mejorar la experiencia de usuario", - "Introducidas nuevas características en el panel de control", - "Añadido soporte para temas personalizados", - "Mejorada la accesibilidad en toda la aplicación", + 'Rediseño completo de la UI para mejorar la experiencia de usuario', + 'Introducidas nuevas características en el panel de control', + 'Añadido soporte para temas personalizados', + 'Mejorada la accesibilidad en toda la aplicación', ], - image: - "https://g-ndmbkfuhw2w.vusercontent.net/placeholder.svg?height=200&width=400", + image: 'https://g-ndmbkfuhw2w.vusercontent.net/placeholder.svg?height=200&width=400', }, { - date: "2023-09-15", - title: "Versión 1.9.5", + date: '2023-09-15', + title: 'Versión 1.9.5', changes: [ - "Optimización de rendimiento en la carga inicial", - "Nuevas opciones de personalización para usuarios premium", + 'Optimización de rendimiento en la carga inicial', + 'Nuevas opciones de personalización para usuarios premium', ], - image: - "https://g-ndmbkfuhw2w.vusercontent.net/placeholder.svg?height=200&width=400", + image: 'https://g-ndmbkfuhw2w.vusercontent.net/placeholder.svg?height=200&width=400', }, { - date: "2023-08-01", - title: "Versión 1.9.0", + date: '2023-08-01', + title: 'Versión 1.9.0', changes: [ - "Lanzamiento de la API pública para desarrolladores", - "Mejoras en la sincronización de datos entre dispositivos", + 'Lanzamiento de la API pública para desarrolladores', + 'Mejoras en la sincronización de datos entre dispositivos', ], - image: - "https://g-ndmbkfuhw2w.vusercontent.net/placeholder.svg?height=200&width=400", + image: 'https://g-ndmbkfuhw2w.vusercontent.net/placeholder.svg?height=200&width=400', }, -]; +] const Comp = () => { return ( @@ -66,47 +61,63 @@ const Comp = () => { - + - + -
    -

    Changelog

    +
    +

    Changelog

    {changelogEntries.map((entry, index) => (
    -
    +
    {entry.date} -

    {entry.title}

    +

    + {entry.title} +

    -
    +
    {`Imagen
    -
      - {entry.changes.map((change, changeIndex) => ( -
    • - {change} -
    • - ))} +
        + {entry.changes.map( + (change, changeIndex) => ( +
      • + {change} +
      • + ), + )}
    @@ -114,8 +125,8 @@ const Comp = () => {
    - ); -}; + ) +} // export default function Changelog() { // return ( diff --git a/apps/website/app/[locale]/privacy/page.tsx b/apps/website/app/[locale]/privacy/page.tsx index a87d611..dac7f9f 100644 --- a/apps/website/app/[locale]/privacy/page.tsx +++ b/apps/website/app/[locale]/privacy/page.tsx @@ -1,29 +1,35 @@ export default function Home() { return ( -
    -

    Privacy

    +
    +

    Privacy

    - At Dokploy, we are committed to protecting your privacy. This Privacy - Policy explains how we collect, use, and safeguard your personal - information when you use our website and services. + At Dokploy, we are committed to protecting your privacy. + This Privacy Policy explains how we collect, use, and + safeguard your personal information when you use our website + and services.

    - By using Dokploy, you agree to the collection and use of information - in accordance with this Privacy Policy. If you do not agree with these - practices, please do not use our services. + By using Dokploy, you agree to the collection and use of + information in accordance with this Privacy Policy. If you + do not agree with these practices, please do not use our + services.

    -

    +

    1. Information We Collect

    - We only collect limited, non-personal data through Umami Analytics, a - privacy-focused analytics tool. No personal identifying information - (PII) is collected. The data we collect includes: + We only collect limited, non-personal data through Umami + Analytics, a privacy-focused analytics tool. No personal + identifying information (PII) is collected. The data we + collect includes:

    -
      -
    • Website usage statistics (e.g., page views, session duration)
    • +
        +
      • + Website usage statistics (e.g., page views, session + duration) +
      • Anonymized IP addresses
      • Referring websites
      • Browser and device type
      • @@ -31,70 +37,77 @@ export default function Home() {
    -

    +

    2. How We Use the Information

    The information we collect is used solely for improving the - functionality and user experience of our platform. Specifically, we - use it to: + functionality and user experience of our platform. + Specifically, we use it to:

    -
      +
      • Monitor traffic and website performance
      • Optimize the user experience
      • Understand how users interact with our platform

      - Additionally, we use a single cookie to manage user sessions, which is - necessary for the proper functioning of the platform. + Additionally, we use a single cookie to manage user + sessions, which is necessary for the proper functioning of + the platform.

    -

    3. Data Security

    +

    + 3. Data Security +

    - We take reasonable precautions to protect your data. Since we do not - collect personal information, the risk of data misuse is minimized. - Umami Analytics is privacy-friendly and does not rely on cookies or - store PII. + We take reasonable precautions to protect your data. Since + we do not collect personal information, the risk of data + misuse is minimized. Umami Analytics is privacy-friendly and + does not rely on cookies or store PII.

    -

    4. Third-Party Services

    +

    + 4. Third-Party Services +

    - We do not share your data with any third-party services other than - Umami Analytics. We do not sell, trade, or transfer your data to - outside parties. + We do not share your data with any third-party services + other than Umami Analytics. We do not sell, trade, or + transfer your data to outside parties.

    -

    5. Cookies

    +

    5. Cookies

    - Dokploy does not use cookies to track user activity. Umami Analytics - is cookie-free and does not require any tracking cookies for its - functionality. + Dokploy does not use cookies to track user activity. Umami + Analytics is cookie-free and does not require any tracking + cookies for its functionality.

    -

    +

    6. Changes to This Privacy Policy

    - We may update this Privacy Policy from time to time. Any changes will - be posted on this page, and it is your responsibility to review this - policy periodically. + We may update this Privacy Policy from time to time. Any + changes will be posted on this page, and it is your + responsibility to review this policy periodically.

    -

    12. Contact Information

    +

    + 12. Contact Information +

    - If you have any questions or concerns regarding these Privacy Policy, - please contact us at: + If you have any questions or concerns regarding these + Privacy Policy, please contact us at:

    Email: @@ -107,5 +120,5 @@ export default function Home() {

    - ); + ) } diff --git a/apps/website/app/[locale]/terms/page.tsx b/apps/website/app/[locale]/terms/page.tsx index ddaa103..630c5a3 100644 --- a/apps/website/app/[locale]/terms/page.tsx +++ b/apps/website/app/[locale]/terms/page.tsx @@ -1,21 +1,22 @@ export default function Home() { return ( -
    -

    +
    +

    Terms and Conditions

    - Welcome to Dokploy! These Terms and Conditions outline the rules and - regulations for the use of Dokploy’s website and services. + Welcome to Dokploy! These Terms and Conditions outline the + rules and regulations for the use of Dokploy’s website and + services.

    - By accessing or using our services, you agree to be bound by the - following terms. If you do not agree with these terms, please do not - use our website or services. + By accessing or using our services, you agree to be bound by + the following terms. If you do not agree with these terms, + please do not use our website or services.

    -

    1. Definitions

    +

    1. Definitions

    Website: Refers to the website of Dokploy (

    - Services: The platform and related services offered by Dokploy for - deploying and managing applications using Docker and other related - tools. + Services: The platform and related services offered by + Dokploy for deploying and managing applications using Docker + and other related tools.

    User: Any individual or organization using Dokploy.

    - Subscription: The paid plan for using additional features, resources, - or server capacity. + Subscription: The paid plan for using additional features, + resources, or server capacity.

    -

    2. Service Description

    +

    + 2. Service Description +

    - Dokploy is a platform that allows users to deploy and manage web - applications on their own servers using custom builders and Docker - technology. Dokploy offers both free and paid services, including - subscriptions for adding additional servers, features, or increased - capacity. + Dokploy is a platform that allows users to deploy and manage + web applications on their own servers using custom builders + and Docker technology. Dokploy offers both free and paid + services, including subscriptions for adding additional + servers, features, or increased capacity.

    -

    +

    3. User Responsibilities

    - Users are responsible for maintaining the security of their accounts, - servers, and applications deployed through Dokploy. + Users are responsible for maintaining the security of their + accounts, servers, and applications deployed through + Dokploy.

    - Users must not use the platform for illegal activities, including but - not limited to distributing malware, violating intellectual property - rights, or engaging in cyberattacks. + Users must not use the platform for illegal activities, + including but not limited to distributing malware, violating + intellectual property rights, or engaging in cyberattacks.

    - Users must comply with all local, state, and international laws in - connection with their use of Dokploy. + Users must comply with all local, state, and international + laws in connection with their use of Dokploy.

    -

    +

    4. Subscription and Payment

    -
      +
      • - By purchasing a subscription, users agree to the pricing and payment - terms detailed on the website or via Paddle (our payment processor). + By purchasing a subscription, users agree to the pricing + and payment terms detailed on the website or via Paddle + (our payment processor).
      • - Subscriptions renew automatically unless canceled by the user before - the renewal date. + Subscriptions renew automatically unless canceled by the + user before the renewal date.
    -

    5. Refund Policy

    +

    + 5. Refund Policy +

    - Due to the nature of our digital services, Dokploy operates on a - no-refund policy for any paid subscriptions, except where required by - law. We offer a self-hosted version of Dokploy with the same core - functionalities, which users can deploy and use without any cost. We - recommend users try the self-hosted version to evaluate the platform - before committing to a paid subscription. + Due to the nature of our digital services, Dokploy operates + on a no-refund policy for any paid subscriptions, except + where required by law. We offer a self-hosted version of + Dokploy with the same core functionalities, which users can + deploy and use without any cost. We recommend users try the + self-hosted version to evaluate the platform before + committing to a paid subscription.

    -

    +

    6. Limitations of Liability

    - Dokploy is provided "as is" without any warranties, express or - implied, including but not limited to the availability, reliability, - or accuracy of the service. + Dokploy is provided "as is" without any warranties, express + or implied, including but not limited to the availability, + reliability, or accuracy of the service.

    - Users are fully responsible for any modifications made to their remote - servers or the environment where Dokploy is deployed. Any changes to - the server configuration, system settings, security policies, or other - environments that deviate from the recommended use of Dokploy may - result in compatibility issues, performance degradation, or security - vulnerabilities. Additionally, Dokploy may not function properly on - unsupported operating systems or environments. We do not guarantee the - platform will operate correctly or reliably under modified server - conditions or on unsupported systems, and we will not be held liable - for any disruptions, malfunctions, or damages resulting from such - changes or unsupported configurations. + Users are fully responsible for any modifications made to + their remote servers or the environment where Dokploy is + deployed. Any changes to the server configuration, system + settings, security policies, or other environments that + deviate from the recommended use of Dokploy may result in + compatibility issues, performance degradation, or security + vulnerabilities. Additionally, Dokploy may not function + properly on unsupported operating systems or environments. + We do not guarantee the platform will operate correctly or + reliably under modified server conditions or on unsupported + systems, and we will not be held liable for any disruptions, + malfunctions, or damages resulting from such changes or + unsupported configurations.

    -

    +

    7. Service Modifications and Downtime

    - While we strive to provide uninterrupted service, there may be periods - of downtime due to scheduled maintenance or upgrades to our - infrastructure, such as server maintenance or system improvements. We - will provide notice to users ahead of any planned maintenance. + While we strive to provide uninterrupted service, there may + be periods of downtime due to scheduled maintenance or + upgrades to our infrastructure, such as server maintenance + or system improvements. We will provide notice to users + ahead of any planned maintenance.

    -

    +

    8. Intellectual Property

    - Dokploy retains all intellectual property rights to the platform, - including code, design, and content. + Dokploy retains all intellectual property rights to the + platform, including code, design, and content.

    - Users are granted a limited, non-exclusive, and non-transferable - license to use Dokploy in accordance with these terms. + Users are granted a limited, non-exclusive, and + non-transferable license to use Dokploy in accordance with + these terms.

    - Users may not modify, reverse-engineer, or distribute any part of the - platform without express permission. + Users may not modify, reverse-engineer, or distribute any + part of the platform without express permission.

    -

    9. Termination

    +

    9. Termination

    - Dokploy reserves the right to suspend or terminate access to the - platform for users who violate these terms or engage in harmful - behavior. + Dokploy reserves the right to suspend or terminate access to + the platform for users who violate these terms or engage in + harmful behavior.

    - Users may terminate their account at any time by contacting support. - Upon termination, access to the platform will be revoked, and any - stored data may be permanently deleted. + Users may terminate their account at any time by contacting + support. Upon termination, access to the platform will be + revoked, and any stored data may be permanently deleted.

    -

    10. Changes to Terms

    +

    + 10. Changes to Terms +

    - Dokploy reserves the right to update these Terms & Conditions at any - time. Changes will be effective immediately upon posting on the - website. It is the user's responsibility to review these terms - periodically. + Dokploy reserves the right to update these Terms & + Conditions at any time. Changes will be effective + immediately upon posting on the website. It is the user's + responsibility to review these terms periodically.

    -

    11. Governing Law

    +

    + 11. Governing Law +

    - These Terms & Conditions are governed by applicable laws based on the - user's location. Any disputes arising under these terms will be - resolved in accordance with the legal jurisdiction relevant to the - user’s location, unless otherwise required by applicable law. + These Terms & Conditions are governed by applicable laws + based on the user's location. Any disputes arising under + these terms will be resolved in accordance with the legal + jurisdiction relevant to the user’s location, unless + otherwise required by applicable law.

    -

    12. Contact Information

    +

    + 12. Contact Information +

    - If you have any questions or concerns regarding these Terms, you can - reach us at: + If you have any questions or concerns regarding these Terms, + you can reach us at:

    Email: @@ -201,5 +220,5 @@ export default function Home() {

    - ); + ) } diff --git a/apps/website/app/api/contact/route.ts b/apps/website/app/api/contact/route.ts index 26469e1..1322171 100644 --- a/apps/website/app/api/contact/route.ts +++ b/apps/website/app/api/contact/route.ts @@ -1,31 +1,31 @@ -import type { NextRequest } from "next/server"; -import { NextResponse } from "next/server"; -import { Resend } from "resend"; -import { submitToHubSpot, getHubSpotUTK } from "@/lib/hubspot"; +import type { NextRequest } from 'next/server' +import { NextResponse } from 'next/server' +import { Resend } from 'resend' +import { submitToHubSpot, getHubSpotUTK } from '@/lib/hubspot' interface ContactFormData { - inquiryType: "support" | "sales" | "other"; - firstName: string; - lastName: string; - email: string; - company: string; - message: string; + inquiryType: 'support' | 'sales' | 'other' + firstName: string + lastName: string + email: string + company: string + message: string } export async function POST(request: NextRequest) { try { // Initialize Resend with API key check - const apiKey = process.env.RESEND_API_KEY; + const apiKey = process.env.RESEND_API_KEY if (!apiKey) { - console.error("RESEND_API_KEY is not configured"); + console.error('RESEND_API_KEY is not configured') return NextResponse.json( - { error: "Email service not configured" }, + { error: 'Email service not configured' }, { status: 500 }, - ); + ) } - const resend = new Resend(apiKey); - const body: ContactFormData = await request.json(); + const resend = new Resend(apiKey) + const body: ContactFormData = await request.json() // Validate required fields if ( @@ -37,41 +37,45 @@ export async function POST(request: NextRequest) { !body.message ) { return NextResponse.json( - { error: "All fields are required" }, + { error: 'All fields are required' }, { status: 400 }, - ); + ) } // Validate email format - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ if (!emailRegex.test(body.email)) { return NextResponse.json( - { error: "Invalid email format" }, + { error: 'Invalid email format' }, { status: 400 }, - ); + ) } // Submit to HubSpot if it's a sales inquiry - if (body.inquiryType === "sales") { + if (body.inquiryType === 'sales') { try { - const hutk = getHubSpotUTK(request.headers.get("cookie") || undefined); - const hubspotSuccess = await submitToHubSpot(body, hutk); + const hutk = getHubSpotUTK( + request.headers.get('cookie') || undefined, + ) + const hubspotSuccess = await submitToHubSpot(body, hutk) if (hubspotSuccess) { - console.log("Successfully submitted sales inquiry to HubSpot"); + console.log( + 'Successfully submitted sales inquiry to HubSpot', + ) } else { console.warn( - "Failed to submit sales inquiry to HubSpot, but continuing with email", - ); + 'Failed to submit sales inquiry to HubSpot, but continuing with email', + ) } } catch (error) { - console.error("Error submitting to HubSpot:", error); + console.error('Error submitting to HubSpot:', error) // Continue with email even if HubSpot fails } } // Format email content - const emailSubject = `[${body.inquiryType.toUpperCase()}] New contact form submission from ${body.firstName} ${body.lastName}`; + const emailSubject = `[${body.inquiryType.toUpperCase()}] New contact form submission from ${body.firstName} ${body.lastName}` const emailBody = ` New contact form submission: @@ -86,23 +90,23 @@ ${body.message} --- Sent from Dokploy website contact form - `.trim(); + `.trim() // Send email to Dokploy team await resend.emails.send({ - from: "Dokploy Contact Form ", + from: 'Dokploy Contact Form ', to: - body.inquiryType === "sales" - ? ["sales@dokploy.com", "contact@dokploy.com"] - : ["contact@dokploy.com"], + body.inquiryType === 'sales' + ? ['sales@dokploy.com', 'contact@dokploy.com'] + : ['contact@dokploy.com'], subject: emailSubject, text: emailBody, replyTo: body.email, - }); + }) // Send confirmation email to the user const confirmationSubject = - "Thank you for contacting Dokploy - We received your message"; + 'Thank you for contacting Dokploy - We received your message' const confirmationBody = ` Hello ${body.firstName} ${body.lastName}, @@ -122,24 +126,24 @@ The Dokploy Team --- This is an automated confirmation email. Please do not reply to this email. If you need immediate assistance, contact us at contact@dokploy.com - `.trim(); + `.trim() await resend.emails.send({ - from: "Dokploy Team ", + from: 'Dokploy Team ', to: [body.email], subject: confirmationSubject, text: confirmationBody, - }); + }) return NextResponse.json( - { message: "Contact form submitted successfully" }, + { message: 'Contact form submitted successfully' }, { status: 200 }, - ); + ) } catch (error) { - console.error("Error processing contact form:", error); + console.error('Error processing contact form:', error) return NextResponse.json( - { error: "Internal server error" }, + { error: 'Internal server error' }, { status: 500 }, - ); + ) } } diff --git a/apps/website/app/api/github-stars/route.ts b/apps/website/app/api/github-stars/route.ts index 57c7a4d..9426ff5 100644 --- a/apps/website/app/api/github-stars/route.ts +++ b/apps/website/app/api/github-stars/route.ts @@ -1,34 +1,32 @@ -import { NextResponse } from "next/server"; +import { NextResponse } from 'next/server' // Cache the result for 5 minutes to avoid rate limiting -let cachedStars: { count: number; timestamp: number } | null = null; -const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes in milliseconds +let cachedStars: { count: number; timestamp: number } | null = null +const CACHE_DURATION = 5 * 60 * 1000 // 5 minutes in milliseconds export async function GET(request: Request) { - const { searchParams } = new URL(request.url); - const owner = searchParams.get("owner"); - const repo = searchParams.get("repo"); + const { searchParams } = new URL(request.url) + const owner = searchParams.get('owner') + const repo = searchParams.get('repo') if (!owner || !repo) { return NextResponse.json( - { error: "Owner and repo parameters are required" }, + { error: 'Owner and repo parameters are required' }, { status: 400 }, - ); + ) } // Check if we have a valid cached result - if ( - cachedStars && - Date.now() - cachedStars.timestamp < CACHE_DURATION - ) { + if (cachedStars && Date.now() - cachedStars.timestamp < CACHE_DURATION) { return NextResponse.json( { stargazers_count: cachedStars.count }, { headers: { - "Cache-Control": "public, s-maxage=300, stale-while-revalidate=600", + 'Cache-Control': + 'public, s-maxage=300, stale-while-revalidate=600', }, }, - ); + ) } try { @@ -36,42 +34,42 @@ export async function GET(request: Request) { `https://api.github.com/repos/${owner}/${repo}`, { headers: { - Accept: "application/vnd.github.v3+json", - "User-Agent": "Dokploy-Website", + Accept: 'application/vnd.github.v3+json', + 'User-Agent': 'Dokploy-Website', }, }, - ); + ) if (!response.ok) { return NextResponse.json( - { error: "Failed to fetch repository data" }, + { error: 'Failed to fetch repository data' }, { status: response.status }, - ); + ) } - const data = await response.json(); - const starCount = data.stargazers_count; + const data = await response.json() + const starCount = data.stargazers_count // Cache the result cachedStars = { count: starCount, timestamp: Date.now(), - }; + } return NextResponse.json( { stargazers_count: starCount }, { headers: { - "Cache-Control": "public, s-maxage=300, stale-while-revalidate=600", + 'Cache-Control': + 'public, s-maxage=300, stale-while-revalidate=600', }, }, - ); + ) } catch (error) { - console.error("Error fetching GitHub stars:", error); + console.error('Error fetching GitHub stars:', error) return NextResponse.json( - { error: "Internal server error" }, + { error: 'Internal server error' }, { status: 500 }, - ); + ) } } - diff --git a/apps/website/app/api/og/route.ts b/apps/website/app/api/og/route.ts index 9883471..aafbde0 100644 --- a/apps/website/app/api/og/route.ts +++ b/apps/website/app/api/og/route.ts @@ -1,36 +1,36 @@ -import { getPost } from "@/lib/ghost"; -import { generateOGImage } from "@/lib/og-image"; -import type { NextRequest } from "next/server"; +import { getPost } from '@/lib/ghost' +import { generateOGImage } from '@/lib/og-image' +import type { NextRequest } from 'next/server' export async function GET(request: NextRequest) { try { - const { searchParams } = new URL(request.url); - const slug = searchParams.get("slug"); + const { searchParams } = new URL(request.url) + const slug = searchParams.get('slug') - console.log("Generating OG image for slug:", slug); + console.log('Generating OG image for slug:', slug) if (!slug) { - console.error("Missing slug parameter"); - return new Response("Missing slug parameter", { status: 400 }); + console.error('Missing slug parameter') + return new Response('Missing slug parameter', { status: 400 }) } - const post = await getPost(slug); + const post = await getPost(slug) if (!post) { - console.error("Post not found for slug:", slug); - return new Response("Post not found", { status: 404 }); + console.error('Post not found for slug:', slug) + return new Response('Post not found', { status: 404 }) } - console.log("Found post:", post.title); + console.log('Found post:', post.title) const formattedDate = new Date(post.published_at).toLocaleDateString( - "en-US", + 'en-US', { - year: "numeric", - month: "long", - day: "numeric", + year: 'numeric', + month: 'long', + day: 'numeric', }, - ); + ) const ogImage = await generateOGImage({ title: post.title, @@ -42,18 +42,18 @@ export async function GET(request: NextRequest) { : undefined, date: formattedDate, readingTime: post.reading_time, - }); + }) - console.log("Successfully generated OG image"); + console.log('Successfully generated OG image') return new Response(ogImage, { headers: { - "Content-Type": "image/png", - "Cache-Control": "public, max-age=31536000, immutable", + 'Content-Type': 'image/png', + 'Cache-Control': 'public, max-age=31536000, immutable', }, - }); + }) } catch (error) { - console.error("Error generating OG image:", error); - return new Response(`Error generating image: ${error}`, { status: 500 }); + console.error('Error generating OG image:', error) + return new Response(`Error generating image: ${error}`, { status: 500 }) } } diff --git a/apps/website/app/blog/[slug]/components/CodeBlock.tsx b/apps/website/app/blog/[slug]/components/CodeBlock.tsx index da8be4e..ac8ea91 100644 --- a/apps/website/app/blog/[slug]/components/CodeBlock.tsx +++ b/apps/website/app/blog/[slug]/components/CodeBlock.tsx @@ -1,39 +1,39 @@ -"use client"; +'use client' -import { CopyButton } from "@/components/ui/copy-button"; -import * as babel from "prettier/plugins/babel"; -import * as estree from "prettier/plugins/estree"; -import * as yaml from "prettier/plugins/yaml"; -import * as prettier from "prettier/standalone"; -import { type JSX, useLayoutEffect, useState } from "react"; -import type { BundledLanguage } from "shiki/bundle/web"; -import { highlight } from "./shared"; +import { CopyButton } from '@/components/ui/copy-button' +import * as babel from 'prettier/plugins/babel' +import * as estree from 'prettier/plugins/estree' +import * as yaml from 'prettier/plugins/yaml' +import * as prettier from 'prettier/standalone' +import { type JSX, useLayoutEffect, useState } from 'react' +import type { BundledLanguage } from 'shiki/bundle/web' +import { highlight } from './shared' interface CodeBlockProps { - code: string; - lang: BundledLanguage; - initial?: JSX.Element; + code: string + lang: BundledLanguage + initial?: JSX.Element } async function formatCode(code: string, lang: string) { try { - let parser: string; - let plugins = [] as any[]; + let parser: string + let plugins = [] as any[] switch (lang.toLowerCase()) { - case "yaml": - case "yml": - parser = "yaml"; - plugins = [yaml]; - break; - case "javascript": - case "typescript": - case "jsx": - case "tsx": - parser = "babel-ts"; - plugins = [babel, estree]; - break; + case 'yaml': + case 'yml': + parser = 'yaml' + plugins = [yaml] + break + case 'javascript': + case 'typescript': + case 'jsx': + case 'tsx': + parser = 'babel-ts' + plugins = [babel, estree] + break default: - return code; + return code } const formatted = await prettier.format(code, { parser, @@ -43,50 +43,50 @@ async function formatCode(code: string, lang: string) { tabWidth: 2, useTabs: false, printWidth: 120, - }); - return formatted; + }) + return formatted } catch (error) { - console.error("Error formatting code:", error); - return code; + console.error('Error formatting code:', error) + return code } } export function CodeBlock({ code, lang, initial }: CodeBlockProps) { - const [nodes, setNodes] = useState(initial); - const [formattedCode, setFormattedCode] = useState(code); + const [nodes, setNodes] = useState(initial) + const [formattedCode, setFormattedCode] = useState(code) useLayoutEffect(() => { async function formatAndHighlight() { try { - const formatted = await formatCode(code, lang); - setFormattedCode(formatted); - const highlighted = await highlight(formatted, lang); - setNodes(highlighted); + const formatted = await formatCode(code, lang) + setFormattedCode(formatted) + const highlighted = await highlight(formatted, lang) + setNodes(highlighted) } catch (error) { - const highlighted = await highlight(code, lang); - setNodes(highlighted); + const highlighted = await highlight(code, lang) + setNodes(highlighted) } } - void formatAndHighlight(); - }, [code, lang]); + void formatAndHighlight() + }, [code, lang]) if (!nodes) { return (
    -
    -
    -
    +
    +
    +
    - ); + ) } return (
    -
    +
    {nodes}
    - ); + ) } diff --git a/apps/website/app/blog/[slug]/components/Headings.tsx b/apps/website/app/blog/[slug]/components/Headings.tsx index cc6ac6d..7385214 100644 --- a/apps/website/app/blog/[slug]/components/Headings.tsx +++ b/apps/website/app/blog/[slug]/components/Headings.tsx @@ -1,18 +1,18 @@ -"use client"; +'use client' -import { useRouter } from "next/navigation"; -import type { DetailedHTMLProps, HTMLAttributes } from "react"; -import slugify from "slugify"; +import { useRouter } from 'next/navigation' +import type { DetailedHTMLProps, HTMLAttributes } from 'react' +import slugify from 'slugify' type HeadingProps = DetailedHTMLProps< HTMLAttributes, HTMLHeadingElement ->; +> function LinkIcon() { return ( - ); + ) } export function H1({ children, ...props }: HeadingProps) { - const router = useRouter(); - const id = slugify(children?.toString() || "", { lower: true, strict: true }); + const router = useRouter() + const id = slugify(children?.toString() || '', { + lower: true, + strict: true, + }) const handleClick = () => { - router.push(`#${id}`); - }; + router.push(`#${id}`) + } return (

    {children}

    - ); + ) } export function H2({ children, ...props }: HeadingProps) { - const router = useRouter(); - const id = slugify(children?.toString() || "", { lower: true, strict: true }); + const router = useRouter() + const id = slugify(children?.toString() || '', { + lower: true, + strict: true, + }) const handleClick = () => { - router.push(`#${id}`); - }; + router.push(`#${id}`) + } return (

    {children}

    - ); + ) } export function H3({ children, ...props }: HeadingProps) { - const router = useRouter(); - const id = slugify(children?.toString() || "", { lower: true, strict: true }); + const router = useRouter() + const id = slugify(children?.toString() || '', { + lower: true, + strict: true, + }) const handleClick = () => { - router.push(`#${id}`); - }; + router.push(`#${id}`) + } return (

    {children}

    - ); + ) } diff --git a/apps/website/app/blog/[slug]/components/TableOfContents.tsx b/apps/website/app/blog/[slug]/components/TableOfContents.tsx index 3bbaac3..099d3db 100644 --- a/apps/website/app/blog/[slug]/components/TableOfContents.tsx +++ b/apps/website/app/blog/[slug]/components/TableOfContents.tsx @@ -1,65 +1,67 @@ -"use client"; +'use client' -import { useEffect, useState } from "react"; +import { useEffect, useState } from 'react' interface Heading { - id: string; - text: string; - level: number; + id: string + text: string + level: number } export function TableOfContents() { - const [headings, setHeadings] = useState([]); - const [activeId, setActiveId] = useState(); + const [headings, setHeadings] = useState([]) + const [activeId, setActiveId] = useState() useEffect(() => { - const elements = Array.from(document.querySelectorAll("h1, h2, h3")) + const elements = Array.from(document.querySelectorAll('h1, h2, h3')) .filter((element) => element.id) .map((element) => ({ id: element.id, - text: element.textContent || "", + text: element.textContent || '', level: Number(element.tagName.charAt(1)), - })); - setHeadings(elements); + })) + setHeadings(elements) const observer = new IntersectionObserver( (entries) => { for (const entry of entries) { if (entry.isIntersecting) { - setActiveId(entry.target.id); + setActiveId(entry.target.id) } } }, - { rootMargin: "-100px 0px -66%" }, - ); + { rootMargin: '-100px 0px -66%' }, + ) for (const { id } of elements) { - const element = document.getElementById(id); - if (element) observer.observe(element); + const element = document.getElementById(id) + if (element) observer.observe(element) } - return () => observer.disconnect(); - }, []); + return () => observer.disconnect() + }, []) return (
    - ); + ) } diff --git a/apps/website/app/blog/[slug]/components/ZoomableImage.tsx b/apps/website/app/blog/[slug]/components/ZoomableImage.tsx index 7b97960..cbcc66e 100644 --- a/apps/website/app/blog/[slug]/components/ZoomableImage.tsx +++ b/apps/website/app/blog/[slug]/components/ZoomableImage.tsx @@ -1,21 +1,25 @@ -"use client"; +'use client' -import { cn } from "@/lib/utils"; -import { PhotoProvider, PhotoView } from "react-photo-view"; -import "react-photo-view/dist/react-photo-view.css"; +import { cn } from '@/lib/utils' +import { PhotoProvider, PhotoView } from 'react-photo-view' +import 'react-photo-view/dist/react-photo-view.css' interface ZoomableImageProps { - src: string; - alt: string; - className?: string; + src: string + alt: string + className?: string } export function ZoomableImage({ src, alt, className }: ZoomableImageProps) { return ( - {alt} + {alt} - ); + ) } diff --git a/apps/website/app/blog/[slug]/components/shared.ts b/apps/website/app/blog/[slug]/components/shared.ts index 4e6096b..a2daa08 100644 --- a/apps/website/app/blog/[slug]/components/shared.ts +++ b/apps/website/app/blog/[slug]/components/shared.ts @@ -1,14 +1,14 @@ -import { toJsxRuntime } from "hast-util-to-jsx-runtime"; -import type { JSX } from "react"; -import { Fragment } from "react"; -import { jsx, jsxs } from "react/jsx-runtime"; -import type { BundledLanguage } from "shiki/bundle/web"; -import { codeToHast } from "shiki/bundle/web"; +import { toJsxRuntime } from 'hast-util-to-jsx-runtime' +import type { JSX } from 'react' +import { Fragment } from 'react' +import { jsx, jsxs } from 'react/jsx-runtime' +import type { BundledLanguage } from 'shiki/bundle/web' +import { codeToHast } from 'shiki/bundle/web' export async function highlight(code: string, lang: BundledLanguage) { const out = await codeToHast(code, { lang, - theme: "houston", - }); - return toJsxRuntime(out, { Fragment, jsx, jsxs }) as JSX.Element; + theme: 'houston', + }) + return toJsxRuntime(out, { Fragment, jsx, jsxs }) as JSX.Element } diff --git a/apps/website/app/blog/[slug]/page.tsx b/apps/website/app/blog/[slug]/page.tsx index 1907a88..5740d6b 100644 --- a/apps/website/app/blog/[slug]/page.tsx +++ b/apps/website/app/blog/[slug]/page.tsx @@ -1,48 +1,48 @@ -import { getPost, getPosts } from "@/lib/ghost"; -import type { Metadata, ResolvingMetadata } from "next"; -import Image from "next/image"; -import Link from "next/link"; -import { notFound } from "next/navigation"; -import type React from "react"; -import ReactMarkdown from "react-markdown"; -import type { Components } from "react-markdown"; -import rehypeRaw from "rehype-raw"; -import remarkGfm from "remark-gfm"; -import remarkToc from "remark-toc"; -import type { BundledLanguage } from "shiki/bundle/web"; -import TurndownService from "turndown"; +import { getPost, getPosts } from '@/lib/ghost' +import type { Metadata, ResolvingMetadata } from 'next' +import Image from 'next/image' +import Link from 'next/link' +import { notFound } from 'next/navigation' +import type React from 'react' +import ReactMarkdown from 'react-markdown' +import type { Components } from 'react-markdown' +import rehypeRaw from 'rehype-raw' +import remarkGfm from 'remark-gfm' +import remarkToc from 'remark-toc' +import type { BundledLanguage } from 'shiki/bundle/web' +import TurndownService from 'turndown' // @ts-ignore -import * as turndownPluginGfm from "turndown-plugin-gfm"; -import { CodeBlock } from "./components/CodeBlock"; -import { H1, H2, H3 } from "./components/Headings"; -import { TableOfContents } from "./components/TableOfContents"; -import { ZoomableImage } from "./components/ZoomableImage"; -import { useTranslations } from "@/lib/intl"; +import * as turndownPluginGfm from 'turndown-plugin-gfm' +import { CodeBlock } from './components/CodeBlock' +import { H1, H2, H3 } from './components/Headings' +import { TableOfContents } from './components/TableOfContents' +import { ZoomableImage } from './components/ZoomableImage' +import { useTranslations } from '@/lib/intl' type Props = { - params: { slug: string }; -}; + params: { slug: string } +} export async function generateMetadata( { params }: Props, parent: ResolvingMetadata, ): Promise { - const { slug } = await params; - const post = await getPost(slug); + const { slug } = await params + const post = await getPost(slug) if (!post) { return { - title: "Post Not Found", - }; + title: 'Post Not Found', + } } const ogUrl = new URL( `/api/og`, - process.env.NODE_ENV === "production" - ? "https://dokploy.com" - : "http://localhost:3000", - ); - ogUrl.searchParams.set("slug", slug); + process.env.NODE_ENV === 'production' + ? 'https://dokploy.com' + : 'http://localhost:3000', + ) + ogUrl.searchParams.set('slug', slug) return { title: post.title, @@ -50,7 +50,7 @@ export async function generateMetadata( openGraph: { title: post.title, description: post.custom_excerpt || post.excerpt, - type: "article", + type: 'article', url: `${process.env.NEXT_PUBLIC_APP_URL}/blog/${post.slug}`, images: [ { @@ -62,67 +62,67 @@ export async function generateMetadata( ], }, twitter: { - card: "summary_large_image", + card: 'summary_large_image', title: post.title, description: post.custom_excerpt || post.excerpt, images: [ogUrl.toString()], }, - }; + } } export default async function BlogPostPage({ params }: Props) { - const { slug } = await params; - const t = useTranslations("blog"); - const post = await getPost(slug); - const allPosts = await getPosts(); + const { slug } = await params + const t = useTranslations('blog') + const post = await getPost(slug) + const allPosts = await getPosts() - const relatedPosts = allPosts.filter((p) => p.id !== post?.id).slice(0, 3); + const relatedPosts = allPosts.filter((p) => p.id !== post?.id).slice(0, 3) if (!post) { - notFound(); + notFound() } const cleanHtml = (html: string) => { - if (typeof window !== "undefined") { - const parser = new DOMParser(); - const doc = parser.parseFromString(html, "text/html"); + if (typeof window !== 'undefined') { + const parser = new DOMParser() + const doc = parser.parseFromString(html, 'text/html') const scripts = doc.querySelectorAll( 'script[type="application/ld+json"], script', - ); - scripts.forEach((script) => script.remove()); - const unwantedElements = doc.querySelectorAll("style, meta, link"); - unwantedElements.forEach((el) => el.remove()); - return doc.body.innerHTML; + ) + scripts.forEach((script) => script.remove()) + const unwantedElements = doc.querySelectorAll('style, meta, link') + unwantedElements.forEach((el) => el.remove()) + return doc.body.innerHTML } else { return html .replace( /]*type="application\/ld\+json"[^>]*>[\s\S]*?<\/script>/gi, - "", + '', ) - .replace(/]*>[\s\S]*?<\/script>/gi, "") - .replace(/]*>[\s\S]*?<\/style>/gi, "") - .replace(/]*>/gi, "") - .replace(/]*>/gi, ""); + .replace(/]*>[\s\S]*?<\/script>/gi, '') + .replace(/]*>[\s\S]*?<\/style>/gi, '') + .replace(/]*>/gi, '') + .replace(/]*>/gi, '') } - }; + } const turndownService = new TurndownService({ - headingStyle: "atx", - codeBlockStyle: "fenced", - }); - const gfm = turndownPluginGfm.gfm; - const tables = turndownPluginGfm.tables; - const strikethrough = turndownPluginGfm.strikethrough; - turndownService.use([tables, strikethrough, gfm, remarkToc]); + headingStyle: 'atx', + codeBlockStyle: 'fenced', + }) + const gfm = turndownPluginGfm.gfm + const tables = turndownPluginGfm.tables + const strikethrough = turndownPluginGfm.strikethrough + turndownService.use([tables, strikethrough, gfm, remarkToc]) - const cleanedHtml = cleanHtml(post.html); - const markdown = turndownService.turndown(cleanedHtml); + const cleanedHtml = cleanHtml(post.html) + const markdown = turndownService.turndown(cleanedHtml) - const formattedDate = new Date(post.published_at).toLocaleDateString("en", { - year: "numeric", - month: "long", - day: "numeric", - }); + const formattedDate = new Date(post.published_at).toLocaleDateString('en', { + year: 'numeric', + month: 'long', + day: 'numeric', + }) const components: Partial = { h1: H1, @@ -130,7 +130,7 @@ export default async function BlogPostPage({ params }: Props) { h3: H3, p: ({ node, children, ...props }) => (

    {children} @@ -139,7 +139,7 @@ export default async function BlogPostPage({ params }: Props) { a: ({ node, href, ...props }) => ( (