Skip to content

Commit ae9a77d

Browse files
committed
feat(storefront): Additional bannerListCmsField , pageHeroCmsFields and productShelfCmsFields
Extending CMS common field configurations from composables Minor fixes and additional options on `CmsField` type
1 parent acabeba commit ae9a77d

File tree

6 files changed

+119
-21
lines changed

6 files changed

+119
-21
lines changed

packages/storefront/src/decap-cms/cms-fields.d.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export type CmsField = {
2323
fields?: Record<string, CmsField> | null,
2424
types?: Record<string, CmsField>,
2525
label?: string | Record<string, string>,
26+
_dropped?: true,
27+
_nullable?: true,
2628
} & Record<string, any>;
2729

2830
export type CmsFields = Record<string, CmsField>;
@@ -62,15 +64,19 @@ export type InferCmsFieldOutput<F extends CmsField> =
6264

6365
export type InferCmsOutput<FS extends CmsFields> = {
6466
[I in keyof FS as FS[I]['required'] extends true ? I : never]:
65-
InferCmsFieldOutput<FS[I]>;
67+
FS[I]['_dropped'] extends true ? never : InferCmsFieldOutput<FS[I]>;
6668
} & {
6769
[I in keyof FS as FS[I]['required'] extends true ? never : I]?:
68-
InferCmsFieldOutput<FS[I]>;
70+
FS[I]['_dropped'] extends true
71+
? never
72+
: FS[I]['_nullable'] extends true
73+
? InferCmsFieldOutput<FS[I]> | null
74+
: InferCmsFieldOutput<FS[I]>;
6975
};
7076

7177
export type CmsComponent = Partial<Omit<CmsField, 'widget'>> & {
7278
label: string | Record<string, string>,
73-
fields: CmsFields,
79+
fields?: CmsFields,
7480
};
7581

7682
export type CmsConfigExtend = {

packages/storefront/src/decap-cms/collections/get-configs-coll.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -620,26 +620,18 @@ const getConfigsColl = ({
620620
widget: 'code',
621621
required: false,
622622
default_language: 'css',
623-
allow_language_selection: false,
624-
output_code_only: true,
625623
},
626624
{
627625
name: 'htmlHead',
628626
label: 'HTML <head>',
629627
widget: 'code',
630628
required: false,
631-
default_language: 'html',
632-
allow_language_selection: false,
633-
output_code_only: true,
634629
},
635630
{
636631
name: 'htmlBody',
637632
label: 'HTML <body>',
638633
widget: 'code',
639634
required: false,
640-
default_language: 'html',
641-
allow_language_selection: false,
642-
output_code_only: true,
643635
},
644636
],
645637
},

