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}
+
{
description && (
-
{description}
+
+ {description}
+
)
}
+
+
+

+
{
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 = '有任何問題嗎?歡迎聯絡恩群數位行銷,我
---
-