From c40e0bb07e45bb93571086abeb50b2f7c9a8ca2f Mon Sep 17 00:00:00 2001 From: Sam Rizvi Date: Sat, 4 Nov 2023 23:33:42 -0700 Subject: [PATCH 1/2] chore: prep write for migration to labs --- apps/write/.env.example | 36 - apps/write/.eslintrc.js | 4 - apps/write/.gitignore | 9 - apps/write/.nvmrc | 1 - apps/write/.prettierignore | 3 - apps/write/README.md | 140 - apps/write/app/[domain]/[slug]/not-found.tsx | 29 - .../app/[domain]/[slug]/opengraph-image.tsx | 84 - apps/write/app/[domain]/[slug]/page.tsx | 180 - apps/write/app/[domain]/layout.tsx | 114 - apps/write/app/[domain]/page.tsx | 141 - .../write/app/api/auth/[...nextauth]/route.ts | 7 - .../app/api/domain/[slug]/verify/route.ts | 49 - apps/write/app/api/generate/route.ts | 76 - apps/write/app/api/upload/route.ts | 26 - apps/write/app/app/(auth)/layout.tsx | 14 - .../app/app/(auth)/login/login-button.tsx | 54 - apps/write/app/app/(auth)/login/page.tsx | 36 - apps/write/app/app/(dashboard)/layout.tsx | 17 - apps/write/app/app/(dashboard)/loading.tsx | 12 - apps/write/app/app/(dashboard)/page.tsx | 59 - .../app/app/(dashboard)/post/[id]/layout.tsx | 5 - .../app/app/(dashboard)/post/[id]/loading.tsx | 10 - .../app/(dashboard)/post/[id]/not-found.tsx | 18 - .../app/app/(dashboard)/post/[id]/page.tsx | 29 - .../(dashboard)/post/[id]/settings/page.tsx | 61 - .../app/app/(dashboard)/settings/page.tsx | 46 - .../(dashboard)/site/[id]/analytics/page.tsx | 47 - .../app/app/(dashboard)/site/[id]/layout.tsx | 9 - .../app/app/(dashboard)/site/[id]/loading.tsx | 16 - .../app/(dashboard)/site/[id]/not-found.tsx | 26 - .../app/app/(dashboard)/site/[id]/page.tsx | 54 - .../site/[id]/settings/appearance/page.tsx | 66 - .../site/[id]/settings/domains/page.tsx | 47 - .../(dashboard)/site/[id]/settings/layout.tsx | 55 - .../(dashboard)/site/[id]/settings/nav.tsx | 48 - .../(dashboard)/site/[id]/settings/page.tsx | 49 - apps/write/app/app/(dashboard)/sites/page.tsx | 35 - apps/write/app/favicon.ico | Bin 15086 -> 0 bytes apps/write/app/home/page.tsx | 23 - apps/write/app/layout.tsx | 47 - apps/write/app/providers.tsx | 17 - apps/write/app/robots.ts | 10 - apps/write/app/sitemap.ts | 25 - apps/write/components/analytics.tsx | 153 - apps/write/components/blog-card.tsx | 42 - apps/write/components/blur-image.tsx | 24 - apps/write/components/create-post-button.tsx | 39 - apps/write/components/create-site-button.tsx | 21 - apps/write/components/cta.tsx | 80 - apps/write/components/editor.tsx | 142 - .../components/form/delete-post-form.tsx | 76 - .../components/form/delete-site-form.tsx | 78 - .../components/form/domain-configuration.tsx | 171 - apps/write/components/form/domain-status.tsx | 32 - apps/write/components/form/index.tsx | 154 - .../form/loading-spinner.module.css | 84 - .../write/components/form/loading-spinner.tsx | 20 - apps/write/components/form/uploader.tsx | 143 - .../components/form/use-domain-status.ts | 21 - .../write/components/icons/loading-circle.tsx | 22 - .../components/icons/loading-dots.module.css | 40 - apps/write/components/icons/loading-dots.tsx | 17 - apps/write/components/icons/magic.tsx | 30 - apps/write/components/logo-square.tsx | 80 - apps/write/components/logout-button.tsx | 15 - apps/write/components/mdx.module.css | 7 - apps/write/components/mdx.tsx | 95 - apps/write/components/modal/create-site.tsx | 142 - apps/write/components/modal/index.tsx | 86 - apps/write/components/modal/leaflet.tsx | 71 - apps/write/components/modal/provider.tsx | 44 - apps/write/components/nav.tsx | 206 - apps/write/components/overview-sites-cta.tsx | 32 - apps/write/components/overview-stats.tsx | 58 - apps/write/components/placeholder-card.tsx | 12 - apps/write/components/post-card.tsx | 62 - apps/write/components/posts.tsx | 54 - apps/write/components/profile.tsx | 38 - apps/write/components/report-abuse.tsx | 89 - apps/write/components/site-card.tsx | 58 - apps/write/components/sites.tsx | 46 - apps/write/components/uploader.tsx | 206 - apps/write/lib/actions.ts | 437 -- apps/write/lib/auth.ts | 135 - apps/write/lib/domains.ts | 142 - apps/write/lib/fetchers.ts | 135 - apps/write/lib/hooks/use-window-size.ts | 38 - apps/write/lib/prisma.ts | 12 - apps/write/lib/remark-plugins.tsx | 120 - apps/write/lib/types.ts | 58 - apps/write/lib/utils.ts | 53 - apps/write/middleware.ts | 63 - apps/write/next-env.d.ts | 5 - apps/write/next.config.js | 18 - apps/write/package.json | 68 - apps/write/postcss.config.js | 1 - apps/write/prisma/schema.prisma | 122 - apps/write/public/empty-state.png | Bin 251520 -> 0 bytes apps/write/public/logo-square.png | Bin 7961 -> 0 bytes apps/write/public/logo.png | Bin 15013 -> 0 bytes apps/write/public/og-image.png | Bin 525612 -> 0 bytes apps/write/public/placeholder.png | Bin 74758 -> 0 bytes apps/write/public/producthunt.png | Bin 7145 -> 0 bytes apps/write/public/thumbnail.png | Bin 69925 -> 0 bytes apps/write/public/vercel.svg | 4 - apps/write/styles/CalSans-SemiBold.otf | Bin 68292 -> 0 bytes apps/write/styles/fonts.ts | 39 - apps/write/styles/globals.css | 185 - apps/write/tailwind.config.js | 226 -- apps/write/tsconfig.json | 9 - apps/write/vercel.json | 6 - pnpm-lock.yaml | 3574 +---------------- turbo.json | 29 - 114 files changed, 121 insertions(+), 9982 deletions(-) delete mode 100644 apps/write/.env.example delete mode 100644 apps/write/.eslintrc.js delete mode 100644 apps/write/.gitignore delete mode 100644 apps/write/.nvmrc delete mode 100644 apps/write/.prettierignore delete mode 100644 apps/write/README.md delete mode 100644 apps/write/app/[domain]/[slug]/not-found.tsx delete mode 100644 apps/write/app/[domain]/[slug]/opengraph-image.tsx delete mode 100644 apps/write/app/[domain]/[slug]/page.tsx delete mode 100644 apps/write/app/[domain]/layout.tsx delete mode 100644 apps/write/app/[domain]/page.tsx delete mode 100644 apps/write/app/api/auth/[...nextauth]/route.ts delete mode 100644 apps/write/app/api/domain/[slug]/verify/route.ts delete mode 100644 apps/write/app/api/generate/route.ts delete mode 100644 apps/write/app/api/upload/route.ts delete mode 100644 apps/write/app/app/(auth)/layout.tsx delete mode 100644 apps/write/app/app/(auth)/login/login-button.tsx delete mode 100644 apps/write/app/app/(auth)/login/page.tsx delete mode 100644 apps/write/app/app/(dashboard)/layout.tsx delete mode 100644 apps/write/app/app/(dashboard)/loading.tsx delete mode 100644 apps/write/app/app/(dashboard)/page.tsx delete mode 100644 apps/write/app/app/(dashboard)/post/[id]/layout.tsx delete mode 100644 apps/write/app/app/(dashboard)/post/[id]/loading.tsx delete mode 100644 apps/write/app/app/(dashboard)/post/[id]/not-found.tsx delete mode 100644 apps/write/app/app/(dashboard)/post/[id]/page.tsx delete mode 100644 apps/write/app/app/(dashboard)/post/[id]/settings/page.tsx delete mode 100644 apps/write/app/app/(dashboard)/settings/page.tsx delete mode 100644 apps/write/app/app/(dashboard)/site/[id]/analytics/page.tsx delete mode 100644 apps/write/app/app/(dashboard)/site/[id]/layout.tsx delete mode 100644 apps/write/app/app/(dashboard)/site/[id]/loading.tsx delete mode 100644 apps/write/app/app/(dashboard)/site/[id]/not-found.tsx delete mode 100644 apps/write/app/app/(dashboard)/site/[id]/page.tsx delete mode 100644 apps/write/app/app/(dashboard)/site/[id]/settings/appearance/page.tsx delete mode 100644 apps/write/app/app/(dashboard)/site/[id]/settings/domains/page.tsx delete mode 100644 apps/write/app/app/(dashboard)/site/[id]/settings/layout.tsx delete mode 100644 apps/write/app/app/(dashboard)/site/[id]/settings/nav.tsx delete mode 100644 apps/write/app/app/(dashboard)/site/[id]/settings/page.tsx delete mode 100644 apps/write/app/app/(dashboard)/sites/page.tsx delete mode 100644 apps/write/app/favicon.ico delete mode 100644 apps/write/app/home/page.tsx delete mode 100644 apps/write/app/layout.tsx delete mode 100644 apps/write/app/providers.tsx delete mode 100644 apps/write/app/robots.ts delete mode 100644 apps/write/app/sitemap.ts delete mode 100644 apps/write/components/analytics.tsx delete mode 100644 apps/write/components/blog-card.tsx delete mode 100644 apps/write/components/blur-image.tsx delete mode 100644 apps/write/components/create-post-button.tsx delete mode 100644 apps/write/components/create-site-button.tsx delete mode 100644 apps/write/components/cta.tsx delete mode 100644 apps/write/components/editor.tsx delete mode 100644 apps/write/components/form/delete-post-form.tsx delete mode 100644 apps/write/components/form/delete-site-form.tsx delete mode 100644 apps/write/components/form/domain-configuration.tsx delete mode 100644 apps/write/components/form/domain-status.tsx delete mode 100644 apps/write/components/form/index.tsx delete mode 100644 apps/write/components/form/loading-spinner.module.css delete mode 100644 apps/write/components/form/loading-spinner.tsx delete mode 100644 apps/write/components/form/uploader.tsx delete mode 100644 apps/write/components/form/use-domain-status.ts delete mode 100644 apps/write/components/icons/loading-circle.tsx delete mode 100644 apps/write/components/icons/loading-dots.module.css delete mode 100644 apps/write/components/icons/loading-dots.tsx delete mode 100644 apps/write/components/icons/magic.tsx delete mode 100644 apps/write/components/logo-square.tsx delete mode 100644 apps/write/components/logout-button.tsx delete mode 100644 apps/write/components/mdx.module.css delete mode 100644 apps/write/components/mdx.tsx delete mode 100644 apps/write/components/modal/create-site.tsx delete mode 100644 apps/write/components/modal/index.tsx delete mode 100644 apps/write/components/modal/leaflet.tsx delete mode 100644 apps/write/components/modal/provider.tsx delete mode 100644 apps/write/components/nav.tsx delete mode 100644 apps/write/components/overview-sites-cta.tsx delete mode 100644 apps/write/components/overview-stats.tsx delete mode 100644 apps/write/components/placeholder-card.tsx delete mode 100644 apps/write/components/post-card.tsx delete mode 100644 apps/write/components/posts.tsx delete mode 100644 apps/write/components/profile.tsx delete mode 100644 apps/write/components/report-abuse.tsx delete mode 100644 apps/write/components/site-card.tsx delete mode 100644 apps/write/components/sites.tsx delete mode 100644 apps/write/components/uploader.tsx delete mode 100644 apps/write/lib/actions.ts delete mode 100644 apps/write/lib/auth.ts delete mode 100644 apps/write/lib/domains.ts delete mode 100644 apps/write/lib/fetchers.ts delete mode 100644 apps/write/lib/hooks/use-window-size.ts delete mode 100644 apps/write/lib/prisma.ts delete mode 100644 apps/write/lib/remark-plugins.tsx delete mode 100644 apps/write/lib/types.ts delete mode 100644 apps/write/lib/utils.ts delete mode 100644 apps/write/middleware.ts delete mode 100644 apps/write/next-env.d.ts delete mode 100644 apps/write/next.config.js delete mode 100644 apps/write/package.json delete mode 100644 apps/write/postcss.config.js delete mode 100644 apps/write/prisma/schema.prisma delete mode 100644 apps/write/public/empty-state.png delete mode 100644 apps/write/public/logo-square.png delete mode 100644 apps/write/public/logo.png delete mode 100644 apps/write/public/og-image.png delete mode 100644 apps/write/public/placeholder.png delete mode 100644 apps/write/public/producthunt.png delete mode 100644 apps/write/public/thumbnail.png delete mode 100644 apps/write/public/vercel.svg delete mode 100644 apps/write/styles/CalSans-SemiBold.otf delete mode 100644 apps/write/styles/fonts.ts delete mode 100644 apps/write/styles/globals.css delete mode 100644 apps/write/tailwind.config.js delete mode 100644 apps/write/tsconfig.json delete mode 100644 apps/write/vercel.json diff --git a/apps/write/.env.example b/apps/write/.env.example deleted file mode 100644 index 3f10b1a..0000000 --- a/apps/write/.env.example +++ /dev/null @@ -1,36 +0,0 @@ -# DON'T FORGET TO RENAME TO .env OR .env.local BEFORE PUSHING TO GIT - -### DEVELOPMENT ONLY VARIABLES -# These variables need to be set for local development only - -# Mandatory next-auth URL for localhost -NEXTAUTH_URL=http://app.localhost:3000 - -### PRODUCTION & DEVELOPMENT VARIABLES -# These variables need to be set for local development and when deployed on Vercel - -# Change this to your own domain -NEXT_PUBLIC_ROOT_DOMAIN=vercel.pub - -# PostgreSQL database URL – get one here: https://vercel.com/docs/storage/vercel-postgres/quickstart -POSTGRES_PRISMA_URL= -POSTGRES_URL_NON_POOLING= - -# Vercel Blob Storage for image uploads – currently in beta, please fill out this form for access: https://tally.so/r/nPDMNd. Setup instructions: https://vercel.com/docs/storage/vercel-blob/quickstart -BLOB_READ_WRITE_TOKEN= - -# GitHub OAuth secrets for auth & login – generate these here: https://github-client-generator.vercel.app/ -NEXTAUTH_SECRET= -AUTH_GITHUB_ID= -AUTH_GITHUB_SECRET= - -# Vercel authentication token that can be found here: https://vercel.com/account/tokens -AUTH_BEARER_TOKEN= -# Vercel Project ID that can be found here: https://vercel.com///settings -PROJECT_ID_VERCEL= -# Vercel Team ID that can be found here: https://vercel.com/teams//settings -# Only required if you're using this with a Vercel team -TEAM_ID_VERCEL= - -# OpenAI API key for AI text generation – get one here: https://platform.openai.com/account/api-keys -OPENAI_API_KEY= diff --git a/apps/write/.eslintrc.js b/apps/write/.eslintrc.js deleted file mode 100644 index 03ee743..0000000 --- a/apps/write/.eslintrc.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - root: true, - extends: ['custom'], -}; diff --git a/apps/write/.gitignore b/apps/write/.gitignore deleted file mode 100644 index 0ba0377..0000000 --- a/apps/write/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -**/node_modules -**.next -**.env -**.DS_Store -**.vercel -.vscode -**.git -.vercel -tsconfig.tsbuildinfo diff --git a/apps/write/.nvmrc b/apps/write/.nvmrc deleted file mode 100644 index a58d2d2..0000000 --- a/apps/write/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -18.18.2 \ No newline at end of file diff --git a/apps/write/.prettierignore b/apps/write/.prettierignore deleted file mode 100644 index 88f425b..0000000 --- a/apps/write/.prettierignore +++ /dev/null @@ -1,3 +0,0 @@ -pnpm-lock.yaml -node_modules -.next \ No newline at end of file diff --git a/apps/write/README.md b/apps/write/README.md deleted file mode 100644 index b999141..0000000 --- a/apps/write/README.md +++ /dev/null @@ -1,140 +0,0 @@ - - Platforms Starter Kit -

