diff --git a/apps/frontend/src/components/CtaSection.astro b/apps/frontend/src/components/CtaSection.astro new file mode 100644 index 0000000..518a47a --- /dev/null +++ b/apps/frontend/src/components/CtaSection.astro @@ -0,0 +1,34 @@ +--- +/** + * CTA Section Component + */ +--- + +
+
+
+
+

+ 準備好開始新的旅程了嗎? +

+

+ 歡迎與我們聯絡 +

+
+ + 預約諮詢 + +
+
+
diff --git a/apps/frontend/src/components/Header.astro b/apps/frontend/src/components/Header.astro index 36e3bbb..556133a 100644 --- a/apps/frontend/src/components/Header.astro +++ b/apps/frontend/src/components/Header.astro @@ -1,29 +1,65 @@ --- -import { Image } from "astro:assets"; // Header component with scroll-based background and enhanced mobile animations +// --- TypeScript Interfaces (from payload-types.ts Header global) --- +interface NavLink { + type?: ("reference" | "custom") | null; + newTab?: boolean | null; + reference?: + | ({ relationTo: "pages"; value: string | { slug: string } } | null) + | ({ relationTo: "posts"; value: string | { slug: string } } | null); + url?: string | null; + label: string; +} + +interface NavItem { + link: NavLink; + id?: string | null; +} + +// --- Hardcoded fallback nav (used when CMS is unavailable) --- +const FALLBACK_NAV: NavItem[] = [ + { link: { type: "custom", url: "/about-enchun", label: "關於恩群" } }, + { + link: { + type: "custom", + url: "/marketing-solutions", + label: "行銷方案", + }, + }, + { link: { type: "custom", url: "/marketing-lens", label: "行銷放大鏡" } }, + { link: { type: "custom", url: "/enchun-basecamp", label: "恩群大本營" } }, + { link: { type: "custom", url: "/website-portfolio", label: "網站設計" } }, + { link: { type: "custom", url: "/contact", label: "聯絡我們" } }, +]; + // Use local backend in development, production URL from dev.vars/wrangler const isDev = import.meta.env.DEV; const PAYLOAD_CMS_URL = isDev - ? "http://localhost:3000" // Local backend in dev (port may vary) + ? "http://localhost:3000" : import.meta.env.PAYLOAD_CMS_URL || "https://enchun-admin.anlstudio.cc"; // Fetch navigation data from Payload CMS server-side -let navItems: any[] = []; +let navItems: NavItem[] = []; try { const response = await fetch( - `${PAYLOAD_CMS_URL}/api/globals/header?depth=2&draft=false&locale=undefined&trash=false`, + `${PAYLOAD_CMS_URL}/api/globals/header?depth=2&draft=false&trash=false`, ); if (response.ok) { const data = await response.json(); - navItems = data?.navItems || data || []; + navItems = data?.navItems || []; } } catch (error) { console.error("[Header SSR] Failed to fetch navigation:", error); } +// Use fallback when CMS returns empty +if (!navItems.length) { + navItems = FALLBACK_NAV; +} + // Helper to get link URL -function getLinkUrl(link: any): string { +function getLinkUrl(link: NavLink): string { if (!link) return "#"; if (link.type === "custom" && link.url) { @@ -32,10 +68,8 @@ function getLinkUrl(link: any): string { if (link.type === "reference" && link.reference?.value) { if (typeof link.reference.value === "string") { - // It's an ID, construct URL based on relationTo return `/${link.reference.relationTo || "pages"}/${link.reference.value}`; } else if (link.reference.value.slug) { - // It's a populated object with slug return `/${link.reference.value.slug}`; } } @@ -43,21 +77,16 @@ function getLinkUrl(link: any): string { return "#"; } -// Check if label should have a badge -function getBadgeForLabel(label: string): string { - if (label.includes("行銷方案")) { - return `Hot`; - } - if (label.includes("行銷放大鏡")) { - return `New`; - } - return ""; +// Return badge type for label (data-driven, no HTML strings) +function getBadgeType(label: string): "hot" | "new" | null { + if (label.includes("行銷方案")) return "hot"; + if (label.includes("行銷放大鏡")) return "new"; + return null; } // Check if link is active function isLinkActive(url: string): boolean { - const currentPath = Astro.url.pathname; - return currentPath === url || (url === "/" && currentPath === "/"); + return Astro.url.pathname === url; } --- @@ -65,53 +94,58 @@ function isLinkActive(url: string): boolean { class="fixed top-0 left-0 right-0 z-50 transition-all duration-300 ease-in-out will-change-transform" id="main-header" > - diff --git a/apps/frontend/src/components/HeaderBg.astro b/apps/frontend/src/components/HeaderBg.astro new file mode 100644 index 0000000..94ac3ce --- /dev/null +++ b/apps/frontend/src/components/HeaderBg.astro @@ -0,0 +1,10 @@ +--- + +--- + + +
+
diff --git a/apps/frontend/src/components/PortfolioCard.astro b/apps/frontend/src/components/PortfolioCard.astro index 58d30b6..d300b78 100644 --- a/apps/frontend/src/components/PortfolioCard.astro +++ b/apps/frontend/src/components/PortfolioCard.astro @@ -5,28 +5,30 @@ */ interface PortfolioItem { - slug: string - title: string - description: string - image?: string - tags?: string[] - externalUrl?: string + slug: string; + title: string; + description: string; + image?: string; + tags?: string[]; + externalUrl?: string; } interface Props { - item: PortfolioItem + item: PortfolioItem; } -const { item } = Astro.props +const { item } = Astro.props; -const imageUrl = item.image || '/placeholder-portfolio.jpg' -const title = item.title || 'Untitled' -const description = item.description || '' -const tags = item.tags || [] -const hasExternalLink = !!item.externalUrl -const linkHref = hasExternalLink ? item.externalUrl : `/website-portfolio/${item.slug}` -const linkTarget = hasExternalLink ? '_blank' : '_self' -const linkRel = hasExternalLink ? 'noopener noreferrer' : '' +const imageUrl = item.image || "/placeholder-portfolio.jpg"; +const title = item.title || "Untitled"; +const description = item.description || ""; +const tags = item.tags || []; +const hasExternalLink = !!item.externalUrl; +const linkHref = hasExternalLink + ? item.externalUrl + : `/website-portfolio/${item.slug}`; +const linkTarget = hasExternalLink ? "_blank" : "_self"; +const linkRel = hasExternalLink ? "noopener noreferrer" : ""; ---
  • @@ -36,37 +38,44 @@ const linkRel = hasExternalLink ? 'noopener noreferrer' : '' rel={linkRel} class="block bg-white rounded-xl overflow-hidden shadow-sm hover:shadow-lg transition-all duration-300 ease-in-out no-underline text-inherit hover:-translate-y-1 group" > - -
    - {title} -
    - -
    +
    -

    {title}

    +

    + {title} +

    { description && ( -

    {description}

    +

    + {description} +

    ) } + +
    + {title} +
    { tags.length > 0 && (
    {tags.map((tag) => ( - {tag} + + {tag} + ))}
    ) diff --git a/apps/frontend/src/components/SectionHeader.astro b/apps/frontend/src/components/SectionHeader.astro index c87c604..2564dc0 100644 --- a/apps/frontend/src/components/SectionHeader.astro +++ b/apps/frontend/src/components/SectionHeader.astro @@ -5,23 +5,40 @@ */ interface Props { - title: string - subtitle: string - class?: string - sectionBg?:string + title: string; + subtitle: string; + class?: string; + sectionBg?: string; } -const { title, subtitle, class: className, sectionBg:classNameBg } = Astro.props +const { + title, + subtitle, + class: className, + sectionBg: classNameBg, +} = Astro.props; ---
    - -
    +
    -

    {title}

    -

    {subtitle}

    +

    + {title} +

    +

    + {subtitle} +

    -
    +
    diff --git a/apps/frontend/src/pages/about-enchun.astro b/apps/frontend/src/pages/about-enchun.astro index 4bd733c..1dcec69 100644 --- a/apps/frontend/src/pages/about-enchun.astro +++ b/apps/frontend/src/pages/about-enchun.astro @@ -3,21 +3,37 @@ * About Page - 關於恩群數位 * 展示公司特色、服務優勢和與其他公司的差異 */ -import Layout from '../layouts/Layout.astro' -import AboutHero from '../sections/AboutHero.astro' -import FeatureSection from '../sections/FeatureSection.astro' -import ComparisonSection from '../sections/ComparisonSection.astro' -import CTASection from '../sections/CTASection.astro' +import Layout from "../layouts/Layout.astro"; +import AboutHero from "../sections/AboutHero.astro"; +import FeatureSection from "../sections/FeatureSection.astro"; +import ComparisonSection from "../sections/ComparisonSection.astro"; +import CTASection from "../sections/CTASection.astro"; +import SectionHeader from "../components/SectionHeader.astro"; +import CtaSection from "@/components/CtaSection.astro"; // Metadata for SEO -const title = '關於恩群數位 | 專業數位行銷服務團隊' -const description = '恩群數位行銷成立於2018年,提供全方位數位行銷服務。我們在地化優先、數據驅動,是您最可信赖的數位行銷夥伴。' +const title = "關於恩群數位 | 專業數位行銷服務團隊"; +const description = + "恩群數位行銷成立於2018年,提供全方位數位行銷服務。我們在地化優先、數據驅動,是您最可信赖的數位行銷夥伴。"; --- - + + + @@ -25,14 +41,5 @@ const description = '恩群數位行銷成立於2018年,提供全方位數位 - + diff --git a/apps/frontend/src/pages/contact-us.astro b/apps/frontend/src/pages/contact-us.astro index a62a60b..dfaad08 100644 --- a/apps/frontend/src/pages/contact-us.astro +++ b/apps/frontend/src/pages/contact-us.astro @@ -12,102 +12,102 @@ const description = '有任何問題嗎?歡迎聯絡恩群數位行銷,我 --- -
    -
    +
    +
    -
    -

    聯絡我們

    -

    +

    +

    聯絡我們

    +

    有任何問題嗎?歡迎聯絡我們,我們將竭誠為您服務。

    -

    +

    * 標註欄位為必填

    -
    + -
    -