From b199f8999831113dda5e0f7b717c715e51ef5dcf Mon Sep 17 00:00:00 2001 From: pkupuk Date: Fri, 27 Feb 2026 20:05:43 +0800 Subject: [PATCH] Integrate CMS with Marketing Solutions page Links the marketing solutions frontend page to the Payload CMS Pages collection via the new API library. Removes legacy static portfolio routes and components to consolidate marketing content. Enhances the Header and Footer Astro components with improved responsive styling. --- .../src/app/(frontend)/posts/[slug]/page.tsx | 22 +- .../backend/src/app/(frontend)/posts/page.tsx | 3 +- .../posts/page/[pageNumber]/page.tsx | 2 + .../backend/src/blocks/ServicesList/config.ts | 86 + apps/backend/src/collections/Pages/index.ts | 3 +- apps/backend/src/payload-types.ts | 57 +- apps/frontend/package.json | 5 +- apps/frontend/src/components/Footer.astro | 93 +- apps/frontend/src/components/Header.astro | 51 +- .../src/components/PortfolioCard.astro | 115 +- .../src/components/SectionHeader.astro | 27 + .../src/components/blog/ArticleCard.astro | 126 +- .../src/components/blog/CategoryFilter.astro | 73 +- .../src/components/blog/RelatedPosts.astro | 127 +- .../src/lib/api/marketing-solution.ts | 208 + apps/frontend/src/lib/sanitize.ts | 40 + .../src/pages/marketing-solutions.astro | 26 +- .../src/pages/web-portfolios/[slug].astro | 345 -- .../src/pages/web-portfolios/index.astro | 244 - .../src/pages/webdesign-profolio/[slug].astro | 442 -- apps/frontend/src/sections/AboutHero.astro | 70 +- .../src/sections/EnvironmentSlider.astro | 54 +- apps/frontend/src/sections/ServicesList.astro | 401 +- .../frontend/src/sections/SolutionsHero.astro | 116 +- apps/frontend/src/sections/TeamsHero.astro | 81 +- apps/frontend/src/styles/theme.css | 6 +- bun.lock | 4438 +++++++++++++++++ package.json | 7 +- pnpm-lock.yaml | 1641 ++++-- 29 files changed, 6475 insertions(+), 2434 deletions(-) create mode 100644 apps/backend/src/blocks/ServicesList/config.ts create mode 100644 apps/frontend/src/components/SectionHeader.astro create mode 100644 apps/frontend/src/lib/api/marketing-solution.ts create mode 100644 apps/frontend/src/lib/sanitize.ts delete mode 100644 apps/frontend/src/pages/web-portfolios/[slug].astro delete mode 100644 apps/frontend/src/pages/web-portfolios/index.astro delete mode 100644 apps/frontend/src/pages/webdesign-profolio/[slug].astro create mode 100644 bun.lock diff --git a/apps/backend/src/app/(frontend)/posts/[slug]/page.tsx b/apps/backend/src/app/(frontend)/posts/[slug]/page.tsx index 04c3344..d508cee 100644 --- a/apps/backend/src/app/(frontend)/posts/[slug]/page.tsx +++ b/apps/backend/src/app/(frontend)/posts/[slug]/page.tsx @@ -16,25 +16,9 @@ import { generateMeta } from '@/utilities/generateMeta' import PageClient from './page.client' import { LivePreviewListener } from '@/components/LivePreviewListener' -export async function generateStaticParams() { - const payload = await getPayload({ config: configPromise }) - const posts = await payload.find({ - collection: 'posts', - draft: false, - limit: 1000, - overrideAccess: false, - pagination: false, - select: { - slug: true, - }, - }) - - const params = posts.docs.map(({ slug }) => { - return { slug } - }) - - return params -} +// Use dynamic rendering instead of static generation +// This avoids the need for database connection during build time +export const dynamic = 'force-dynamic' type Args = { params: Promise<{ diff --git a/apps/backend/src/app/(frontend)/posts/page.tsx b/apps/backend/src/app/(frontend)/posts/page.tsx index 5cf1389..4007d93 100644 --- a/apps/backend/src/app/(frontend)/posts/page.tsx +++ b/apps/backend/src/app/(frontend)/posts/page.tsx @@ -8,7 +8,8 @@ import { getPayload } from 'payload' import React from 'react' import PageClient from './page.client' -export const dynamic = 'force-static' +// Use dynamic rendering to avoid database connection during build +export const dynamic = 'force-dynamic' export const revalidate = 600 export default async function Page() { diff --git a/apps/backend/src/app/(frontend)/posts/page/[pageNumber]/page.tsx b/apps/backend/src/app/(frontend)/posts/page/[pageNumber]/page.tsx index e55131d..3aec936 100644 --- a/apps/backend/src/app/(frontend)/posts/page/[pageNumber]/page.tsx +++ b/apps/backend/src/app/(frontend)/posts/page/[pageNumber]/page.tsx @@ -9,6 +9,8 @@ import React from 'react' import PageClient from './page.client' import { notFound } from 'next/navigation' +// Use dynamic rendering to avoid database connection during build +export const dynamic = 'force-dynamic' export const revalidate = 600 type Args = { diff --git a/apps/backend/src/blocks/ServicesList/config.ts b/apps/backend/src/blocks/ServicesList/config.ts new file mode 100644 index 0000000..6fc4ccd --- /dev/null +++ b/apps/backend/src/blocks/ServicesList/config.ts @@ -0,0 +1,86 @@ +import type { Block } from 'payload' + +export const ServicesList: Block = { + slug: 'servicesList', + interfaceName: 'ServicesListBlock', + fields: [ + { + name: 'services', + type: 'array', + required: true, + admin: { + initCollapsed: true, + }, + fields: [ + { + name: 'title', + type: 'text', + required: true, + }, + { + name: 'description', + type: 'textarea', + required: true, + }, + { + name: 'category', + type: 'text', + required: true, + }, + { + name: 'iconType', + type: 'select', + defaultValue: 'preset', + options: [ + { label: 'Preset Icon', value: 'preset' }, + { label: 'Custom SVG', value: 'svg' }, + { label: 'Upload Image', value: 'upload' }, + ], + }, + { + name: 'icon', + type: 'text', + admin: { + description: 'Preset icon name: facebook, google, ads, news, youtube, forum, web, video', + condition: (_, siblingData) => siblingData?.iconType === 'preset', + }, + }, + { + name: 'iconSvg', + type: 'textarea', + admin: { + description: 'Paste SVG code directly (e.g., ...)', + condition: (_, siblingData) => siblingData?.iconType === 'svg', + }, + }, + { + name: 'iconImage', + type: 'upload', + relationTo: 'media', + admin: { + description: 'Upload an icon image (SVG, PNG)', + condition: (_, siblingData) => siblingData?.iconType === 'upload', + }, + }, + { + name: 'isHot', + type: 'checkbox', + defaultValue: false, + }, + { + name: 'image', + type: 'upload', + relationTo: 'media', + }, + { + name: 'link', + type: 'text', + }, + ], + }, + ], + labels: { + singular: 'Services List', + plural: 'Services Lists', + }, +} diff --git a/apps/backend/src/collections/Pages/index.ts b/apps/backend/src/collections/Pages/index.ts index a320dc8..0b7e4e0 100644 --- a/apps/backend/src/collections/Pages/index.ts +++ b/apps/backend/src/collections/Pages/index.ts @@ -8,6 +8,7 @@ import { Archive } from '../../blocks/ArchiveBlock/config' import { CallToAction } from '../../blocks/CallToAction/config' import { Content } from '../../blocks/Content/config' import { MediaBlock } from '../../blocks/MediaBlock/config' +import { ServicesList } from '../../blocks/ServicesList/config' import { hero } from '@/heros/config' import { slugField } from '@/fields/slug' import { populatePublishedAt } from '../../hooks/populatePublishedAt' @@ -76,7 +77,7 @@ export const Pages: CollectionConfig<'pages'> = { { name: 'layout', type: 'blocks', - blocks: [CallToAction, Content, MediaBlock, Archive], + blocks: [CallToAction, Content, MediaBlock, Archive, ServicesList], required: true, admin: { initCollapsed: true, diff --git a/apps/backend/src/payload-types.ts b/apps/backend/src/payload-types.ts index bc187aa..d887ced 100644 --- a/apps/backend/src/payload-types.ts +++ b/apps/backend/src/payload-types.ts @@ -194,7 +194,7 @@ export interface Page { | null; media?: (string | null) | Media; }; - layout: (CallToActionBlock | ContentBlock | MediaBlock | ArchiveBlock)[]; + layout: (CallToActionBlock | ContentBlock | MediaBlock | ArchiveBlock | ServicesListBlock)[]; meta?: { title?: string | null; /** @@ -564,6 +564,37 @@ export interface ArchiveBlock { blockName?: string | null; blockType: 'archive'; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "ServicesListBlock". + */ +export interface ServicesListBlock { + services: { + title: string; + description: string; + category: string; + iconType?: ('preset' | 'svg' | 'upload') | null; + /** + * Preset icon name: facebook, google, ads, news, youtube, forum, web, video + */ + icon?: string | null; + /** + * Paste SVG code directly (e.g., ...) + */ + iconSvg?: string | null; + /** + * Upload an icon image (SVG, PNG) + */ + iconImage?: (string | null) | Media; + isHot?: boolean | null; + image?: (string | null) | Media; + link?: string | null; + id?: string | null; + }[]; + id?: string | null; + blockName?: string | null; + blockType: 'servicesList'; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "portfolio". @@ -923,6 +954,7 @@ export interface PagesSelect { content?: T | ContentBlockSelect; mediaBlock?: T | MediaBlockSelect; archive?: T | ArchiveBlockSelect; + servicesList?: T | ServicesListBlockSelect; }; meta?: | T @@ -1011,6 +1043,29 @@ export interface ArchiveBlockSelect { id?: T; blockName?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "ServicesListBlock_select". + */ +export interface ServicesListBlockSelect { + services?: + | T + | { + title?: T; + description?: T; + category?: T; + iconType?: T; + icon?: T; + iconSvg?: T; + iconImage?: T; + isHot?: T; + image?: T; + link?: T; + id?: T; + }; + id?: T; + blockName?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "posts_select". diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 9217836..52b5a6f 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -17,8 +17,9 @@ "@tailwindcss/vite": "^4.1.14", "agentation": "^2.1.1", "agentation-mcp": "^1.1.0", - "astro": "6.0.0-beta.1", - "better-auth": "^1.3.13" + "astro": "6.0.0-beta.17", + "better-auth": "^1.3.13", + "isomorphic-dompurify": "^3.0.0" }, "devDependencies": { "@astrojs/check": "^0.9.6", diff --git a/apps/frontend/src/components/Footer.astro b/apps/frontend/src/components/Footer.astro index 79e3ca1..b21df78 100644 --- a/apps/frontend/src/components/Footer.astro +++ b/apps/frontend/src/components/Footer.astro @@ -42,8 +42,8 @@ try { const currentYear = new Date().getFullYear(); --- -
-
+
+