Platforms Starter Kit

-
- -

- The all-in-one starter kit
- for building multi-tenant applications. -

- -

- Introduction · - Demo · - Deploy Your Own · - Guide · - Kitchen Sink · - Contributing -

-
- -## Introduction - -The [Platforms Starter Kit](https://app.vercel.pub/) is a full-stack Next.js app with multi-tenancy and custom domain support. Built with [Next.js App Router](https://nextjs.org/docs/app), [Vercel Postgres](https://vercel.com/storage/postgres) and the [Vercel Domains API](https://vercel.com/docs/rest-api/endpoints#domains). - -Here's a quick 30-second demo: - -https://github.com/vercel/platforms/assets/28986134/bd370257-0c27-4cf5-8a56-28589f36f0ef - -## Features - -1. **Multi-tenancy:** Programmatically assign unlimited custom domains, subdomains, and SSL certificates to your users using the [Vercel Domains API](https://vercel.com/docs/rest-api/endpoints#domains) -2. **Performance**: Fast & beautiful blog posts cached via [Vercel's Edge Network](https://vercel.com/docs/concepts/edge-network/overview), with the ability to invalidate the cache on-demand (when users make changes) using [Incremental Static Regeneration](https://vercel.com/docs/concepts/next.js/incremental-static-regeneration) + Next.js' `revalidateTag` API -3. **AI Editor**: AI-powered Markdown editor for a Notion-style writing experience powered by [Novel](https://novel.sh/) -4. **Image Uploads**: Drag & drop / copy & paste image uploads, backed by [Vercel Blob](https://vercel.com/storage/blob) -5. **Custom styles**: Custom fonts, 404 pages, favicons, sitemaps for each site via the [Next.js file-based Metadata API](https://nextjs.org/docs/app/api-reference/file-conventions/metadata) -6. **Dynamic OG Cards**: Each blog post comes with a dynamic OG image powered by [@vercel/og](https://vercel.com/docs/concepts/functions/edge-functions/og-image-generation) -7. **Dark Mode**: For a better user experience at night - - - - - Demo - - -## Deploy Your Own - -Deploy your own version of this starter kit with Vercel. - -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?demo-title=Platforms+Starter+Kit&demo-description=A+template+for+site+builders+and+low-code+tools.&demo-url=https%3A%2F%2Fdemo.vercel.pub%2F&demo-image=%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F40JwjdHlPr0Z575MPYbxUA%2Fd5903afc68cb34569a3886293414c37c%2FOG_Image.png&project-name=Platforms+Starter+Kit&repository-name=platforms-starter-kit&repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fplatforms&from=templates&env=NEXT_PUBLIC_ROOT_DOMAIN%2CNEXTAUTH_SECRET%2CAUTH_GITHUB_ID%2CAUTH_GITHUB_SECRET%2CAUTH_BEARER_TOKEN%2CPROJECT_ID_VERCEL%2CTEAM_ID_VERCEL%2COPENAI_API_KEY&envDescription=These+environment+variables+are+required+to+run+this+application.&envLink=https%3A%2F%2Fgithub.com%2Fvercel%2Fplatforms%2Fblob%2Fmain%2F.env.example&stores=%5B%7B%22type%22%3A%22postgres%22%7D%5D) - -You can also [read the guide](https://vercel.com/guides/nextjs-multi-tenant-application) to learn how to develop your own version of this template. - -## What is a multi-tenant application? - -Multi-tenant applications serve multiple customers across different subdomains/custom domains with a single unified codebase. - -For example, our demo is a multi-tenant application: - -- Subdomain: [demo.vercel.pub](http://demo.vercel.pub) -- Custom domain: [platformize.co](http://platformize.co) (maps to [demo.vercel.pub](http://demo.vercel.pub)) -- Build your own: [app.vercel.pub](http://app.vercel.pub) - -Another example is [Hashnode](https://vercel.com/customers/hashnode), a popular blogging platform. Each writer has their own unique `.hashnode.dev` subdomain for their blog: - -- [eda.hashnode.dev](https://eda.hashnode.dev/) -- [katycodesstuff.hashnode.dev](https://katycodesstuff.hashnode.dev/) -- [akoskm.hashnode.dev](https://akoskm.hashnode.dev/) - -Users can also map custom domains to their `.hashnode.dev` subdomain: - -- [akoskm.com](https://akoskm.com/) → [akoskm.hashnode.dev](https://akoskm.hashnode.dev/) - -With the Platforms Starter Kit, you can offer unlimited custom domains at no extra cost to your customers as a premium feature, without having to worry about custom nameservers or configuring SSL certificates. - -## Examples of platforms - -Vercel customers like [Hashnode](https://vercel.com/customers/hashnode), [Super](https://super.so), and [Cal.com](https://cal.com) are building scalable platforms on top of Vercel and Next.js. There are multiple types of platforms you can build with this starter kit: - -### 1. Content creation platforms - -These are content-heavy platforms (blogs) with simple, standardized page layouts and route structure. - -> “With Vercel, we spend less time managing our infrastructure and more time delivering value to our users.” — Sandeep Panda, Co-founder, Hashnode - -1. [Hashnode](https://hashnode.com) -2. [Mirror.xyz](https://mirror.xyz/) -3. [Read.cv](https://read.cv/) - -### 2. Website & e-commerce store builders - -No-code site builders with customizable pages. - -By using Next.js and Vercel, [Super](https://super.so/) has fast, globally distributed websites with a no-code editor (Notion). Their customers get all the benefits of Next.js (like [Image Optimization](https://nextjs.org/docs/basic-features/image-optimization)) without touching any code. - -1. [Super.so](https://super.so) -2. [Typedream](https://typedream.com) -3. [Makeswift](https://www.makeswift.com/) - -### 3. B2B2C platforms - -Multi-tenant authentication, login, and access controls. - -With Vercel and Next.js, platforms like [Instatus](https://instatus.com) are able to create status pages that are _10x faster_ than competitors. - -1. [Instatus](https://instatus.com/) -2. [Cal.com](https://cal.com/) -3. [Dub](https://dub.sh/) - -## Built on open source - -This working demo site was built using the Platforms Starter Kit and: - -- [Next.js](https://nextjs.org/) as the React framework -- [Tailwind](https://tailwindcss.com/) for CSS styling -- [Prisma](https://prisma.io/) as the ORM for database access -- [Novel](https://novel.sh/) for the WYSIWYG editor -- [Vercel Postgres](https://vercel.com/storage/postgres) for the database -- [Vercel Blob](https://vercel.com/storage/blob) for image uploads -- [NextAuth.js](https://next-auth.js.org/) for authentication -- [Tremor](https://tremor.so/) for charts -- [Vercel](http://vercel.com/) for deployment - -## Contributing - -- [Start a discussion](https://github.com/vercel/platforms/discussions) with a question, piece of feedback, or idea you want to share with the team. -- [Open an issue](https://github.com/vercel/platforms/issues) if you believe you've encountered a bug with the starter kit. - -## Author - -- Steven Tey ([@steventey](https://twitter.com/steventey)) - -## License - -The MIT License. - ---- - - - - diff --git a/apps/write/app/[domain]/[slug]/not-found.tsx b/apps/write/app/[domain]/[slug]/not-found.tsx deleted file mode 100644 index 95cb2f1..0000000 --- a/apps/write/app/[domain]/[slug]/not-found.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { headers } from 'next/headers'; -import Image from 'next/image'; - -import { getSiteData } from '@/lib/fetchers'; - -export default async function NotFound() { - const headersList = headers(); - const domain = headersList - .get('host') - ?.replace('.localhost:3002', `.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`); - const data = await getSiteData(domain as string); - - return ( -
-

