mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-07-17 09:18:26 +00:00
feat(website): add education offers and FAQ page JSON-LD builders
This commit is contained in:
21
apps/website/src/config/pricing.test.ts
Normal file
21
apps/website/src/config/pricing.test.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { educationOffers, pricingOffers } from './pricing'
|
||||
|
||||
describe('educationOffers', () => {
|
||||
it('marks up the discounted education prices, not the list prices', () => {
|
||||
const edu = educationOffers('en')
|
||||
const list = pricingOffers('en')
|
||||
|
||||
// Same plans in the same order, so a per-tier comparison is meaningful.
|
||||
expect(edu.map((offer) => offer.name)).toEqual(
|
||||
list.map((offer) => offer.name)
|
||||
)
|
||||
expect(edu.length).toBeGreaterThan(0)
|
||||
|
||||
edu.forEach((offer, index) => {
|
||||
expect(offer.price).toMatch(/^\d+(\.\d+)?$/)
|
||||
expect(Number(offer.price)).toBeLessThan(Number(list[index].price))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -6,23 +6,27 @@ interface PricingTier {
|
||||
slug: string
|
||||
labelKey: TranslationKey
|
||||
priceKey: TranslationKey
|
||||
eduPriceKey: TranslationKey
|
||||
}
|
||||
|
||||
const tiers: PricingTier[] = [
|
||||
{
|
||||
slug: 'standard',
|
||||
labelKey: 'pricing.plan.standard.label',
|
||||
priceKey: 'pricing.plan.standard.price'
|
||||
priceKey: 'pricing.plan.standard.price',
|
||||
eduPriceKey: 'pricing.plan.standard.eduPrice'
|
||||
},
|
||||
{
|
||||
slug: 'creator',
|
||||
labelKey: 'pricing.plan.creator.label',
|
||||
priceKey: 'pricing.plan.creator.price'
|
||||
priceKey: 'pricing.plan.creator.price',
|
||||
eduPriceKey: 'pricing.plan.creator.eduPrice'
|
||||
},
|
||||
{
|
||||
slug: 'pro',
|
||||
labelKey: 'pricing.plan.pro.label',
|
||||
priceKey: 'pricing.plan.pro.price'
|
||||
priceKey: 'pricing.plan.pro.price',
|
||||
eduPriceKey: 'pricing.plan.pro.eduPrice'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -32,9 +36,13 @@ export interface PricingOffer {
|
||||
url: string
|
||||
}
|
||||
|
||||
export function pricingOffers(locale: Locale): PricingOffer[] {
|
||||
// Shared offer builder: the pricing page marks up list prices, the education
|
||||
// page marks up the discounted student/educator prices. Only plain USD amounts
|
||||
// become offers so the JSON-LD Offer always carries a concrete numeric price.
|
||||
function offersFrom(locale: Locale, education: boolean): PricingOffer[] {
|
||||
return tiers.flatMap((tier) => {
|
||||
const display = t(tier.priceKey, locale).trim()
|
||||
const priceKey = education ? tier.eduPriceKey : tier.priceKey
|
||||
const display = t(priceKey, locale).trim()
|
||||
const match = /^\$(\d+(?:\.\d+)?)$/.exec(display)
|
||||
if (!match) {
|
||||
console.warn(
|
||||
@@ -51,3 +59,11 @@ export function pricingOffers(locale: Locale): PricingOffer[] {
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
export function pricingOffers(locale: Locale): PricingOffer[] {
|
||||
return offersFrom(locale, false)
|
||||
}
|
||||
|
||||
export function educationOffers(locale: Locale): PricingOffer[] {
|
||||
return offersFrom(locale, true)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
comfyUiApplicationNode,
|
||||
comfyUiSoftwareId,
|
||||
comfyUiSourceCodeNode,
|
||||
faqPageNode,
|
||||
itemListNode,
|
||||
jsonLdId,
|
||||
organizationId,
|
||||
@@ -201,6 +202,25 @@ describe('productNode', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('faqPageNode', () => {
|
||||
const pageUrl = 'https://comfy.org/education/'
|
||||
|
||||
it('maps each FAQ to a Question with an accepted answer', () => {
|
||||
const node = faqPageNode(pageUrl, [
|
||||
{ question: 'What discount do I get?', answer: 'Up to 25% off.' }
|
||||
])
|
||||
expect(node['@type']).toBe('FAQPage')
|
||||
expect(node['@id']).toBe(jsonLdId(pageUrl, 'faq'))
|
||||
const questions = node.mainEntity as Record<string, unknown>[]
|
||||
expect(questions).toHaveLength(1)
|
||||
expect(questions[0]).toMatchObject({
|
||||
'@type': 'Question',
|
||||
name: 'What discount do I get?',
|
||||
acceptedAnswer: { '@type': 'Answer', text: 'Up to 25% off.' }
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('comfyUiSourceCodeNode', () => {
|
||||
it('links the source code to the ComfyUI application via targetProduct', () => {
|
||||
const node = comfyUiSourceCodeNode(siteUrl)
|
||||
|
||||
@@ -347,6 +347,26 @@ export function productNode(input: ProductInput): JsonLdNode {
|
||||
}
|
||||
}
|
||||
|
||||
export interface FaqEntry {
|
||||
question: string
|
||||
answer: string
|
||||
}
|
||||
|
||||
export function faqPageNode(pageUrl: string, faqs: FaqEntry[]): JsonLdNode {
|
||||
return {
|
||||
'@type': 'FAQPage',
|
||||
'@id': jsonLdId(pageUrl, 'faq'),
|
||||
mainEntity: faqs.map((faq) => ({
|
||||
'@type': 'Question',
|
||||
name: faq.question,
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: faq.answer
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
export interface PageGraphInput {
|
||||
url: string
|
||||
name: string
|
||||
|
||||
Reference in New Issue
Block a user