Skip to content

Commit dc268db

Browse files
committed
CMS-45730 Add Header and Footer components; implement breadcrumbs in Header and update layout structure
1 parent a1a8644 commit dc268db

File tree

5 files changed

+125
-64
lines changed

5 files changed

+125
-64
lines changed

templates/alloy-template/src/app/[...slug]/page.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import Footer from '@/components/base/Footer';
2+
import Header from '@/components/base/Header';
13
import { GraphClient } from '@optimizely/cms-sdk';
24
import { OptimizelyComponent } from '@optimizely/cms-sdk/react/server';
35
import { notFound } from 'next/navigation';
@@ -15,11 +17,23 @@ export default async function Page({ params }: Props) {
1517
const client = new GraphClient(process.env.OPTIMIZELY_GRAPH_SINGLE_KEY!, {
1618
graphUrl: process.env.OPTIMIZELY_GRAPH_URL,
1719
});
18-
const c = await client.getContentByPath(`/${slug.join('/')}/`);
20+
const path = `/${slug.join('/')}/`;
21+
const c = await client.getContentByPath(path);
22+
23+
const children = (await client.getItems(path)) ?? [];
24+
const ancestors = (await client.getPath(path)) ?? [];
1925

2026
if (c.length === 0) {
2127
notFound();
2228
}
2329

24-
return <OptimizelyComponent opti={c[0]} />;
30+
return (
31+
<>
32+
<Header currentPath={{ children, ancestors }} />
33+
<div className="container mx-auto p-10">
34+
<OptimizelyComponent opti={c[0]} />
35+
</div>
36+
<Footer />
37+
</>
38+
);
2539
}

templates/alloy-template/src/app/layout.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,10 @@ import {
77
import { initReactComponentRegistry } from '@optimizely/cms-sdk/react/server';
88

99
import './globals.css';
10-
import Header from '@/components/base/Header';
11-
import Footer from '@/components/base/Footer';
1210
import Teaser, { TeaserContentType } from '@/components/base/Teaser';
1311
import Editorial, { EditorialContentType } from '@/components/base/Editorial';
1412
import Contact, { ContactContentType } from '@/components/base/Contact';
15-
import StartPage, { StartPageContentType } from '@/components/Start';
13+
import StartPage, { StartContentType } from '@/components/Start';
1614
import Product, { ProductContentType } from '@/components/Product';
1715
import Standard, { StandardContentType } from '@/components/Standard';
1816
import Notice, { NoticeContentType } from '@/components/base/Notice';
@@ -43,7 +41,7 @@ initContentTypeRegistry([
4341
NewsContentType,
4442
NoticeContentType,
4543
ProductContentType,
46-
StartPageContentType,
44+
StartContentType,
4745
TeaserContentType,
4846
StandardContentType,
4947
]);
@@ -63,7 +61,7 @@ initReactComponentRegistry({
6361
},
6462
});
6563

