feat(home): CMS-managed birthday card background patterns
Admins can now upload custom pattern images for green and orange pricing cards via Home Page → День народження → Паттерн зелених/оранжевої карток. Falls back to static /images/figma/card-pattern-*.png if not set. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7371462eab
commit
83283ed43c
3 changed files with 50 additions and 7 deletions
15
migrations/0014_birthday_patterns.sql
Normal file
15
migrations/0014_birthday_patterns.sql
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
-- 0014: birthday card background pattern media fields on home_page global
|
||||
|
||||
-- Live table: two nullable FK columns referencing media
|
||||
ALTER TABLE "home_page"
|
||||
ADD COLUMN IF NOT EXISTS "birthday_intro_pattern_green_id" integer
|
||||
REFERENCES "media"("id") ON DELETE SET NULL ON UPDATE NO ACTION,
|
||||
ADD COLUMN IF NOT EXISTS "birthday_intro_pattern_orange_id" integer
|
||||
REFERENCES "media"("id") ON DELETE SET NULL ON UPDATE NO ACTION;
|
||||
|
||||
-- Versions table: same columns on the version snapshot table
|
||||
ALTER TABLE "_home_page_v"
|
||||
ADD COLUMN IF NOT EXISTS "version_birthday_intro_pattern_green_id" integer
|
||||
REFERENCES "media"("id") ON DELETE SET NULL ON UPDATE NO ACTION,
|
||||
ADD COLUMN IF NOT EXISTS "version_birthday_intro_pattern_orange_id" integer
|
||||
REFERENCES "media"("id") ON DELETE SET NULL ON UPDATE NO ACTION;
|
||||
|
|
@ -73,15 +73,20 @@ export default async function HomePage() {
|
|||
src={home?.video?.src ?? undefined}
|
||||
/>
|
||||
)
|
||||
case 'birthday':
|
||||
case 'birthday': {
|
||||
const pg = home?.birthdayIntro?.patternGreen
|
||||
const po = home?.birthdayIntro?.patternOrange
|
||||
return (
|
||||
<BirthdayPricing
|
||||
key={key}
|
||||
packages={birthdayPackages.length > 0 ? birthdayPackages : undefined}
|
||||
title={home?.sectionTitles?.birthday ?? undefined}
|
||||
intro={home?.birthdayIntro?.text ?? undefined}
|
||||
patternGreen={pg && typeof pg === 'object' ? (pg.url ?? undefined) : (pg ?? undefined)}
|
||||
patternOrange={po && typeof po === 'object' ? (po.url ?? undefined) : (po ?? undefined)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
case 'gallery':
|
||||
return (
|
||||
<Gallery
|
||||
|
|
|
|||
|
|
@ -3,17 +3,27 @@ import Link from 'next/link'
|
|||
import type { BirthdayPackageCMS } from '@/types/globals'
|
||||
|
||||
const IMG_CHECK = '/images/figma/check-mark.webp'
|
||||
const IMG_PATTERN_GREEN = '/images/figma/card-pattern-light.png' // lighter green on transparent → visible on dark green bg
|
||||
const IMG_PATTERN_ORANGE = '/images/figma/card-pattern-dark.png' // dark green on transparent → visible on orange bg
|
||||
const DEFAULT_PATTERN_GREEN = '/images/figma/card-pattern-light.png'
|
||||
const DEFAULT_PATTERN_ORANGE = '/images/figma/card-pattern-dark.png'
|
||||
|
||||
interface BirthdayPricingProps {
|
||||
packages?: BirthdayPackageCMS[]
|
||||
title?: string
|
||||
intro?: string
|
||||
patternGreen?: string
|
||||
patternOrange?: string
|
||||
}
|
||||
|
||||
export function BirthdayPricing({ packages, title, intro }: BirthdayPricingProps) {
|
||||
export function BirthdayPricing({
|
||||
packages,
|
||||
title,
|
||||
intro,
|
||||
patternGreen,
|
||||
patternOrange,
|
||||
}: BirthdayPricingProps) {
|
||||
const activePackages = packages ?? []
|
||||
const imgPatternGreen = patternGreen ?? DEFAULT_PATTERN_GREEN
|
||||
const imgPatternOrange = patternOrange ?? DEFAULT_PATTERN_ORANGE
|
||||
|
||||
return (
|
||||
<section className="bg-[#f1fbeb] py-[20px] md:py-[60px] lg:py-[40px]">
|
||||
|
|
@ -37,7 +47,12 @@ export function BirthdayPricing({ packages, title, intro }: BirthdayPricingProps
|
|||
{/* Figma 1:231 — non-featured wrappers get pt-84 so featured card stands 84px taller */}
|
||||
<div className="flex flex-col justify-center gap-10 lg:flex-row lg:items-start lg:gap-[59px]">
|
||||
{activePackages.map((pkg) => (
|
||||
<PricingCard key={pkg.id} pkg={pkg} />
|
||||
<PricingCard
|
||||
key={pkg.id}
|
||||
pkg={pkg}
|
||||
imgPatternGreen={imgPatternGreen}
|
||||
imgPatternOrange={imgPatternOrange}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -49,7 +64,15 @@ function formatPrice(price: number, currency: string | null | undefined): string
|
|||
return `${price.toLocaleString('uk-UA')} ${currency ?? '₴'}`
|
||||
}
|
||||
|
||||
function PricingCard({ pkg }: { pkg: BirthdayPackageCMS }) {
|
||||
function PricingCard({
|
||||
pkg,
|
||||
imgPatternGreen,
|
||||
imgPatternOrange,
|
||||
}: {
|
||||
pkg: BirthdayPackageCMS
|
||||
imgPatternGreen: string
|
||||
imgPatternOrange: string
|
||||
}) {
|
||||
const {
|
||||
name,
|
||||
price,
|
||||
|
|
@ -116,7 +139,7 @@ function PricingCard({ pkg }: { pkg: BirthdayPackageCMS }) {
|
|||
aria-hidden="true"
|
||||
className="pointer-events-none absolute inset-0"
|
||||
style={{
|
||||
backgroundImage: `url('${featured ? IMG_PATTERN_ORANGE : IMG_PATTERN_GREEN}')`,
|
||||
backgroundImage: `url('${featured ? imgPatternOrange : imgPatternGreen}')`,
|
||||
backgroundSize: '100% auto',
|
||||
backgroundRepeat: 'repeat',
|
||||
opacity: 0.2,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue