mirror of
https://github.com/vercel/commerce.git
synced 2025-05-15 14:06:59 +00:00
feat(poc): add basic cms pages
This commit is contained in:
parent
a598b3f1e5
commit
f61c41f24a
@ -11,9 +11,9 @@ export const revalidate = 43200; // 12 hours in seconds
|
|||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params
|
params
|
||||||
}: {
|
}: {
|
||||||
params: { page: string };
|
params: { cms: string };
|
||||||
}): Promise<Metadata> {
|
}): Promise<Metadata> {
|
||||||
const page = await getPage(params.page);
|
const page = await getPage(params.cms);
|
||||||
|
|
||||||
if (!page) return notFound();
|
if (!page) return notFound();
|
||||||
|
|
||||||
@ -35,8 +35,8 @@ export async function generateMetadata({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page({ params }: { params: { page: string } }) {
|
export default async function Page({ params }: { params: { cms: string } }) {
|
||||||
const page = await getPage(params.page);
|
const page = await getPage(params.cms);
|
||||||
|
|
||||||
if (!page) return notFound();
|
if (!page) return notFound();
|
||||||
let date = page.createdAt;
|
let date = page.createdAt;
|
@ -56,7 +56,7 @@ export default async function Footer() {
|
|||||||
</nav>
|
</nav>
|
||||||
))}
|
))}
|
||||||
<div className="col-span-1 text-black dark:text-white lg:col-span-2 inline-grid justify-items-end">
|
<div className="col-span-1 text-black dark:text-white lg:col-span-2 inline-grid justify-items-end">
|
||||||
<a aria-label="Github Repository" href="https://github.com/vercel/commerce">
|
<a aria-label="Github Repository" href="https://github.com/shopware/frontends" target="_blank" rel="noopener noreferrer">
|
||||||
<GitHubIcon className="h-6" />
|
<GitHubIcon className="h-6" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
6
lib/shopware/api-types/apiTypes-6.5.2.0.d.ts
vendored
6
lib/shopware/api-types/apiTypes-6.5.2.0.d.ts
vendored
@ -950,7 +950,7 @@ export type components = {
|
|||||||
position: number;
|
position: number;
|
||||||
sectionId: string;
|
sectionId: string;
|
||||||
sectionPosition?: string;
|
sectionPosition?: string;
|
||||||
slots?: components['schemas']['CmsSlot'];
|
slots?: components['schemas']['CmsSlot'][];
|
||||||
type: string;
|
type: string;
|
||||||
/** Format: date-time */
|
/** Format: date-time */
|
||||||
updatedAt?: string;
|
updatedAt?: string;
|
||||||
@ -993,7 +993,7 @@ export type components = {
|
|||||||
name?: string;
|
name?: string;
|
||||||
previewMedia?: components['schemas']['Media'];
|
previewMedia?: components['schemas']['Media'];
|
||||||
previewMediaId?: string;
|
previewMediaId?: string;
|
||||||
sections?: components['schemas']['CmsSection'];
|
sections?: components['schemas']['CmsSection'][];
|
||||||
translated?: {
|
translated?: {
|
||||||
cssClass?: string;
|
cssClass?: string;
|
||||||
entity?: string;
|
entity?: string;
|
||||||
@ -1029,7 +1029,7 @@ export type components = {
|
|||||||
backgroundMedia?: components['schemas']['Media'];
|
backgroundMedia?: components['schemas']['Media'];
|
||||||
backgroundMediaId?: string;
|
backgroundMediaId?: string;
|
||||||
backgroundMediaMode?: string;
|
backgroundMediaMode?: string;
|
||||||
blocks?: components['schemas']['CmsBlock'];
|
blocks?: components['schemas']['CmsBlock'][];
|
||||||
cmsPageVersionId?: string;
|
cmsPageVersionId?: string;
|
||||||
/** Format: date-time */
|
/** Format: date-time */
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
|
@ -85,6 +85,28 @@ export function getDefaultCategoryCriteria(page: number = 1, limit: number = 1)
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getDefaultCategoryWithCmsCriteria(page: number = 1, limit: number = 1) {
|
||||||
|
return {
|
||||||
|
page: page,
|
||||||
|
limit: limit,
|
||||||
|
associations: {
|
||||||
|
cmsPage: {
|
||||||
|
associations: {
|
||||||
|
sections: {
|
||||||
|
associations: {
|
||||||
|
blocks: {
|
||||||
|
associations: {
|
||||||
|
slots: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function getStaticCollectionCriteria(page: number = 1, limit: number = 20) {
|
export function getStaticCollectionCriteria(page: number = 1, limit: number = 20) {
|
||||||
return {
|
return {
|
||||||
page: page,
|
page: page,
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
} from './types';
|
} from './types';
|
||||||
import {
|
import {
|
||||||
getDefaultCategoryCriteria,
|
getDefaultCategoryCriteria,
|
||||||
|
getDefaultCategoryWithCmsCriteria,
|
||||||
getDefaultCrossSellingCriteria,
|
getDefaultCrossSellingCriteria,
|
||||||
getDefaultProductCriteria,
|
getDefaultProductCriteria,
|
||||||
getDefaultProductsCriteria,
|
getDefaultProductsCriteria,
|
||||||
@ -26,29 +27,31 @@ import {
|
|||||||
requestSeoUrl
|
requestSeoUrl
|
||||||
} from './api';
|
} from './api';
|
||||||
import {
|
import {
|
||||||
|
transformCollection,
|
||||||
|
transformHandle,
|
||||||
transformMenu,
|
transformMenu,
|
||||||
transformPage,
|
transformPage,
|
||||||
transformProduct,
|
transformProduct,
|
||||||
transformProducts,
|
transformProducts,
|
||||||
transformCollection,
|
|
||||||
transformStaticCollection
|
transformStaticCollection
|
||||||
} from './transform';
|
} from './transform';
|
||||||
|
|
||||||
export async function getMenu(params?: { type?: StoreNavigationTypeSW, depth?: number }): Promise<Menu[]> {
|
export async function getMenu(params?: {
|
||||||
|
type?: StoreNavigationTypeSW;
|
||||||
|
depth?: number;
|
||||||
|
}): Promise<Menu[]> {
|
||||||
const type = params?.type || 'main-navigation';
|
const type = params?.type || 'main-navigation';
|
||||||
const depth = params?.depth || 1;
|
const depth = params?.depth || 1;
|
||||||
const res = await requestNavigation(type, depth);
|
const res = await requestNavigation(type, depth);
|
||||||
|
|
||||||
return res ? transformMenu(res) : [];
|
return res ? transformMenu(res, type) : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPage(handle: string): Promise<Page | undefined> {
|
export async function getPage(handle: string | []): Promise<Page | undefined> {
|
||||||
const seoUrlElement = await getFirstSeoUrlElement(handle);
|
const pageHandle = transformHandle(handle).replace('cms/', '');
|
||||||
|
const seoUrlElement = await getFirstSeoUrlElement(pageHandle);
|
||||||
if (seoUrlElement) {
|
if (seoUrlElement) {
|
||||||
const resCategory = await requestCategory(
|
const resCategory = await getCategory(seoUrlElement);
|
||||||
seoUrlElement.foreignKey,
|
|
||||||
getDefaultCategoryCriteria()
|
|
||||||
);
|
|
||||||
|
|
||||||
return resCategory ? transformPage(seoUrlElement, resCategory) : undefined;
|
return resCategory ? transformPage(seoUrlElement, resCategory) : undefined;
|
||||||
}
|
}
|
||||||
@ -107,15 +110,11 @@ export async function getCollectionProducts(params?: {
|
|||||||
defaultSearchCriteria?: Partial<ProductListingCriteria>;
|
defaultSearchCriteria?: Partial<ProductListingCriteria>;
|
||||||
}): Promise<Product[]> {
|
}): Promise<Product[]> {
|
||||||
let res;
|
let res;
|
||||||
let collectionName: string | [] | undefined = params?.collection;
|
|
||||||
let category = params?.categoryId;
|
let category = params?.categoryId;
|
||||||
|
const collectionName = transformHandle(params?.collection ?? '');
|
||||||
const sorting = getSortingCriteria(params?.sortKey, params?.reverse);
|
const sorting = getSortingCriteria(params?.sortKey, params?.reverse);
|
||||||
|
|
||||||
if (!category && collectionName) {
|
if (!category && collectionName !== '') {
|
||||||
// this part is important for the catch all collection handle/slug
|
|
||||||
if (Array.isArray(collectionName)) {
|
|
||||||
collectionName = collectionName.join('/');
|
|
||||||
}
|
|
||||||
const seoUrlElement = await getFirstSeoUrlElement(collectionName);
|
const seoUrlElement = await getFirstSeoUrlElement(collectionName);
|
||||||
if (seoUrlElement) {
|
if (seoUrlElement) {
|
||||||
category = seoUrlElement.foreignKey;
|
category = seoUrlElement.foreignKey;
|
||||||
@ -139,18 +138,22 @@ export async function getCollectionProducts(params?: {
|
|||||||
return res ? transformProducts(res) : [];
|
return res ? transformProducts(res) : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getCategory(
|
||||||
|
seoUrl: ApiSchemas['SeoUrl'],
|
||||||
|
cms: boolean = false
|
||||||
|
): Promise<ApiSchemas['Category']> {
|
||||||
|
const criteria = cms ? getDefaultCategoryWithCmsCriteria() : getDefaultCategoryCriteria();
|
||||||
|
const resCategory = await requestCategory(seoUrl.foreignKey, criteria);
|
||||||
|
|
||||||
|
return resCategory;
|
||||||
|
}
|
||||||
|
|
||||||
// This function is only used for generateMetadata at app/search/(collection)/[...collection]/page.tsx
|
// This function is only used for generateMetadata at app/search/(collection)/[...collection]/page.tsx
|
||||||
export async function getCollection(handle: string | []) {
|
export async function getCollection(handle: string | []) {
|
||||||
let collectionName: string | [] | undefined = handle;
|
const collectionName = transformHandle(handle);
|
||||||
if (Array.isArray(collectionName)) {
|
|
||||||
collectionName = collectionName.join('/');
|
|
||||||
}
|
|
||||||
const seoUrlElement = await getFirstSeoUrlElement(collectionName);
|
const seoUrlElement = await getFirstSeoUrlElement(collectionName);
|
||||||
if (seoUrlElement) {
|
if (seoUrlElement) {
|
||||||
const resCategory = await requestCategory(
|
const resCategory = await getCategory(seoUrlElement);
|
||||||
seoUrlElement.foreignKey,
|
|
||||||
getDefaultCategoryCriteria()
|
|
||||||
);
|
|
||||||
const path = seoUrlElement.seoPathInfo ?? '';
|
const path = seoUrlElement.seoPathInfo ?? '';
|
||||||
const collection = transformCollection(seoUrlElement, resCategory);
|
const collection = transformCollection(seoUrlElement, resCategory);
|
||||||
|
|
||||||
@ -164,10 +167,7 @@ export async function getCollection(handle: string | []) {
|
|||||||
export async function getProduct(handle: string | []): Promise<Product | undefined> {
|
export async function getProduct(handle: string | []): Promise<Product | undefined> {
|
||||||
let productSW: ApiSchemas['Product'] | undefined;
|
let productSW: ApiSchemas['Product'] | undefined;
|
||||||
let productId: string | undefined;
|
let productId: string | undefined;
|
||||||
let productHandle: string | [] | undefined = handle;
|
const productHandle = transformHandle(handle);
|
||||||
if (Array.isArray(productHandle)) {
|
|
||||||
productHandle = productHandle.join('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
const seoUrlElement = await getFirstSeoUrlElement(productHandle);
|
const seoUrlElement = await getFirstSeoUrlElement(productHandle);
|
||||||
if (seoUrlElement) {
|
if (seoUrlElement) {
|
||||||
|
@ -3,28 +3,32 @@ import {
|
|||||||
CategoryListingResultSW,
|
CategoryListingResultSW,
|
||||||
Collection,
|
Collection,
|
||||||
Menu,
|
Menu,
|
||||||
|
Page,
|
||||||
Product,
|
Product,
|
||||||
ProductOption,
|
ProductOption,
|
||||||
ProductVariant
|
ProductVariant
|
||||||
} from './types';
|
} from './types';
|
||||||
import { ListItem } from 'components/layout/search/filter';
|
import { ListItem } from 'components/layout/search/filter';
|
||||||
|
|
||||||
export function transformMenu(res: ApiSchemas['NavigationRouteResponse']) {
|
export function transformMenu(res: ApiSchemas['NavigationRouteResponse'], type: string) {
|
||||||
let menu: Menu[] = [];
|
let menu: Menu[] = [];
|
||||||
|
|
||||||
res.map((item) => menu.push(transformMenuItem(item)));
|
res.map((item) => menu.push(transformMenuItem(item, type)));
|
||||||
|
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
function transformMenuItem(item: ApiSchemas['Category']): Menu {
|
function transformMenuItem(item: ApiSchemas['Category'], type: string): Menu {
|
||||||
|
// @ToDo: currently only footer-navigation is used for cms pages, this need to be more dynamic (shoud depending on the item)
|
||||||
return {
|
return {
|
||||||
id: item.id ?? '',
|
id: item.id ?? '',
|
||||||
title: item.name,
|
title: item.name,
|
||||||
children: item.children?.map((item) => transformMenuItem(item)) ?? [],
|
children: item.children?.map((item) => transformMenuItem(item, type)) ?? [],
|
||||||
path:
|
path:
|
||||||
item.seoUrls && item.seoUrls.length > 0 && item.seoUrls[0] && item.seoUrls[0].seoPathInfo
|
item.seoUrls && item.seoUrls.length > 0 && item.seoUrls[0] && item.seoUrls[0].seoPathInfo
|
||||||
? '/search/' + item.seoUrls[0].seoPathInfo
|
? type === 'footer-navigation'
|
||||||
|
? '/cms/' + item.seoUrls[0].seoPathInfo
|
||||||
|
: '/search/' + item.seoUrls[0].seoPathInfo
|
||||||
: '',
|
: '',
|
||||||
type: item.children && item.children.length > 0 ? 'headline' : 'link'
|
type: item.children && item.children.length > 0 ? 'headline' : 'link'
|
||||||
};
|
};
|
||||||
@ -32,25 +36,52 @@ function transformMenuItem(item: ApiSchemas['Category']): Menu {
|
|||||||
|
|
||||||
export function transformPage(
|
export function transformPage(
|
||||||
seoUrlElement: ApiSchemas['SeoUrl'],
|
seoUrlElement: ApiSchemas['SeoUrl'],
|
||||||
resCategory: ApiSchemas['Category']
|
category: ApiSchemas['Category']
|
||||||
) {
|
): Page {
|
||||||
|
let plainHtmlContent;
|
||||||
|
if (category.cmsPage) {
|
||||||
|
const cmsPage: ApiSchemas['CmsPage'] = category.cmsPage;
|
||||||
|
plainHtmlContent = transformToPlainHtmlContent(cmsPage);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: seoUrlElement.id ?? '',
|
id: seoUrlElement.id ?? '',
|
||||||
title: resCategory.translated?.metaTitle ?? resCategory.name ?? '',
|
title: category.translated?.metaTitle ?? category.name ?? '',
|
||||||
handle: seoUrlElement.seoPathInfo,
|
handle: seoUrlElement.seoPathInfo,
|
||||||
body: resCategory.description ?? '',
|
body: plainHtmlContent ?? category.description ?? '',
|
||||||
bodySummary: resCategory.translated?.metaDescription ?? resCategory.description ?? '',
|
bodySummary: category.translated?.metaDescription ?? category.description ?? '',
|
||||||
seo: {
|
seo: {
|
||||||
title: resCategory.translated?.metaTitle ?? resCategory.name ?? '',
|
title: category.translated?.metaTitle ?? category.name ?? '',
|
||||||
description: resCategory.translated?.metaDescription ?? resCategory.description ?? ''
|
description: category.translated?.metaDescription ?? category.description ?? ''
|
||||||
},
|
},
|
||||||
createdAt: seoUrlElement.createdAt ?? '',
|
createdAt: seoUrlElement.createdAt ?? '',
|
||||||
updatedAt: seoUrlElement.updatedAt ?? '',
|
updatedAt: seoUrlElement.updatedAt ?? '',
|
||||||
routeName: seoUrlElement.routeName,
|
routeName: seoUrlElement.routeName,
|
||||||
|
originalCmsPage: category.cmsPage,
|
||||||
foreignKey: seoUrlElement.foreignKey
|
foreignKey: seoUrlElement.foreignKey
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function transformToPlainHtmlContent(cmsPage: ApiSchemas['CmsPage']): string {
|
||||||
|
let plainHtmlContent = '';
|
||||||
|
|
||||||
|
cmsPage.sections?.map((section) => {
|
||||||
|
section.blocks?.map((block) => {
|
||||||
|
block.slots?.map((slot) => {
|
||||||
|
if (slot.slot === 'content' && slot.config?.content) {
|
||||||
|
const currentContent: string = slot.config.content.value + '';
|
||||||
|
// we do not add content with h1, because will be added via template already
|
||||||
|
if (!currentContent.match(/(<\/?h)([1])/)) {
|
||||||
|
plainHtmlContent += currentContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return plainHtmlContent;
|
||||||
|
}
|
||||||
|
|
||||||
export function transformCollection(
|
export function transformCollection(
|
||||||
seoUrlElement: ApiSchemas['SeoUrl'],
|
seoUrlElement: ApiSchemas['SeoUrl'],
|
||||||
resCategory: ApiSchemas['Category']
|
resCategory: ApiSchemas['Category']
|
||||||
@ -234,3 +265,12 @@ function transformVariants(parent: ApiSchemas['Product']): ProductVariant[] {
|
|||||||
|
|
||||||
return productVariants;
|
return productVariants;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function transformHandle(handle: string | []): string {
|
||||||
|
let collectionName: string | [] | undefined = handle;
|
||||||
|
if (Array.isArray(collectionName)) {
|
||||||
|
collectionName = collectionName.join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
return collectionName ?? '';
|
||||||
|
}
|
||||||
|
@ -18,7 +18,7 @@ export type SeoURLResultSW = {
|
|||||||
} & ApiSchemas['EntitySearchResult'];
|
} & ApiSchemas['EntitySearchResult'];
|
||||||
|
|
||||||
/** Vercel Commerce Types */
|
/** Vercel Commerce Types */
|
||||||
export type Menu = { id: string; title: string; path: string, type: string, children: Menu[] };
|
export type Menu = { id: string; title: string; path: string; type: string; children: Menu[] };
|
||||||
|
|
||||||
export type Page = {
|
export type Page = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -31,6 +31,7 @@ export type Page = {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
routeName?: string;
|
routeName?: string;
|
||||||
foreignKey?: string;
|
foreignKey?: string;
|
||||||
|
originalCmsPage?: ApiSchemas['CmsPage'];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ProductOption = {
|
export type ProductOption = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user