恩群數位累積多年廣告行銷操作經驗,擁有全方位行銷人才,讓我們可以為客戶精準的規劃每一分廣告預算,讓你的品牌深入人心。更重要的是恩群的存在,為了成為每家公司最佳數位夥伴,作為彼此最堅強的後盾,你會知道有我們的陪伴 你並不孤單。 @@ -72,7 +72,7 @@ const currentYear = new Date().getFullYear(); href="https://www.facebook.com/EnChun-Taiwan-100979265112420" target="_blank" rel="noopener noreferrer" - class="flex items-center mb-2" + class="flex items-center mb-2 no-underline hover:underline transition-colors" > -

+

諮詢電話:
02 5570 0527

enchuntaiwan@gmail.com
@@ -99,16 +99,28 @@ const currentYear = new Date().getFullYear(); > 行銷方案 -
    - {footerNavItems.length > 0 && footerNavItems[0]?.childNavItems - ? footerNavItems[0].childNavItems.map((item: any) => ( +
@@ -118,40 +130,35 @@ const currentYear = new Date().getFullYear(); > 行銷放大鏡 -
    - {categories.length > 0 - ? categories.map((cat: any) => ( +
-
-

- copyright © Enchun digital 2018 - {currentYear} -

-
+ +
+

+ copyright © Enchun digital 2018 - {currentYear} +

- - diff --git a/apps/frontend/src/components/Header.astro b/apps/frontend/src/components/Header.astro index d0232a5..36e3bbb 100644 --- a/apps/frontend/src/components/Header.astro +++ b/apps/frontend/src/components/Header.astro @@ -62,10 +62,10 @@ function isLinkActive(url: string): boolean { ---
-