{data ? `${data.name}: ` : ''}404

- missing site -

- {data - ? data.message404 - : "Blimey! You've found a page that doesn't exist."} -

-
- ); -} diff --git a/apps/write/app/[domain]/[slug]/opengraph-image.tsx b/apps/write/app/[domain]/[slug]/opengraph-image.tsx deleted file mode 100644 index 3fc69f4..0000000 --- a/apps/write/app/[domain]/[slug]/opengraph-image.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* eslint-disable @next/next/no-img-element */ - -import { ImageResponse } from 'next/og'; -import { sql } from '@vercel/postgres'; - -import { truncate } from '@/lib/utils'; - -export const runtime = 'edge'; - -export default async function PostOG({ - params, -}: { - params: { domain: string; slug: string }; -}) { - const domain = decodeURIComponent(params.domain); - const slug = decodeURIComponent(params.slug); - - const subdomain = domain.endsWith(`.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`) - ? domain.replace(`.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`, '') - : null; - - const response = await sql` - SELECT post.title, post.description, post.image, "user".name as "authorName", "user".image as "authorImage" - FROM "Post" AS post - INNER JOIN "Site" AS site ON post."siteId" = site.id - INNER JOIN "User" AS "user" ON site."userId" = "user".id - WHERE - ( - site.subdomain = ${subdomain} - OR site."customDomain" = ${domain} - ) - AND post.slug = ${slug} - LIMIT 1; -`; - - const data = response.rows[0]; - - if (!data) { - return new Response('Not found', { status: 404 }); - } - - const clashData = await fetch( - new URL('@/styles/CalSans-SemiBold.otf', import.meta.url), - ).then((res) => res.arrayBuffer()); - - return new ImageResponse( - ( -
-
-

- {data.title} -

-

- {truncate(data.description, 120)} -

-
- {data.authorName} -

by {data.authorName}

-
- {data.title} -
-
- ), - { - width: 1200, - height: 600, - fonts: [ - { - name: 'Clash', - data: clashData, - }, - ], - emoji: 'blobmoji', - }, - ); -} diff --git a/apps/write/app/[domain]/[slug]/page.tsx b/apps/write/app/[domain]/[slug]/page.tsx deleted file mode 100644 index db4e902..0000000 --- a/apps/write/app/[domain]/[slug]/page.tsx +++ /dev/null @@ -1,180 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { notFound } from 'next/navigation'; - -import { getPostData, getSiteData } from '@/lib/fetchers'; -import prisma from '@/lib/prisma'; -import { placeholderBlurhash, toDateString } from '@/lib/utils'; -import BlogCard from '@/components/blog-card'; -import BlurImage from '@/components/blur-image'; -import MDX from '@/components/mdx'; - -export async function generateMetadata({ - params, -}: { - params: { domain: string; slug: string }; -}) { - const domain = decodeURIComponent(params.domain); - const slug = decodeURIComponent(params.slug); - - const [data, siteData] = await Promise.all([ - getPostData(domain, slug), - getSiteData(domain), - ]); - if (!data || !siteData) { - return null; - } - const { title, description } = data; - - return { - title, - description, - openGraph: { - title, - description, - }, - twitter: { - card: 'summary_large_image', - title, - description, - creator: '@vercel', - }, - // Optional: Set canonical URL to custom domain if it exists - // ...(params.domain.endsWith(`.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`) && - // siteData.customDomain && { - // alternates: { - // canonical: `https://${siteData.customDomain}/${params.slug}`, - // }, - // }), - }; -} - -export async function generateStaticParams() { - const allPosts = await prisma.post.findMany({ - select: { - slug: true, - site: { - select: { - subdomain: true, - customDomain: true, - }, - }, - }, - // feel free to remove this filter if you want to generate paths for all posts - where: { - site: { - subdomain: 'demo', - }, - }, - }); - - const allPaths = allPosts - .flatMap(({ site, slug }) => [ - site?.subdomain && { - domain: `${site.subdomain}.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`, - slug, - }, - site?.customDomain && { - domain: site.customDomain, - slug, - }, - ]) - .filter(Boolean); - - return allPaths; -} - -export default async function SitePostPage({ - params, -}: { - params: { domain: string; slug: string }; -}) { - const domain = decodeURIComponent(params.domain); - const slug = decodeURIComponent(params.slug); - const data = await getPostData(domain, slug); - - if (!data) { - notFound(); - } - - return ( - <> -
-
-

- {toDateString(data.createdAt)} -

-

- {data.title} -

-

- {data.description} -

-
- -
-
- {data.site?.user?.image ? ( - - ) : ( -
- ? -
- )} -
-
- by {data.site?.user?.name} -
-
-
-
-
- -
- - - - {data.adjacentPosts.length > 0 && ( -
- - )} - {data.adjacentPosts && ( -
- {data.adjacentPosts.map((data: any, index: number) => ( - - ))} -
- )} - - ); -} diff --git a/apps/write/app/[domain]/layout.tsx b/apps/write/app/[domain]/layout.tsx deleted file mode 100644 index 72faf7d..0000000 --- a/apps/write/app/[domain]/layout.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { ReactNode } from 'react'; -import { Metadata } from 'next'; -import Image from 'next/image'; -import Link from 'next/link'; -import { notFound, redirect } from 'next/navigation'; - -import { getSiteData } from '@/lib/fetchers'; -import CTA from '@/components/cta'; -import ReportAbuse from '@/components/report-abuse'; -import { fontMapper } from '@/styles/fonts'; - -export async function generateMetadata({ - params, -}: { - params: { domain: string }; -}): Promise { - const domain = decodeURIComponent(params.domain); - const data = await getSiteData(domain); - if (!data) { - return null; - } - const { - name: title, - description, - image, - logo, - } = data as { - name: string; - description: string; - image: string; - logo: string; - }; - - return { - title, - description, - openGraph: { - title, - description, - images: [image], - }, - twitter: { - card: 'summary_large_image', - title, - description, - images: [image], - creator: '@vercel', - }, - icons: [logo], - metadataBase: new URL(`https://${domain}`), - // Optional: Set canonical URL to custom domain if it exists - // ...(params.domain.endsWith(`.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`) && - // data.customDomain && { - // alternates: { - // canonical: `https://${data.customDomain}`, - // }, - // }), - }; -} - -export default async function SiteLayout({ - params, - children, -}: { - params: { domain: string }; - children: ReactNode; -}) { - const domain = decodeURIComponent(params.domain); - const data = await getSiteData(domain); - - if (!data) { - notFound(); - } - - // Optional: Redirect to custom domain if it exists - if ( - domain.endsWith(`.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`) && - data.customDomain && - process.env.REDIRECT_TO_CUSTOM_DOMAIN_IF_EXISTS === 'true' - ) { - return redirect(`https://${data.customDomain}`); - } - - return ( -
-
-
- -
- {data.name -
- - {data.name} - - -
-
- -
{children}
- - {domain == `demo.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}` || - domain == 'platformize.co' ? ( - - ) : ( - - )} -
- ); -} diff --git a/apps/write/app/[domain]/page.tsx b/apps/write/app/[domain]/page.tsx deleted file mode 100644 index dbe0535..0000000 --- a/apps/write/app/[domain]/page.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import Image from 'next/image'; -import Link from 'next/link'; -import { notFound } from 'next/navigation'; - -import { getPostsForSite, getSiteData } from '@/lib/fetchers'; -import prisma from '@/lib/prisma'; -import { placeholderBlurhash, toDateString } from '@/lib/utils'; -import BlogCard from '@/components/blog-card'; -import BlurImage from '@/components/blur-image'; - -export async function generateStaticParams() { - const allSites = await prisma.site.findMany({ - select: { - subdomain: true, - customDomain: true, - }, - // feel free to remove this filter if you want to generate paths for all sites - where: { - subdomain: 'demo', - }, - }); - - const allPaths = allSites - .flatMap(({ subdomain, customDomain }) => [ - subdomain && { - domain: `${subdomain}.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`, - }, - customDomain && { - domain: customDomain, - }, - ]) - .filter(Boolean); - - return allPaths; -} - -export default async function SiteHomePage({ - params, -}: { - params: { domain: string }; -}) { - const domain = decodeURIComponent(params.domain); - const [data, posts] = await Promise.all([ - getSiteData(domain), - getPostsForSite(domain), - ]); - - if (!data) { - notFound(); - } - - return ( - <> -
- {posts.length > 0 ? ( -
- -
- -
-
-

- {posts[0].title} -

-

- {posts[0].description} -

-
-
- {data.user?.image ? ( - - ) : ( -
- ? -
- )} -
-

- {data.user?.name} -

-
-

- {toDateString(posts[0].createdAt)} -

-
-
- -
- ) : ( -
- missing post - missing post -

- No posts yet. -

-
- )} -
- - {posts.length > 1 && ( -
-

- More stories -

-
- {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */} - {posts.slice(1).map((metadata: any, index: number) => ( - - ))} -
-
- )} - - ); -} diff --git a/apps/write/app/api/auth/[...nextauth]/route.ts b/apps/write/app/api/auth/[...nextauth]/route.ts deleted file mode 100644 index 5d00646..0000000 --- a/apps/write/app/api/auth/[...nextauth]/route.ts +++ /dev/null @@ -1,7 +0,0 @@ -import NextAuth from 'next-auth'; - -import { authOptions } from '@/lib/auth'; - -const handler = NextAuth(authOptions); - -export { handler as GET, handler as POST }; diff --git a/apps/write/app/api/domain/[slug]/verify/route.ts b/apps/write/app/api/domain/[slug]/verify/route.ts deleted file mode 100644 index a2fa76d..0000000 --- a/apps/write/app/api/domain/[slug]/verify/route.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { NextResponse } from 'next/server'; - -import { - getConfigResponse, - getDomainResponse, - verifyDomain, -} from '@/lib/domains'; -import { DomainVerificationStatusProps } from '@/lib/types'; - -export async function GET( - _req: Request, - { params }: { params: { slug: string } }, -) { - const domain = decodeURIComponent(params.slug); - let status: DomainVerificationStatusProps = 'Valid Configuration'; - - const [domainJson, configJson] = await Promise.all([ - getDomainResponse(domain), - getConfigResponse(domain), - ]); - - if (domainJson?.error?.code === 'not_found') { - // domain not found on Vercel project - status = 'Domain Not Found'; - - // unknown error - } else if (domainJson.error) { - status = 'Unknown Error'; - - // if domain is not verified, we try to verify now - } else if (!domainJson.verified) { - status = 'Pending Verification'; - const verificationJson = await verifyDomain(domain); - - // domain was just verified - if (verificationJson && verificationJson.verified) { - status = 'Valid Configuration'; - } - } else if (configJson.misconfigured) { - status = 'Invalid Configuration'; - } else { - status = 'Valid Configuration'; - } - - return NextResponse.json({ - status, - domainJson, - }); -} diff --git a/apps/write/app/api/generate/route.ts b/apps/write/app/api/generate/route.ts deleted file mode 100644 index 401c90e..0000000 --- a/apps/write/app/api/generate/route.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Ratelimit } from '@upstash/ratelimit'; -import { kv } from '@vercel/kv'; -import { OpenAIStream, StreamingTextResponse } from 'ai'; -import { Configuration, OpenAIApi } from 'openai-edge'; - -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): Promise { - if ( - process.env.NODE_ENV != 'development' && - process.env.KV_REST_API_URL && - process.env.KV_REST_API_TOKEN - ) { - const ip = req.headers.get('x-forwarded-for'); - const ratelimit = new Ratelimit({ - redis: kv, - limiter: Ratelimit.slidingWindow(50, '1 d'), - }); - - const { success, limit, reset, remaining } = await ratelimit.limit( - `platforms_ratelimit_${ip}`, - ); - - if (!success) { - return new Response('You have reached your request limit for the day.', { - status: 429, - headers: { - 'X-RateLimit-Limit': limit.toString(), - 'X-RateLimit-Remaining': remaining.toString(), - 'X-RateLimit-Reset': reset.toString(), - }, - }); - } - } - - let { prompt: content } = await req.json(); - - // remove trailing slash, - // slice the content from the end to prioritize later characters - content = content.replace(/\/$/, '').slice(-5000); - - const response = await openai.createChatCompletion({ - model: 'gpt-3.5-turbo', - messages: [ - { - role: 'system', - content: - 'You are an expert ghost writer. Extend the existing text by adding new insights or continuing the narrative. ' + - 'Do not restate or paraphrase the existing content. ' + - 'You must mimic the tone and style of the provided content to maintain consistency.' + - 'Your response should be concise, NOT EXCEEDING 300 characters, yet it should form complete and coherent sentences. ', - }, - { - role: 'user', - content, - }, - ], - temperature: 0.7, - top_p: 0.9, - frequency_penalty: 0.5, - presence_penalty: 0.6, - stream: true, - n: 1, - }); - - // Convert the response into a friendly text-stream - const stream = OpenAIStream(response); - - // Respond with the stream - return new StreamingTextResponse(stream); -} diff --git a/apps/write/app/api/upload/route.ts b/apps/write/app/api/upload/route.ts deleted file mode 100644 index 5fda6c1..0000000 --- a/apps/write/app/api/upload/route.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NextResponse } from 'next/server'; -import { put } from '@vercel/blob'; -import { nanoid } from 'nanoid'; - -export const runtime = 'edge'; - -export async function POST(req: Request) { - if (!process.env.BLOB_READ_WRITE_TOKEN) { - return new Response( - "Missing BLOB_READ_WRITE_TOKEN. Don't forget to add that to your .env file.", - { - status: 401, - }, - ); - } - - const file = req.body || ''; - const contentType = req.headers.get('content-type') || 'text/plain'; - const filename = `${nanoid()}.${contentType.split('/')[1]}`; - const blob = await put(filename, file, { - contentType, - access: 'public', - }); - - return NextResponse.json(blob); -} diff --git a/apps/write/app/app/(auth)/layout.tsx b/apps/write/app/app/(auth)/layout.tsx deleted file mode 100644 index 9b2a419..0000000 --- a/apps/write/app/app/(auth)/layout.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { ReactNode } from 'react'; -import { Metadata } from 'next'; - -export const metadata: Metadata = { - title: 'Login | Platforms Starter Kit', -}; - -export default function AuthLayout({ children }: { children: ReactNode }) { - return ( -
- {children} -
- ); -} diff --git a/apps/write/app/app/(auth)/login/login-button.tsx b/apps/write/app/app/(auth)/login/login-button.tsx deleted file mode 100644 index ba09bc5..0000000 --- a/apps/write/app/app/(auth)/login/login-button.tsx +++ /dev/null @@ -1,54 +0,0 @@ -'use client'; - -import { useEffect, useState } from 'react'; -import { useSearchParams } from 'next/navigation'; -import { signIn } from 'next-auth/react'; -import { toast } from 'sonner'; - -import LoadingDots from '@/components/icons/loading-dots'; - -export default function LoginButton() { - const [loading, setLoading] = useState(false); - - // Get error message added by next/auth in URL. - const searchParams = useSearchParams(); - const error = searchParams?.get('error'); - - useEffect(() => { - const errorMessage = Array.isArray(error) ? error.pop() : error; - errorMessage && toast.error(errorMessage); - }, [error]); - - return ( - - ); -} diff --git a/apps/write/app/app/(auth)/login/page.tsx b/apps/write/app/app/(auth)/login/page.tsx deleted file mode 100644 index 52ab206..0000000 --- a/apps/write/app/app/(auth)/login/page.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Suspense } from 'react'; -import { Logo } from '@ui/index'; - -import LoginButton from './login-button'; - -export default function LoginPage() { - return ( -
- -

- AI Assisted Blog -

-

- Set-up your own personal blog in 3 minutes or less.{' '} - - Read the announcement. - -

- -
- - } - > - - -
-
- ); -} diff --git a/apps/write/app/app/(dashboard)/layout.tsx b/apps/write/app/app/(dashboard)/layout.tsx deleted file mode 100644 index 5316e1a..0000000 --- a/apps/write/app/app/(dashboard)/layout.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { ReactNode, Suspense } from 'react'; - -import Nav from '@/components/nav'; -import Profile from '@/components/profile'; - -export default function DashboardLayout({ children }: { children: ReactNode }) { - return ( -
-
}> - - - -
{children}
-
- ); -} diff --git a/apps/write/app/app/(dashboard)/loading.tsx b/apps/write/app/app/(dashboard)/loading.tsx deleted file mode 100644 index 72a11b9..0000000 --- a/apps/write/app/app/(dashboard)/loading.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import LoadingDots from '@/components/icons/loading-dots'; - -export default function Loading() { - return ( - <> -
-
- -
- - ); -} diff --git a/apps/write/app/app/(dashboard)/page.tsx b/apps/write/app/app/(dashboard)/page.tsx deleted file mode 100644 index d78ce6d..0000000 --- a/apps/write/app/app/(dashboard)/page.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { Suspense } from 'react'; - -import OverviewSitesCTA from '@/components/overview-sites-cta'; -import OverviewStats from '@/components/overview-stats'; -import PlaceholderCard from '@/components/placeholder-card'; -import Posts from '@/components/posts'; -import Sites from '@/components/sites'; - -export default function Overview() { - return ( -
-
-

- Overview -

- -
- -
-
-

- Top Sites -

- - - -
- - {Array.from({ length: 4 }).map((_, i) => ( - - ))} -
- } - > - - -
- -
-

- Recent Posts -

- - {Array.from({ length: 8 }).map((_, i) => ( - - ))} -
- } - > - - -
-
- ); -} diff --git a/apps/write/app/app/(dashboard)/post/[id]/layout.tsx b/apps/write/app/app/(dashboard)/post/[id]/layout.tsx deleted file mode 100644 index 5e3c96d..0000000 --- a/apps/write/app/app/(dashboard)/post/[id]/layout.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { ReactNode } from 'react'; - -export default function PostLayout({ children }: { children: ReactNode }) { - return
{children}
; -} diff --git a/apps/write/app/app/(dashboard)/post/[id]/loading.tsx b/apps/write/app/app/(dashboard)/post/[id]/loading.tsx deleted file mode 100644 index 54fba86..0000000 --- a/apps/write/app/app/(dashboard)/post/[id]/loading.tsx +++ /dev/null @@ -1,10 +0,0 @@ -// a bunch of loading divs - -export default function Loading() { - return ( - <> -
-
- - ); -} diff --git a/apps/write/app/app/(dashboard)/post/[id]/not-found.tsx b/apps/write/app/app/(dashboard)/post/[id]/not-found.tsx deleted file mode 100644 index 827ed58..0000000 --- a/apps/write/app/app/(dashboard)/post/[id]/not-found.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import Image from 'next/image'; - -export default function NotFoundPost() { - return ( -
-

404

- missing site -

- Post does not exist, or you do not have permission to edit it -

-
- ); -} diff --git a/apps/write/app/app/(dashboard)/post/[id]/page.tsx b/apps/write/app/app/(dashboard)/post/[id]/page.tsx deleted file mode 100644 index faa975c..0000000 --- a/apps/write/app/app/(dashboard)/post/[id]/page.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { notFound, redirect } from 'next/navigation'; - -import { getSession } from '@/lib/auth'; -import prisma from '@/lib/prisma'; -import Editor from '@/components/editor'; - -export default async function PostPage({ params }: { params: { id: string } }) { - const session = await getSession(); - if (!session) { - redirect('/login'); - } - const data = await prisma.post.findUnique({ - where: { - id: decodeURIComponent(params.id), - }, - include: { - site: { - select: { - subdomain: true, - }, - }, - }, - }); - if (!data || data.userId !== session.user.id) { - notFound(); - } - - return ; -} diff --git a/apps/write/app/app/(dashboard)/post/[id]/settings/page.tsx b/apps/write/app/app/(dashboard)/post/[id]/settings/page.tsx deleted file mode 100644 index eb33515..0000000 --- a/apps/write/app/app/(dashboard)/post/[id]/settings/page.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { notFound, redirect } from 'next/navigation'; - -import { updatePostMetadata } from '@/lib/actions'; -import { getSession } from '@/lib/auth'; -import prisma from '@/lib/prisma'; -import Form from '@/components/form'; -import DeletePostForm from '@/components/form/delete-post-form'; - -export default async function PostSettings({ - params, -}: { - params: { id: string }; -}) { - const session = await getSession(); - if (!session) { - redirect('/login'); - } - const data = await prisma.post.findUnique({ - where: { - id: decodeURIComponent(params.id), - }, - }); - if (!data || data.userId !== session.user.id) { - notFound(); - } - return ( -
-
-

- Post Settings -

-
- - - - -
-
- ); -} diff --git a/apps/write/app/app/(dashboard)/settings/page.tsx b/apps/write/app/app/(dashboard)/settings/page.tsx deleted file mode 100644 index f122637..0000000 --- a/apps/write/app/app/(dashboard)/settings/page.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { redirect } from 'next/navigation'; - -import { editUser } from '@/lib/actions'; -import { getSession } from '@/lib/auth'; -import Form from '@/components/form'; - -export default async function SettingsPage() { - const session = await getSession(); - if (!session) { - redirect('/login'); - } - return ( -
-
-

- Settings -

- - -
-
- ); -} diff --git a/apps/write/app/app/(dashboard)/site/[id]/analytics/page.tsx b/apps/write/app/app/(dashboard)/site/[id]/analytics/page.tsx deleted file mode 100644 index dac0b69..0000000 --- a/apps/write/app/app/(dashboard)/site/[id]/analytics/page.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { notFound, redirect } from 'next/navigation'; - -import { getSession } from '@/lib/auth'; -import prisma from '@/lib/prisma'; -import AnalyticsMockup from '@/components/analytics'; - -export default async function SiteAnalytics({ - params, -}: { - params: { id: string }; -}) { - const session = await getSession(); - if (!session) { - redirect('/login'); - } - const data = await prisma.site.findUnique({ - where: { - id: decodeURIComponent(params.id), - }, - }); - if (!data || data.userId !== session.user.id) { - notFound(); - } - - const url = `${data.subdomain}.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`; - - return ( - <> -
-
-

- Analytics for {data.name} -

- - {url} ↗ - -
-
- - - ); -} diff --git a/apps/write/app/app/(dashboard)/site/[id]/layout.tsx b/apps/write/app/app/(dashboard)/site/[id]/layout.tsx deleted file mode 100644 index 0db55ff..0000000 --- a/apps/write/app/app/(dashboard)/site/[id]/layout.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { ReactNode } from 'react'; - -export default function SiteLayout({ children }: { children: ReactNode }) { - return ( -
-
{children}
-
- ); -} diff --git a/apps/write/app/app/(dashboard)/site/[id]/loading.tsx b/apps/write/app/app/(dashboard)/site/[id]/loading.tsx deleted file mode 100644 index 82459ee..0000000 --- a/apps/write/app/app/(dashboard)/site/[id]/loading.tsx +++ /dev/null @@ -1,16 +0,0 @@ -// a bunch of loading divs - -import PlaceholderCard from '@/components/placeholder-card'; - -export default function Loading() { - return ( - <> -
-
- {Array.from({ length: 8 }).map((_, i) => ( - - ))} -
- - ); -} diff --git a/apps/write/app/app/(dashboard)/site/[id]/not-found.tsx b/apps/write/app/app/(dashboard)/site/[id]/not-found.tsx deleted file mode 100644 index e9cc9c0..0000000 --- a/apps/write/app/app/(dashboard)/site/[id]/not-found.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import Image from 'next/image'; - -export default function NotFoundSite() { - return ( -
-

404

- missing site - missing site -

- Site does not exist, or you do not have permission to view it -

-
- ); -} diff --git a/apps/write/app/app/(dashboard)/site/[id]/page.tsx b/apps/write/app/app/(dashboard)/site/[id]/page.tsx deleted file mode 100644 index 64ad7ba..0000000 --- a/apps/write/app/app/(dashboard)/site/[id]/page.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { notFound, redirect } from 'next/navigation'; - -import { getSession } from '@/lib/auth'; -import prisma from '@/lib/prisma'; -import CreatePostButton from '@/components/create-post-button'; -import Posts from '@/components/posts'; - -export default async function SitePosts({ - params, -}: { - params: { id: string }; -}) { - const session = await getSession(); - if (!session) { - redirect('/login'); - } - const data = await prisma.site.findUnique({ - where: { - id: decodeURIComponent(params.id), - }, - }); - - if (!data || data.userId !== session.user.id) { - notFound(); - } - - const url = `${data.subdomain}.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`; - - return ( - <> -
-
-

- All Posts for {data.name} -

- - {url} ↗ - -
- -
- - - ); -} diff --git a/apps/write/app/app/(dashboard)/site/[id]/settings/appearance/page.tsx b/apps/write/app/app/(dashboard)/site/[id]/settings/appearance/page.tsx deleted file mode 100644 index 1b26de2..0000000 --- a/apps/write/app/app/(dashboard)/site/[id]/settings/appearance/page.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { updateSite } from '@/lib/actions'; -import prisma from '@/lib/prisma'; -import Form from '@/components/form'; - -export default async function SiteSettingsAppearance({ - params, -}: { - params: { id: string }; -}) { - const data = await prisma.site.findUnique({ - where: { - id: decodeURIComponent(params.id), - }, - }); - - return ( -
- - - - -
- ); -} diff --git a/apps/write/app/app/(dashboard)/site/[id]/settings/domains/page.tsx b/apps/write/app/app/(dashboard)/site/[id]/settings/domains/page.tsx deleted file mode 100644 index 837d3c3..0000000 --- a/apps/write/app/app/(dashboard)/site/[id]/settings/domains/page.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { updateSite } from '@/lib/actions'; -import prisma from '@/lib/prisma'; -import Form from '@/components/form'; - -export default async function SiteSettingsDomains({ - params, -}: { - params: { id: string }; -}) { - const data = await prisma.site.findUnique({ - where: { - id: decodeURIComponent(params.id), - }, - }); - - return ( -
- - -
- ); -} diff --git a/apps/write/app/app/(dashboard)/site/[id]/settings/layout.tsx b/apps/write/app/app/(dashboard)/site/[id]/settings/layout.tsx deleted file mode 100644 index 4f55c70..0000000 --- a/apps/write/app/app/(dashboard)/site/[id]/settings/layout.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { ReactNode } from 'react'; -import { notFound, redirect } from 'next/navigation'; - -import { getSession } from '@/lib/auth'; -import prisma from '@/lib/prisma'; - -import SiteSettingsNav from './nav'; - -export default async function SiteAnalyticsLayout({ - params, - children, -}: { - params: { id: string }; - children: ReactNode; -}) { - const session = await getSession(); - if (!session) { - redirect('/login'); - } - const data = await prisma.site.findUnique({ - where: { - id: decodeURIComponent(params.id), - }, - }); - - if (!data || data.userId !== session.user.id) { - notFound(); - } - - const url = `${data.subdomain}.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`; - - return ( - <> -
-

- Settings for {data.name} -

- - {url} ↗ - -
- - {children} - - ); -} diff --git a/apps/write/app/app/(dashboard)/site/[id]/settings/nav.tsx b/apps/write/app/app/(dashboard)/site/[id]/settings/nav.tsx deleted file mode 100644 index f3d10a5..0000000 --- a/apps/write/app/app/(dashboard)/site/[id]/settings/nav.tsx +++ /dev/null @@ -1,48 +0,0 @@ -'use client'; - -import Link from 'next/link'; -import { useParams, useSelectedLayoutSegment } from 'next/navigation'; -import { cn } from '@ui/index'; - -export default function SiteSettingsNav() { - const { id } = useParams() as { id?: string }; - const segment = useSelectedLayoutSegment(); - - const navItems = [ - { - name: 'General', - href: `/site/${id}/settings`, - segment: null, - }, - { - name: 'Domains', - href: `/site/${id}/settings/domains`, - segment: 'domains', - }, - { - name: 'Appearance', - href: `/site/${id}/settings/appearance`, - segment: 'appearance', - }, - ]; - - return ( -
- {navItems.map((item) => ( - - {item.name} - - ))} -
- ); -} diff --git a/apps/write/app/app/(dashboard)/site/[id]/settings/page.tsx b/apps/write/app/app/(dashboard)/site/[id]/settings/page.tsx deleted file mode 100644 index ffb1b2e..0000000 --- a/apps/write/app/app/(dashboard)/site/[id]/settings/page.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { updateSite } from '@/lib/actions'; -import prisma from '@/lib/prisma'; -import Form from '@/components/form'; -import DeleteSiteForm from '@/components/form/delete-site-form'; - -export default async function SiteSettingsIndex({ - params, -}: { - params: { id: string }; -}) { - const data = await prisma.site.findUnique({ - where: { - id: decodeURIComponent(params.id), - }, - }); - - return ( -
- - - - - -
- ); -} diff --git a/apps/write/app/app/(dashboard)/sites/page.tsx b/apps/write/app/app/(dashboard)/sites/page.tsx deleted file mode 100644 index f953fcd..0000000 --- a/apps/write/app/app/(dashboard)/sites/page.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Suspense } from 'react'; - -import CreateSiteButton from '@/components/create-site-button'; -import CreateSiteModal from '@/components/modal/create-site'; -import PlaceholderCard from '@/components/placeholder-card'; -import Sites from '@/components/sites'; - -export default function AllSites({ params }: { params: { id: string } }) { - return ( -
-
-
-

- All Sites -

- - - -
- - {Array.from({ length: 8 }).map((_, i) => ( - - ))} -
- } - > - {/* @ts-expect-error Server Component */} - - -
-
- ); -} diff --git a/apps/write/app/favicon.ico b/apps/write/app/favicon.ico deleted file mode 100644 index ba6d10185624837358f34218db5b16ab2416dcd7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 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 - Platforms on Vercel -