packages/storefront/src/decap-cms/get-cms-config.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { CmsField, CmsComponent, CmsConfigExtend } from './cms-fields';
1+
import type { CmsField, CmsFields, CmsConfigExtend } from './cms-fields';
22
import type { ParsedCmsField } from './collections/get-configs-coll';
33
import Deepmerge from '@fastify/deepmerge';
44
import { i18n as _i18n } from '@ecomplus/utils';
@@ -31,8 +31,13 @@ export const getCmsConfig = async () => {
3131
}));
3232
const response = await afetch('/admin/config.json');
3333
const { components, mergeConfig } = await response.json() as CmsConfigExtend;
34-
const parseNestedCmsFields = (cmsFields: CmsComponent['fields']) => {
34+
const parseNestedCmsFields = (cmsFields: CmsFields) => {
3535
return Object.keys(cmsFields).map((name) => {
36+
delete cmsFields[name]._dropped;
37+
delete cmsFields[name]._nullable;
38+
if (!cmsFields[name].label) {
39+
cmsFields[name].widget = 'hidden';
40+
}
3641
const nestedFields = cmsFields[name].fields;
3742
return {
3843
required: false,
@@ -47,7 +52,8 @@ export const getCmsConfig = async () => {
4752
label: 'Hero slider',
4853
...(components.hero as any),
4954
widget: 'object' as const,
50-
fields: parseNestedCmsFields(components.hero.fields),
55+
fields: components.hero.fields
56+
&& parseNestedCmsFields(components.hero.fields),
5157
};
5258
const sectionsConfig = {
5359
name: 'sections',
@@ -64,7 +70,8 @@ export const getCmsConfig = async () => {
6470
name,
6571
...components.sections,
6672
widget: 'object' as const,
67-
fields: parseNestedCmsFields(components.sections[name].fields),
73+
fields: components.sections[name].fields
74+
&& parseNestedCmsFields(components.sections[name].fields),
6875
})),
6976
};
7077
const collOptions = {
@@ -73,7 +80,7 @@ export const getCmsConfig = async () => {
7380
locale,
7481
markdownOptions: {
7582
buttons: [
76-
'bold', 'italic', 'link',
83+
'bold', 'italic', 'link', 'code',
7784
'heading-three', 'heading-four', 'heading-five',
7885
'quote', 'bulleted-list', 'numbered-list',
7986
],
@@ -165,6 +172,14 @@ export const getCmsConfig = async () => {
165172
}
166173
return;
167174
}
175+
if (field.widget === 'code') {
176+
Object.assign(field, {
177+
default_language: 'html',
178+
allow_language_selection: false,
179+
output_code_only: true,
180+
...field,
181+
});
182+
}
168183
if (field.widget === 'object' || field.widget === 'list') {
169184
if (field.collapsed === undefined) field.collapsed = true;
170185
}

packages/storefront/src/lib/composables/use-banner.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { CmsField, CmsFields } from '@@sf/content';
12
import { computed } from 'vue';
23
import { parseShippingPhrase } from '@@sf/state/modules-info';
34

@@ -32,7 +33,7 @@ export const bannerCmsFields = ({
3233
label: { pt: 'Link no banner', en: 'Link in banner' },
3334
},
3435
title: {
35-
widget: 'string',
36+
widget: 'text',
3637
label: { pt: 'Título sobreposto', en: 'Overlapping title' },
3738
},
3839
subtitle: {
@@ -44,10 +45,18 @@ export const bannerCmsFields = ({
4445
label: { pt: 'Botão', en: 'Button' },
4546
},
4647
buttonLink: {
47-
widget: 'number',
48+
widget: 'string',
4849
label: { pt: 'Link do botão', en: 'Button link' },
4950
},
50-
}) as const;
51+
}) as const satisfies CmsFields;
52+
53+
export const bannerListCmsField = ({
54+
widget: 'list',
55+
label: 'Banners',
56+
label_singular: 'banner',
57+
summary: '{{fields.title}}',
58+
fields: bannerCmsFields,
59+
}) as const satisfies CmsField;
5160

5261
export const useBanner = (props: Props) => {
5362
const parsedTitle = computed(() => {

packages/storefront/src/lib/composables/use-product-shelf.ts

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ResourceId, Collections, SearchItem } from '@cloudcommerce/types';
22
import type { SectionPreviewProps } from '@@sf/state/use-cms-preview';
3+
import type { CmsFields } from '@@sf/content';
34
import { ref, shallowReactive } from 'vue';
45
import api from '@cloudcommerce/api';
56
import { inStock as checkInStock } from '@ecomplus/utils';
@@ -8,7 +9,7 @@ import { useSectionPreview } from '@@sf/state/use-cms-preview';
89

910
export type Props = Partial<SectionPreviewProps> & {
1011
collectionId?: ResourceId | null;
11-
searchQuery?: `&${string}` | '';
12+
searchQuery?: string; // `&${string} | ''`
1213
sort?: '-sales' | '-created_at' | 'price' | '-price' | '-price_discount' | string;
1314
title?: string | null;
1415
titleLink?: string | null;
@@ -20,6 +21,67 @@ export type Props = Partial<SectionPreviewProps> & {
2021
isRelatedProducts?: boolean;
2122
}
2223

24+
export const productShelfCmsFields = ({
25+
collectionIdAndInfo: {
26+
widget: 'string',
27+
label: { pt: 'Coleção', en: 'Collection' },
28+
hint: { pt: 'ID da coleção', en: 'Collection ID' },
29+
// `collectionIdAndInfo` parsed to `collectionId` server-side on `usePageMain`
30+
_dropped: true,
31+
},
32+
sort: {
33+
widget: 'select',
34+
options: [{
35+
label: { pt: 'Vendas', en: 'Sales' },
36+
value: '-sales',
37+
}, {
38+
label: { pt: 'Data de criação', en: 'Creation date' },
39+
value: '-created_at',
40+
}, {
41+
label: { pt: 'Menor preço', en: 'Lowest price' },
42+
value: 'price',
43+
}, {
44+
label: { pt: 'Maior preço', en: 'Highest price' },
45+
value: '-price',
46+
}, {
47+
label: { pt: 'Percentual de desconto', en: 'Discount percentage' },
48+
value: '-price_discount',
49+
}],
50+
},
51+
searchQuery: {
52+
widget: 'string',
53+
label: { pt: 'Query adicional', en: 'Additional query' },
54+
hint: { pt: 'Ex.: &brands.slug!=minha-marca', en: 'E.g. &brands.slug!=my-brand' },
55+
},
56+
title: {
57+
widget: 'string',
58+
label: { pt: 'Título', en: 'Title' },
59+
_nullable: true,
60+
},
61+
titleLink: {
62+
widget: 'string',
63+
label: { pt: 'Link no título', en: 'Title link' },
64+
_nullable: true,
65+
},
66+
isShuffle: {
67+
widget: 'boolean',
68+
label: { pt: 'Embaralhar itens', en: 'Shuffle result items' },
69+
},
70+
isHeadless: {
71+
widget: 'boolean',
72+
},
73+
limit: {
74+
widget: 'number',
75+
value_type: 'int',
76+
label: { pt: 'Limite de itens', en: 'Items limit' },
77+
default: 12,
78+
},
79+
page: {
80+
widget: 'number',
81+
value_type: 'int',
82+
},
83+
}) as const satisfies CmsFields;
84+
2385
const useProductShelf = (props: Props) => {
2486
const title = ref<string | null>(props.title || '');
2587
const titleLink = ref<string | null>(props.titleLink || '');

packages/storefront/src/lib/layouts/use-page-main.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import type { ResourceId, Collections } from '@cloudcommerce/types';
2-
import type { PageContent } from '@@sf/content';
2+
import type { CmsFields, PageContent } from '@@sf/content';
33
import type { RouteContext } from '@@sf/ssr-context';
44
import type { Props as UseBannerProps } from '@@sf/composables/use-banner';
55
import type { Props as UseProductShelfProps } from '@@sf/composables/use-product-shelf';
66
import type { Props as UseSearchShowcaseProps } from '@@sf/composables/use-search-showcase';
7+
import { bannerListCmsField } from '@@sf/composables/use-banner';
78
import { useSharedData } from '@@sf/composables/use-shared-data';
89
import { useProductShelf } from '@@sf/composables/use-product-shelf';
910
import { useSearchShowcase } from '@@sf/composables/use-search-showcase';
@@ -15,6 +16,19 @@ export type Props = {
1516
searchEngine?: UseSearchShowcaseProps['searchEngine'];
1617
}
1718

19+
export const pageHeroCmsFields = ({
20+
autoplay: {
21+
widget: 'number',
22+
value_type: 'int',
23+
label: 'Autoplay',
24+
hint: { pt: 'Milissegundos', en: 'Milliseconds' },
25+
},
26+
slides: {
27+
...bannerListCmsField,
28+
required: true,
29+
},
30+
}) as const satisfies CmsFields;
31+
1832
type PageContentHero = Exclude<PageContent['hero'], undefined>;
1933
const now = Date.now();
2034
const parseBanners = (banners: PageContentHero['slides']) => {

0 commit comments

Comments
 (0)