diff --git a/apps/website/astro.config.ts b/apps/website/astro.config.ts index e57aef5cd66..6c542b3a6b4 100644 --- a/apps/website/astro.config.ts +++ b/apps/website/astro.config.ts @@ -3,6 +3,23 @@ import sitemap from '@astrojs/sitemap' import vue from '@astrojs/vue' import tailwindcss from '@tailwindcss/vite' +const LOCALES = ['en', 'zh-CN'] as const +const DEFAULT_LOCALE = 'en' +const PAYMENT_STATUSES = ['success', 'failed'] as const +const LOCALE_PREFIXES = LOCALES.map((locale) => + locale === DEFAULT_LOCALE ? '' : `/${locale}` +) +const SITEMAP_EXCLUDED_PATHNAMES = new Set( + LOCALE_PREFIXES.flatMap((prefix) => + PAYMENT_STATUSES.map((status) => `${prefix}/payment/${status}`) + ) +) + +function isExcludedFromSitemap(page: string): boolean { + const pathname = new URL(page).pathname.replace(/\/$/, '') + return SITEMAP_EXCLUDED_PATHNAMES.has(pathname) +} + export default defineConfig({ site: 'https://comfy.org', output: 'static', @@ -17,7 +34,12 @@ export default defineConfig({ assets: '_website' }, devToolbar: { enabled: !process.env.NO_TOOLBAR }, - integrations: [vue(), sitemap()], + integrations: [ + vue(), + sitemap({ + filter: (page) => !isExcludedFromSitemap(page) + }) + ], vite: { plugins: [tailwindcss()], server: { @@ -27,8 +49,8 @@ export default defineConfig({ } }, i18n: { - locales: ['en', 'zh-CN'], - defaultLocale: 'en', + locales: [...LOCALES], + defaultLocale: DEFAULT_LOCALE, routing: { prefixDefaultLocale: false } diff --git a/apps/website/e2e/payment.spec.ts b/apps/website/e2e/payment.spec.ts new file mode 100644 index 00000000000..3ea28fab245 --- /dev/null +++ b/apps/website/e2e/payment.spec.ts @@ -0,0 +1,115 @@ +import type { Page } from '@playwright/test' +import { expect } from '@playwright/test' + +import { externalLinks } from '../src/config/routes' +import { test } from './fixtures/blockExternalMedia' + +const CLOUD_URL = externalLinks.cloud +const PLATFORM_USAGE_URL = externalLinks.platformUsage +const SUPPORT_URL = externalLinks.support +const DOCS_SUBSCRIPTION_URL = externalLinks.docsSubscription + +async function expectNoIndex(page: Page) { + await expect(page.locator('meta[name="robots"]')).toHaveAttribute( + 'content', + 'noindex, nofollow' + ) +} + +test.describe('Payment success page @smoke', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/payment/success') + }) + + test('has correct title and is noindex', async ({ page }) => { + await expect(page).toHaveTitle('Payment Successful — Comfy') + await expectNoIndex(page) + }) + + test('shows success heading and subtitle', async ({ page }) => { + await expect( + page.getByRole('heading', { name: /Payment successful/i, level: 1 }) + ).toBeVisible() + await expect(page.getByText(/Thanks for your purchase/i)).toBeVisible() + }) + + test('primary CTA links to Comfy Cloud', async ({ page }) => { + const cta = page.getByRole('link', { name: /CONTINUE TO COMFY CLOUD/i }) + await expect(cta).toBeVisible() + await expect(cta).toHaveAttribute('href', CLOUD_URL) + }) + + test('secondary CTA links to platform usage & payments page', async ({ + page + }) => { + const cta = page.getByRole('link', { name: /VIEW USAGE & PAYMENTS/i }) + await expect(cta).toBeVisible() + await expect(cta).toHaveAttribute('href', PLATFORM_USAGE_URL) + }) +}) + +test.describe('Payment failed page @smoke', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/payment/failed') + }) + + test('has correct title and is noindex', async ({ page }) => { + await expect(page).toHaveTitle('Payment Failed — Comfy') + await expectNoIndex(page) + }) + + test('shows failure heading and subtitle', async ({ page }) => { + await expect( + page.getByRole('heading', { + name: /Payment was not completed/i, + level: 1 + }) + ).toBeVisible() + await expect(page.getByText(/payment didn't go through/i)).toBeVisible() + }) + + test('primary CTA links to support help center', async ({ page }) => { + const cta = page.getByRole('link', { name: /CONTACT SUPPORT/i }) + await expect(cta).toBeVisible() + await expect(cta).toHaveAttribute('href', SUPPORT_URL) + }) + + test('secondary CTA links to subscription docs', async ({ page }) => { + const cta = page.getByRole('link', { name: /READ SUBSCRIPTION DOCS/i }) + await expect(cta).toBeVisible() + await expect(cta).toHaveAttribute('href', DOCS_SUBSCRIPTION_URL) + }) +}) + +test.describe('Payment pages zh-CN @smoke', () => { + test('zh-CN success page renders and links correctly', async ({ page }) => { + await page.goto('/zh-CN/payment/success') + await expect(page).toHaveTitle('支付成功 — Comfy') + await expectNoIndex(page) + await expect( + page.getByRole('heading', { name: '支付成功', level: 1 }) + ).toBeVisible() + await expect( + page.getByRole('link', { name: '前往 COMFY CLOUD' }) + ).toHaveAttribute('href', CLOUD_URL) + await expect( + page.getByRole('link', { name: '查看用量与支付' }) + ).toHaveAttribute('href', PLATFORM_USAGE_URL) + }) + + test('zh-CN failed page renders and links correctly', async ({ page }) => { + await page.goto('/zh-CN/payment/failed') + await expect(page).toHaveTitle('支付失败 — Comfy') + await expectNoIndex(page) + await expect( + page.getByRole('heading', { name: '支付未完成', level: 1 }) + ).toBeVisible() + await expect(page.getByRole('link', { name: '联系支持' })).toHaveAttribute( + 'href', + SUPPORT_URL + ) + await expect( + page.getByRole('link', { name: '查看订阅文档' }) + ).toHaveAttribute('href', DOCS_SUBSCRIPTION_URL) + }) +}) diff --git a/apps/website/public/robots.txt b/apps/website/public/robots.txt index 5e6114b55ef..4f2ed61dee9 100644 --- a/apps/website/public/robots.txt +++ b/apps/website/public/robots.txt @@ -29,5 +29,6 @@ Allow: / Disallow: /_astro/ Disallow: /_website/ Disallow: /_vercel/ +Disallow: /payment/ Sitemap: https://comfy.org/sitemap-index.xml diff --git a/apps/website/src/components/payment/PaymentStatusSection.vue b/apps/website/src/components/payment/PaymentStatusSection.vue new file mode 100644 index 00000000000..0d8aec3f1ea --- /dev/null +++ b/apps/website/src/components/payment/PaymentStatusSection.vue @@ -0,0 +1,101 @@ + + + diff --git a/apps/website/src/config/routes.ts b/apps/website/src/config/routes.ts index 8fa008e3b97..128e410c915 100644 --- a/apps/website/src/config/routes.ts +++ b/apps/website/src/config/routes.ts @@ -33,8 +33,10 @@ export const externalLinks = { discord: 'https://discord.com/invite/comfyorg', docs: 'https://docs.comfy.org/', docsApi: 'https://docs.comfy.org/api-reference/cloud', + docsSubscription: 'https://docs.comfy.org/support/subscription/subscribing', github: 'https://github.com/Comfy-Org/ComfyUI', platform: 'https://platform.comfy.org', + platformUsage: 'https://platform.comfy.org/profile/usage', support: 'https://support.comfy.org/hc/en-us', workflows: 'https://comfy.org/workflows', youtube: 'https://www.youtube.com/@ComfyOrg' diff --git a/apps/website/src/i18n/translations.ts b/apps/website/src/i18n/translations.ts index 06342ace683..f93f3f003d4 100644 --- a/apps/website/src/i18n/translations.ts +++ b/apps/website/src/i18n/translations.ts @@ -3592,6 +3592,49 @@ const translations = { 'customers.feedback.role3': { en: 'Head of AI at Creative Studios', 'zh-CN': 'Creative Studios AI 负责人' + }, + + // Payment status pages + 'payment.success.label': { + en: 'PAYMENT', + 'zh-CN': '支付' + }, + 'payment.success.title': { + en: 'Payment successful', + 'zh-CN': '支付成功' + }, + 'payment.success.subtitle': { + en: "Thanks for your purchase. Your account has been credited and you're ready to keep building.", + 'zh-CN': '感谢您的购买。您的账户已充值完成,可以继续创作了。' + }, + 'payment.success.primaryCta': { + en: 'CONTINUE TO COMFY CLOUD', + 'zh-CN': '前往 COMFY CLOUD' + }, + 'payment.success.secondaryCta': { + en: 'VIEW USAGE & PAYMENTS', + 'zh-CN': '查看用量与支付' + }, + 'payment.failed.label': { + en: 'PAYMENT', + 'zh-CN': '支付' + }, + 'payment.failed.title': { + en: 'Payment was not completed', + 'zh-CN': '支付未完成' + }, + 'payment.failed.subtitle': { + en: "Your payment didn't go through and you have not been charged. Reach out to support or read the subscription docs if you need help.", + 'zh-CN': + '您的支付未能完成,未发生扣款。如需帮助,请联系支持或查阅订阅文档。' + }, + 'payment.failed.primaryCta': { + en: 'CONTACT SUPPORT', + 'zh-CN': '联系支持' + }, + 'payment.failed.secondaryCta': { + en: 'READ SUBSCRIPTION DOCS', + 'zh-CN': '查看订阅文档' } } as const satisfies Record> diff --git a/apps/website/src/pages/payment/failed.astro b/apps/website/src/pages/payment/failed.astro new file mode 100644 index 00000000000..2acbf18cfee --- /dev/null +++ b/apps/website/src/pages/payment/failed.astro @@ -0,0 +1,12 @@ +--- +import BaseLayout from '../../layouts/BaseLayout.astro' +import PaymentStatusSection from '../../components/payment/PaymentStatusSection.vue' +--- + + + + diff --git a/apps/website/src/pages/payment/success.astro b/apps/website/src/pages/payment/success.astro new file mode 100644 index 00000000000..07a3c7f7250 --- /dev/null +++ b/apps/website/src/pages/payment/success.astro @@ -0,0 +1,12 @@ +--- +import BaseLayout from '../../layouts/BaseLayout.astro' +import PaymentStatusSection from '../../components/payment/PaymentStatusSection.vue' +--- + + + + diff --git a/apps/website/src/pages/zh-CN/payment/failed.astro b/apps/website/src/pages/zh-CN/payment/failed.astro new file mode 100644 index 00000000000..85a07ae04b6 --- /dev/null +++ b/apps/website/src/pages/zh-CN/payment/failed.astro @@ -0,0 +1,8 @@ +--- +import BaseLayout from '../../../layouts/BaseLayout.astro' +import PaymentStatusSection from '../../../components/payment/PaymentStatusSection.vue' +--- + + + + diff --git a/apps/website/src/pages/zh-CN/payment/success.astro b/apps/website/src/pages/zh-CN/payment/success.astro new file mode 100644 index 00000000000..1e48c69e717 --- /dev/null +++ b/apps/website/src/pages/zh-CN/payment/success.astro @@ -0,0 +1,8 @@ +--- +import BaseLayout from '../../../layouts/BaseLayout.astro' +import PaymentStatusSection from '../../../components/payment/PaymentStatusSection.vue' +--- + + + +