- Edit this page on{' '} - - app/home/page.tsx - -

-
- ); -} diff --git a/apps/write/app/layout.tsx b/apps/write/app/layout.tsx deleted file mode 100644 index e5c2502..0000000 --- a/apps/write/app/layout.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import 'ui/styles/globals.css'; - -import { ReactNode } from 'react'; -import { Metadata } from 'next'; -import { cn } from '@ui/index'; -import { Analytics } from '@vercel/analytics/react'; - -import { cal, inter } from '@/styles/fonts'; - -import { Providers } from './providers'; - -const title = 'Write by And Voila'; -const description = - 'The Platforms Starter Kit is a full-stack Next.js app with multi-tenancy and custom domain support. Built with Next.js App Router, Vercel Postgres and the Vercel Domains API.'; -const image = '/images/open-graph.gif'; - -export const metadata: Metadata = { - title, - description, - icons: ['https://vercel.pub/favicon.ico'], - openGraph: { - title, - description, - images: [image], - }, - twitter: { - card: 'summary_large_image', - title, - description, - images: [image], - creator: '@vercel', - }, - metadataBase: new URL('https://vercel.pub'), -}; - -export default function RootLayout({ children }: { children: ReactNode }) { - return ( - - - - {children} - - - - - ); -} diff --git a/apps/write/app/providers.tsx b/apps/write/app/providers.tsx deleted file mode 100644 index 178a2f1..0000000 --- a/apps/write/app/providers.tsx +++ /dev/null @@ -1,17 +0,0 @@ -'use client'; - -import { ReactNode } from 'react'; -import { SessionProvider } from 'next-auth/react'; -import { Toaster } from 'sonner'; - -import { ModalProvider } from '@/components/modal/provider'; - -export function Providers({ children }: { children: ReactNode }) { - return ( - - - - {children} - - ); -} diff --git a/apps/write/app/robots.ts b/apps/write/app/robots.ts deleted file mode 100644 index d345d01..0000000 --- a/apps/write/app/robots.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { MetadataRoute } from 'next'; - -export default function robots(): MetadataRoute.Robots { - return { - rules: { - userAgent: '*', - disallow: '/', - }, - }; -} diff --git a/apps/write/app/sitemap.ts b/apps/write/app/sitemap.ts deleted file mode 100644 index e246478..0000000 --- a/apps/write/app/sitemap.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { headers } from 'next/headers'; - -import { getPostsForSite } from '@/lib/fetchers'; - -export default async function Sitemap() { - const headersList = headers(); - const domain = - headersList - .get('host') - ?.replace('.localhost:3002', `.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`) ?? - 'vercel.pub'; - - const posts = await getPostsForSite(domain); - - return [ - { - url: `https://${domain}`, - lastModified: new Date(), - }, - ...posts.map(({ slug }) => ({ - url: `https://${domain}/${slug}`, - lastModified: new Date(), - })), - ]; -} diff --git a/apps/write/components/analytics.tsx b/apps/write/components/analytics.tsx deleted file mode 100644 index 5715458..0000000 --- a/apps/write/components/analytics.tsx +++ /dev/null @@ -1,153 +0,0 @@ -'use client'; - -import Image from 'next/image'; -import { - AreaChart, - BarList, - Bold, - Card, - Flex, - Grid, - Text, - Title, -} from '@tremor/react'; - -const chartdata = [ - { - date: 'Jan 23', - Visitors: 2890, - }, - { - date: 'Feb 23', - Visitors: 2756, - }, - { - date: 'Mar 23', - Visitors: 3322, - }, - { - date: 'Apr 23', - Visitors: 3470, - }, - { - date: 'May 23', - Visitors: 3475, - }, - { - date: 'Jun 23', - Visitors: 3129, - }, -]; - -const pages = [ - { name: '/platforms-starter-kit', value: '1,230' }, - { name: '/vercel-is-now-bercel', value: 751 }, - { name: '/nextjs-conf', value: 471 }, - { name: '/150m-series-d', value: 280 }, - { name: '/about', value: 78 }, -]; - -const referrers = [ - { name: 't.co', value: 453 }, - { name: 'vercel.com', value: 351 }, - { name: 'linkedin.com', value: 271 }, - { name: 'google.com', value: 191 }, - { - name: 'news.ycombinator.com', - value: 71, - }, -]; - -const countries = [ - { name: 'United States of America', value: 789, code: 'US' }, - { name: 'India', value: 676, code: 'IN' }, - { name: 'Germany', value: 564, code: 'DE' }, - { name: 'United Kingdom', value: 234, code: 'GB' }, - { name: 'Spain', value: 191, code: 'ES' }, -]; - -const categories = [ - { - title: 'Top Pages', - subtitle: 'Page', - data: pages, - }, - { - title: 'Top Referrers', - subtitle: 'Source', - data: referrers, - }, - { - title: 'Countries', - subtitle: 'Country', - data: countries, - }, -]; - -export default function AnalyticsMockup() { - return ( -
- - Visitors - - Intl.NumberFormat('us').format(number).toString() - } - /> - - - {categories.map(({ title, subtitle, data }) => ( - - {title} - - - {subtitle} - - - Visitors - - - ({ - name, - value, - icon: () => { - if (title === 'Top Referrers') { - return ( - {name} - ); - } else if (title === 'Countries') { - return ( - {code} - ); - } else { - return null; - } - }, - }))} - className="mt-2" - /> - - ))} - -
- ); -} diff --git a/apps/write/components/blog-card.tsx b/apps/write/components/blog-card.tsx deleted file mode 100644 index 9dd38e0..0000000 --- a/apps/write/components/blog-card.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import Link from 'next/link'; - -import { placeholderBlurhash, toDateString } from '@/lib/utils'; - -import BlurImage from './blur-image'; -import type { Post } from '.prisma/client'; - -interface BlogCardProps { - data: Pick< - Post, - 'slug' | 'image' | 'imageBlurhash' | 'title' | 'description' | 'createdAt' - >; -} - -export default function BlogCard({ data }: BlogCardProps) { - return ( - -
- -
-

- {data.title} -

-

- {data.description} -

-

- Published {toDateString(data.createdAt)} -

-
-
- - ); -} diff --git a/apps/write/components/blur-image.tsx b/apps/write/components/blur-image.tsx deleted file mode 100644 index a46289b..0000000 --- a/apps/write/components/blur-image.tsx +++ /dev/null @@ -1,24 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import Image, { ImageProps } from 'next/image'; -import { cn } from '@ui/index'; - -interface BlurImageProps extends ImageProps {} - -export default function BlurImage(props: BlurImageProps) { - const [isLoading, setLoading] = useState(true); - - return ( - {props.alt} setLoading(false)} - /> - ); -} diff --git a/apps/write/components/create-post-button.tsx b/apps/write/components/create-post-button.tsx deleted file mode 100644 index 3ed1888..0000000 --- a/apps/write/components/create-post-button.tsx +++ /dev/null @@ -1,39 +0,0 @@ -'use client'; - -import { useTransition } from 'react'; -import { useParams, useRouter } from 'next/navigation'; -import { cn } from '@ui/index'; -import va from '@vercel/analytics'; - -import { createPost } from '@/lib/actions'; -import LoadingDots from '@/components/icons/loading-dots'; - -export default function CreatePostButton() { - const router = useRouter(); - const { id } = useParams() as { id: string }; - const [isPending, startTransition] = useTransition(); - - return ( - - ); -} diff --git a/apps/write/components/create-site-button.tsx b/apps/write/components/create-site-button.tsx deleted file mode 100644 index 0a50ed5..0000000 --- a/apps/write/components/create-site-button.tsx +++ /dev/null @@ -1,21 +0,0 @@ -'use client'; - -import { ReactNode } from 'react'; - -import { useModal } from '@/components/modal/provider'; - -export default function CreateSiteButton({ - children, -}: { - children: ReactNode; -}) { - const modal = useModal(); - return ( - - ); -} diff --git a/apps/write/components/cta.tsx b/apps/write/components/cta.tsx deleted file mode 100644 index 7008b3a..0000000 --- a/apps/write/components/cta.tsx +++ /dev/null @@ -1,80 +0,0 @@ -'use client'; - -import { useState } from 'react'; - -export default function CTA() { - const [closeCTA, setCloseCTA] = useState(false); - return ( -
- -
-

- Platforms Starter Kit Demo -

-

- This is a demo site showcasing how to build a multi-tenant application - with{' '} - - custom domain - {' '} - support. -

-
- -
- ); -} diff --git a/apps/write/components/editor.tsx b/apps/write/components/editor.tsx deleted file mode 100644 index 19bba9e..0000000 --- a/apps/write/components/editor.tsx +++ /dev/null @@ -1,142 +0,0 @@ -'use client'; - -import { useEffect, useState, useTransition } from 'react'; -import { cn, LucideReact } from '@ui/index'; -import { Editor as NovelEditor } from 'novel'; -import TextareaAutosize from 'react-textarea-autosize'; -import { toast } from 'sonner'; - -import { updatePost, updatePostMetadata } from '@/lib/actions'; - -import LoadingDots from './icons/loading-dots'; -import { Post } from '.prisma/client'; - -type PostWithSite = Post & { site: { subdomain: string | null } | null }; - -export default function Editor({ post }: { post: PostWithSite }) { - const [isPendingSaving, startTransitionSaving] = useTransition(); - const [isPendingPublishing, startTransitionPublishing] = useTransition(); - const [data, setData] = useState(post); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [hydrated, setHydrated] = useState(false); - - const url = process.env.NEXT_PUBLIC_VERCEL_ENV - ? `https://${data.site?.subdomain}.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}/${data.slug}` - : `http://${data.site?.subdomain}.localhost:3002/${data.slug}`; - - // listen to CMD + S and override the default behavior - useEffect(() => { - const onKeyDown = (e: KeyboardEvent) => { - if (e.metaKey && e.key === 's') { - e.preventDefault(); - startTransitionSaving(() => { - (async () => { - await updatePost(data); - })(); - }); - } - }; - document.addEventListener('keydown', onKeyDown); - return () => { - document.removeEventListener('keydown', onKeyDown); - }; - }, [data, startTransitionSaving]); - - return ( -
-
- {data.published && ( - - - - )} -
- {isPendingSaving ? 'Saving...' : 'Saved'} -
- -
-
- setData({ ...data, title: e.target.value })} - className="dark:placeholder-text-600 font-cal border-none px-0 text-3xl placeholder:text-stone-400 focus:outline-none focus:ring-0 dark:bg-black dark:text-white" - /> - setData({ ...data, description: e.target.value })} - className="dark:placeholder-text-600 w-full resize-none border-none px-0 placeholder:text-stone-400 focus:outline-none focus:ring-0 dark:bg-black dark:text-white" - /> -
- { - setData((prev) => ({ - ...prev, - content: editor?.storage.markdown.getMarkdown(), - })); - }} - onDebouncedUpdate={() => { - if ( - data.title === post.title && - data.description === post.description && - data.content === post.content - ) { - return; - } - startTransitionSaving(() => { - (async () => { - await updatePost(data); - })(); - }); - }} - /> -
- ); -} diff --git a/apps/write/components/form/delete-post-form.tsx b/apps/write/components/form/delete-post-form.tsx deleted file mode 100644 index f0596ff..0000000 --- a/apps/write/components/form/delete-post-form.tsx +++ /dev/null @@ -1,76 +0,0 @@ -'use client'; - -import { useParams, useRouter } from 'next/navigation'; -import { cn } from '@ui/index'; -import va from '@vercel/analytics'; -import { useFormStatus } from 'react-dom'; -import { toast } from 'sonner'; - -import { deletePost } from '@/lib/actions'; -import LoadingDots from '@/components/icons/loading-dots'; - -export default function DeletePostForm({ postName }: { postName: string }) { - const { id } = useParams() as { id: string }; - const router = useRouter(); - return ( - - window.confirm('Are you sure you want to delete your post?') && - deletePost(data, id, 'delete').then((res) => { - if (res.error) { - toast.error(res.error); - } else { - va.track('Deleted Post'); - router.refresh(); - router.push(`/site/${res.siteId}`); - toast.success('Successfully deleted post!'); - } - }) - } - className="rounded-lg border border-red-600 bg-white dark:bg-black" - > -
-

Delete Post

-

- Deletes your post permanently. Type in the name of your post{' '} - {postName} to confirm. -

- - -
- -
-

- This action is irreversible. Please proceed with caution. -

-
- -
-
- - ); -} - -function FormButton() { - const { pending } = useFormStatus(); - return ( - - ); -} diff --git a/apps/write/components/form/delete-site-form.tsx b/apps/write/components/form/delete-site-form.tsx deleted file mode 100644 index 5534f54..0000000 --- a/apps/write/components/form/delete-site-form.tsx +++ /dev/null @@ -1,78 +0,0 @@ -'use client'; - -import { useParams, useRouter } from 'next/navigation'; -import { cn } from '@ui/index'; -import va from '@vercel/analytics'; -import { useFormStatus } from 'react-dom'; -import { toast } from 'sonner'; - -import { deleteSite } from '@/lib/actions'; -import LoadingDots from '@/components/icons/loading-dots'; - -export default function DeleteSiteForm({ siteName }: { siteName: string }) { - const { id } = useParams() as { id: string }; - const router = useRouter(); - return ( -
- window.confirm('Are you sure you want to delete your site?') && - deleteSite(data, id, 'delete') - .then(async (res) => { - if (res.error) { - toast.error(res.error); - } else { - va.track('Deleted Site'); - router.refresh(); - router.push('/sites'); - toast.success('Successfully deleted site!'); - } - }) - .catch((err: Error) => toast.error(err.message)) - } - className="rounded-lg border border-red-600 bg-white dark:bg-black" - > -
-

Delete Site

-

- Deletes your site and all posts associated with it. Type in the name - of your site {siteName} to confirm. -

- - -
- -
-

- This action is irreversible. Please proceed with caution. -

-
- -
-
-
- ); -} - -function FormButton() { - const { pending } = useFormStatus(); - return ( - - ); -} diff --git a/apps/write/components/form/domain-configuration.tsx b/apps/write/components/form/domain-configuration.tsx deleted file mode 100644 index ebba2df..0000000 --- a/apps/write/components/form/domain-configuration.tsx +++ /dev/null @@ -1,171 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { cn, LucideReact } from '@ui/index'; - -import { getSubdomain } from '@/lib/domains'; - -import { useDomainStatus } from './use-domain-status'; - -export const InlineSnippet = ({ - className, - children, -}: { - className?: string; - children: string; -}) => { - return ( - - {children} - - ); -}; -export default function DomainConfiguration({ domain }: { domain: string }) { - const [recordType, setRecordType] = useState<'A' | 'CNAME'>('A'); - - const { status, domainJson } = useDomainStatus({ domain }); - - if (!status || status === 'Valid Configuration' || !domainJson) return null; - - const subdomain = getSubdomain(domainJson.name, domainJson.apexName); - - const txtVerification = - (status === 'Pending Verification' && - // eslint-disable-next-line @typescript-eslint/no-explicit-any - domainJson.verification.find((x: any) => x.type === 'TXT')) || - null; - - return ( -
-
- {status === 'Pending Verification' ? ( - - ) : ( - - )} -

{status}

-
- {txtVerification ? ( - <> -

- 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. -

- - ) : status === 'Unknown Error' ? ( -

- {domainJson.error.message} -

- ) : ( - <> -
- - -
-
-

- 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.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`} -

-
-
-

TTL

-

86400

-
-
-

- Note: for TTL, if 86400 is not - available, set the highest value possible. Also, domain - propagation can take up to an hour. -

-
- - )} -
- ); -} diff --git a/apps/write/components/form/domain-status.tsx b/apps/write/components/form/domain-status.tsx deleted file mode 100644 index a88fe1c..0000000 --- a/apps/write/components/form/domain-status.tsx +++ /dev/null @@ -1,32 +0,0 @@ -'use client'; - -import { LucideReact } from '@ui/index'; - -import LoadingSpinner from './loading-spinner'; -import { useDomainStatus } from './use-domain-status'; - -export default function DomainStatus({ domain }: { domain: string }) { - const { status, loading } = useDomainStatus({ domain }); - - return loading ? ( - - ) : status === 'Valid Configuration' ? ( - - ) : status === 'Pending Verification' ? ( - - ) : ( - - ); -} diff --git a/apps/write/components/form/index.tsx b/apps/write/components/form/index.tsx deleted file mode 100644 index 6a835e8..0000000 --- a/apps/write/components/form/index.tsx +++ /dev/null @@ -1,154 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -'use client'; - -import { useParams, useRouter } from 'next/navigation'; -import { cn } from '@ui/index'; -import va from '@vercel/analytics'; -import { useSession } from 'next-auth/react'; -import { useFormStatus } from 'react-dom'; -import { toast } from 'sonner'; - -import LoadingDots from '@/components/icons/loading-dots'; - -import DomainConfiguration from './domain-configuration'; -import DomainStatus from './domain-status'; -import Uploader from './uploader'; - -export default function Form({ - title, - description, - helpText, - inputAttrs, - handleSubmit, -}: { - title: string; - description: string; - helpText: string; - inputAttrs: { - name: string; - type: string; - defaultValue: string; - placeholder?: string; - maxLength?: number; - pattern?: string; - }; - handleSubmit: any; -}) { - const { id } = useParams() as { id?: string }; - const router = useRouter(); - const { update } = useSession(); - return ( -
{ - if ( - inputAttrs.name === 'customDomain' && - inputAttrs.defaultValue && - data.get('customDomain') !== inputAttrs.defaultValue && - !confirm('Are you sure you want to change your custom domain?') - ) { - return; - } - handleSubmit(data, id, inputAttrs.name).then(async (res: any) => { - if (res.error) { - toast.error(res.error); - } else { - va.track(`Updated ${inputAttrs.name}`, id ? { id } : {}); - if (id) { - router.refresh(); - } else { - await update(); - router.refresh(); - } - toast.success(`Successfully updated ${inputAttrs.name}!`); - } - }); - }} - className="rounded-lg border border-stone-200 bg-white dark:border-stone-700 dark:bg-black" - > -
-

{title}

-

- {description} -

- {inputAttrs.name === 'image' || inputAttrs.name === 'logo' ? ( - - ) : inputAttrs.name === 'font' ? ( -
- -
- ) : inputAttrs.name === 'subdomain' ? ( -
- -
- {process.env.NEXT_PUBLIC_ROOT_DOMAIN} -
-
- ) : inputAttrs.name === 'customDomain' ? ( -
- - {inputAttrs.defaultValue && ( -
- -
- )} -
- ) : inputAttrs.name === 'description' ? ( -