66-
export default function RootLayout({
64+
export default async function RootLayout({
6765
children,
6866
}: Readonly<{
6967
children: React.ReactNode;
@@ -73,9 +71,7 @@ export default function RootLayout({
7371
<body
7472
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
7573
>
76-
<Header />
77-
<div className="container mx-auto p-10">{children}</div>
78-
<Footer />
74+
{children}
7975
</body>
8076
</html>
8177
);

templates/alloy-template/src/components/Start.tsx

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import {
77
import { ProductContentType } from './Product';
88
import { StandardContentType } from './Standard';
99

10-
export const StartPageContentType = contentType({
11-
key: 'StartPage',
10+
export const StartContentType = contentType({
11+
key: 'Start',
1212
displayName: 'Start Page',
1313
baseType: '_experience',
1414
mayContainTypes: [StandardContentType, ProductContentType],
@@ -123,20 +123,16 @@ export const StartPageContentType = contentType({
123123
},
124124
});
125125

126-
type StartPageProps = {
127-
opti: Infer<typeof StartPageContentType>;
126+
type StartProps = {
127+
opti: Infer<typeof StartContentType>;
128128
};
129129

130130
function ComponentWrapper({ children, node }: ComponentContainerProps) {
131131
const { pa } = getPreviewUtils(node);
132132
return <div {...pa(node)}>{children}</div>;
133133
}
134134

135-
function StartPage({ opti }: StartPageProps) {
136-
const { pa } = getPreviewUtils(opti);
137-
138-
console.log(opti.image?.url.default);
139-
135+
function StartPage({ opti }: StartProps) {
140136
return (
141137
<>
142138
<div

templates/alloy-template/src/components/base/Footer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ interface FooterSection {
1010

1111
interface FooterProps {
1212
sections?: FooterSection[];
13+
currentPath?: string;
1314
}
1415

1516
const defaultSections: FooterSection[] = [
@@ -47,7 +48,7 @@ const defaultSections: FooterSection[] = [
4748
},
4849
];
4950

50-
function Footer({ sections = defaultSections }: FooterProps) {
51+
function Footer({ sections = defaultSections, currentPath }: FooterProps) {
5152
return (
5253
<footer className="bg-gray-800 text-white">
5354
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">

templates/alloy-template/src/components/base/Header.tsx

Lines changed: 98 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import Link from 'next/link';
2+
13
interface NavigationItem {
24
label: string;
35
href: string;
46
}
57

68
interface HeaderProps {
9+
currentPath: any;
710
navigationItems?: NavigationItem[];
811
logoText?: string;
912
}
@@ -15,55 +18,106 @@ const defaultNavigationItems: NavigationItem[] = [
1518
{ label: 'ABOUT US', href: '/en/about-us' },
1619
];
1720

18-
function Header({ navigationItems = defaultNavigationItems }: HeaderProps) {
21+
async function Header({
22+
currentPath,
23+
navigationItems = defaultNavigationItems,
24+
}: HeaderProps) {
25+
const ancestors = currentPath?.ancestors || [];
26+
27+
// Filter out the start page (first item) and create breadcrumbs
28+
const breadcrumbs = ancestors.slice(1).map((ancestor: any) => ({
29+
label: ancestor._metadata.displayName,
30+
href: ancestor._metadata.url.hierarchical,
31+
}));
32+
1933
return (
20-
<header className="bg-gray-100 border-b border-gray-200">
21-
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
22-
<div className="flex items-center justify-between h-28">
23-
{/* Navigation */}
24-
<nav className="hidden md:flex md:items-center md:space-x-8">
25-
<div className="flex-shrink-0">
26-
{/* Logo */}
27-
<img src="/logo.png" alt="Logo" className="h-14 w-14" />
28-
</div>
29-
<div className="flex items-center space-x-8">
30-
{navigationItems.map((item, index) => (
31-
<a
32-
key={index}
33-
href={item.href}
34-
className="text-gray-700 hover:text-teal-600 transition-colors duration-200 text-lg font-extrabold uppercase tracking-wide"
35-
>
36-
{item.label}
37-
</a>
38-
))}
39-
</div>
40-
</nav>
34+
<>
35+
<header className="bg-gray-100 border-b border-gray-200">
36+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
37+
<div className="flex items-center justify-between h-28">
38+
{/* Navigation */}
39+
<nav className="hidden md:flex md:items-center md:space-x-8">
40+
<div className="flex-shrink-0">
41+
{/* Logo */}
42+
<img src="/logo.png" alt="Logo" className="h-14 w-14" />
43+
</div>
44+
<div className="flex items-center space-x-8">
45+
{navigationItems.map((item, index) => (
46+
<a
47+
key={index}
48+
href={item.href}
49+
className="text-gray-700 hover:text-teal-600 transition-colors duration-200 text-lg font-extrabold uppercase tracking-wide"
50+
>
51+
{item.label}
52+
</a>
53+
))}
54+
</div>
55+
</nav>
4156

42-
{/* Mobile menu button */}
43-
<div className="md:hidden">
44-
<button
45-
type="button"
46-
className="text-gray-700 hover:text-teal-600 focus:outline-none focus:text-teal-600"
47-
aria-label="Open menu"
48-
>
49-
<svg
50-
className="h-6 w-6"
51-
fill="none"
52-
viewBox="0 0 24 24"
53-
stroke="currentColor"
57+
{/* Mobile menu button */}
58+
<div className="md:hidden">
59+
<button
60+
type="button"
61+
className="text-gray-700 hover:text-teal-600 focus:outline-none focus:text-teal-600"
62+
aria-label="Open menu"
5463
>
55-
<path
56-
strokeLinecap="round"
57-
strokeLinejoin="round"
58-
strokeWidth={2}
59-
d="M4 6h16M4 12h16M4 18h16"
60-
/>
61-
</svg>
62-
</button>
64+
<svg
65+
className="h-6 w-6"
66+
fill="none"
67+
viewBox="0 0 24 24"
68+
stroke="currentColor"
69+
>
70+
<path
71+
strokeLinecap="round"
72+
strokeLinejoin="round"
73+
strokeWidth={2}
74+
d="M4 6h16M4 12h16M4 18h16"
75+
/>
76+
</svg>
77+
</button>
78+
</div>
6379
</div>
6480
</div>
65-
</div>
66-
</header>
81+
</header>
82+
83+
{/* Breadcrumbs */}
84+
{breadcrumbs.length > 0 && (
85+
<nav
86+
className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4"
87+
aria-label="Breadcrumb"
88+
>
89+
<ol className="flex items-center space-x-1">
90+
<li>
91+
<Link
92+
href={ancestors[0]?._metadata?.url?.hierarchical || '/'}
93+
className="text-[#1cb898] hover:text-gray-700"
94+
>
95+
Home
96+
</Link>
97+
</li>
98+
{breadcrumbs.map(
99+
(crumb: { label: string; href: string }, index: number) => (
100+
<li key={index} className="flex items-center">
101+
<span className="text-gray-400 mx-1">/</span>
102+
{index === breadcrumbs.length - 1 ? (
103+
<span className="text-gray-700 font-medium">
104+
{crumb.label}
105+
</span>
106+
) : (
107+
<Link
108+
href={crumb.href}
109+
className="text-gray-500 hover:text-gray-700"
110+
>
111+
{crumb.label}
112+
</Link>
113+
)}
114+
</li>
115+
)
116+
)}
117+
</ol>
118+
</nav>
119+
)}
120+
</>
67121
);
68122
}
69123

0 commit comments

Comments
 (0)