@evenicanpm/storefront-core 2.2.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -3
- package/src/api-manager/README.md +52 -27
- package/src/api-manager/datasources/d365/d365-cart.datasource.ts +166 -17
- package/src/api-manager/datasources/d365/d365-invoice.datasource.ts +100 -0
- package/src/api-manager/datasources/d365/d365-order.datasource.ts +75 -18
- package/src/api-manager/datasources/d365/d365-product.datasource.ts +19 -1
- package/src/api-manager/datasources/d365/d365-user.datasource.ts +125 -36
- package/src/api-manager/datasources/d365/d365.datasource.ts +3 -0
- package/src/api-manager/datasources/d365/utils/get-context-cookie.ts +10 -3
- package/src/api-manager/datasources/e4/e4.datasource.ts +9 -0
- package/src/api-manager/datasources/e4/graphqlRequestSdk.ts +7 -0
- package/src/api-manager/datasources/e4/order/e4-order.datasource.ts +7 -4
- package/src/api-manager/datasources/e4/user/e4-user.datasource.ts +4 -0
- package/src/api-manager/index.ts +54 -3
- package/src/api-manager/lib/get-graphql-client.ts +11 -5
- package/src/api-manager/schemas/cart.schema.ts +10 -1
- package/src/api-manager/schemas/invoice.schema.ts +55 -0
- package/src/api-manager/schemas/order.schema.ts +13 -1
- package/src/api-manager/schemas/user.schema.ts +8 -0
- package/src/api-manager/services/create-query.ts +4 -10
- package/src/api-manager/services/invoice/queries/get-invoice-details.ts +18 -0
- package/src/api-manager/services/invoice/queries/get-invoices.ts +18 -0
- package/src/api-manager/services/order/queries/get-order-details.ts +1 -1
- package/src/api-manager/services/order/queries/get-orders.ts +6 -3
- package/src/api-manager/services/user/queries/get-customer-balance.ts +20 -0
- package/src/api-manager/types/Datasource.ts +21 -2
- package/src/auth/better-auth.ts +1 -1
- package/src/cms/blocks/block-manager.tsx +18 -3
- package/src/cms/blocks/components/cta-banner.tsx +65 -0
- package/src/cms/blocks/components/footer/footer-content.tsx +61 -0
- package/src/cms/blocks/components/footer/index.tsx +9 -71
- package/src/cms/blocks/components/footer/interfaces.ts +4 -0
- package/src/cms/blocks/components/footer/sections/footer-contact.tsx +18 -9
- package/src/cms/blocks/components/footer/sections/footer-logo.tsx +17 -2
- package/src/cms/blocks/components/hero-image.tsx +83 -0
- package/src/cms/blocks/index.tsx +2 -0
- package/src/cms/blocks/interfaces.ts +20 -0
- package/src/cms/draft-mode-badge.tsx +26 -0
- package/src/cms/queries.ts +81 -0
- package/src/components/_tests_/site-logo.test.tsx +1 -1
- package/src/components/flex-box/flex-box.tsx +2 -0
- package/src/components/header/components/user.tsx +35 -11
- package/src/components/header/sticky-header.tsx +1 -0
- package/src/components/mini-cart/mini-cart.tsx +3 -3
- package/src/components/product-cards/product-card-plp/product-card.tsx +4 -12
- package/src/components/product-cards/product-card-plp/styles/index.ts +15 -0
- package/src/components/site-logo.tsx +1 -1
- package/src/components/wishlist-dialogs/add-to-wishlist/compound/wishlist-dialog-header.tsx +1 -0
- package/src/components/wishlist-dialogs/add-to-wishlist/compound/wishlist-dialog-item.tsx +3 -1
- package/src/pages/account/account-navigation.tsx +88 -60
- package/src/pages/account/account-routes.ts +86 -0
- package/src/pages/account/orders/order-history-filter-types.tsx +26 -0
- package/src/pages/account/orders/order-history-filters-panel.tsx +375 -0
- package/src/pages/account/orders/order-history-header.tsx +54 -0
- package/src/pages/account/orders/order-history-pagination.tsx +55 -0
- package/src/pages/account/orders/order-history-root.tsx +125 -0
- package/src/pages/account/orders/order-history-row.tsx +110 -0
- package/src/pages/account/orders/order-history-search-and-filter.tsx +449 -0
- package/src/pages/account/orders/order-history-search-bar.tsx +84 -0
- package/src/pages/account/orders/order-history-search-dropdown.tsx +53 -0
- package/src/pages/account/orders/order-history-sort.tsx +90 -0
- package/src/pages/account/orders/order-history-table.tsx +54 -0
- package/src/pages/account/orders/order-row.tsx +2 -2
- package/src/pages/account/orders/orders-drop-down-handler.tsx +19 -0
- package/src/pages/account/table-header.tsx +20 -0
- package/src/pages/account/table-row-skeleton.tsx +31 -0
- package/src/pages/account/wishlist/wishlist-item.tsx +4 -1
- package/src/pages/blog/blog-card.tsx +9 -3
- package/src/pages/blog/blog-detail-view.tsx +152 -0
- package/src/pages/blog/blog-list-view.tsx +59 -0
- package/src/pages/cart/estimate-shipping.tsx +6 -5
- package/src/pages/checkout/checkout-alt-form/checkout-form.tsx +43 -21
- package/src/pages/checkout/checkout-alt-form/steps/payment/payment-details.tsx +144 -19
- package/src/pages/cms-page-view.tsx +53 -0
- package/src/pages/product-details/product-description.tsx +25 -13
- package/src/pages/product-details/product-intro/compound/product-intro-images.tsx +26 -8
- package/src/pages/product-details/product-intro/compound/thumbnail-with-skeleton.tsx +17 -21
- package/src/pages/product-details/product-tabs.tsx +20 -4
- package/src/pages/quickorder/order-upload.tsx +12 -6
- package/src/pages/quickorder/quick-order.tsx +25 -20
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CustomerBalances,
|
|
3
|
+
GetCustomerBalanceInput,
|
|
4
|
+
} from "@evenicanpm/storefront-core/src/api-manager/schemas/user.schema";
|
|
5
|
+
import {
|
|
6
|
+
createApiPath,
|
|
7
|
+
createQuery,
|
|
8
|
+
} from "@evenicanpm/storefront-core/src/api-manager/services/create-query";
|
|
9
|
+
|
|
10
|
+
const apiPath = createApiPath("user", "getCustomerBalance");
|
|
11
|
+
|
|
12
|
+
const getCustomerBalance = createQuery<
|
|
13
|
+
GetCustomerBalanceInput,
|
|
14
|
+
CustomerBalances
|
|
15
|
+
>(apiPath, {
|
|
16
|
+
queryKey: "customerBalance",
|
|
17
|
+
disableProperties: ["accountNumber"],
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export default getCustomerBalance;
|
|
@@ -7,10 +7,16 @@ import type {
|
|
|
7
7
|
GetStatesInput,
|
|
8
8
|
} from "@evenicanpm/storefront-core/src/api-manager/schemas/address.schema";
|
|
9
9
|
import type * as cartSchemas from "@evenicanpm/storefront-core/src/api-manager/schemas/cart.schema";
|
|
10
|
+
import type {
|
|
11
|
+
GetInvoicesDetailsInput,
|
|
12
|
+
GetInvoicesInput,
|
|
13
|
+
Invoice,
|
|
14
|
+
InvoiceDetails,
|
|
15
|
+
} from "@evenicanpm/storefront-core/src/api-manager/schemas/invoice.schema";
|
|
10
16
|
import type {
|
|
11
17
|
GetSalesOrderDetailsInput,
|
|
18
|
+
GetSalesOrdersInput,
|
|
12
19
|
SalesOrder,
|
|
13
|
-
SalesOrderInput,
|
|
14
20
|
} from "@evenicanpm/storefront-core/src/api-manager/schemas/order.schema";
|
|
15
21
|
import type {
|
|
16
22
|
ChannelConfiguration,
|
|
@@ -47,6 +53,7 @@ export interface DatasourceApis {
|
|
|
47
53
|
order: OrderApi;
|
|
48
54
|
address: AddressApi;
|
|
49
55
|
organization: OrganizationApi;
|
|
56
|
+
invoice: InvoiceApi;
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
export abstract class Datasource {
|
|
@@ -59,6 +66,7 @@ export abstract class Datasource {
|
|
|
59
66
|
abstract address: Partial<AddressApi>;
|
|
60
67
|
abstract organization: Partial<OrganizationApi>;
|
|
61
68
|
abstract session: Partial<SessionApi>;
|
|
69
|
+
abstract invoice: Partial<InvoiceApi>;
|
|
62
70
|
|
|
63
71
|
constructor(name: string) {
|
|
64
72
|
this.name = name;
|
|
@@ -89,6 +97,9 @@ export interface UserApi {
|
|
|
89
97
|
updateUser(user: userSchemas.User): Promise<userSchemas.User>;
|
|
90
98
|
getUser(): Promise<userSchemas.User | null>;
|
|
91
99
|
getUserCounts(userId: string): Promise<userSchemas.UserCounts>;
|
|
100
|
+
getCustomerBalance(
|
|
101
|
+
input: userSchemas.GetCustomerBalanceInput,
|
|
102
|
+
): Promise<userSchemas.CustomerBalances>;
|
|
92
103
|
// wishlists
|
|
93
104
|
getWishlists(): Promise<userSchemas.Wishlist[]>;
|
|
94
105
|
getWishlistDetails(
|
|
@@ -199,7 +210,7 @@ export interface CategoryApi {
|
|
|
199
210
|
* Order Data Actions
|
|
200
211
|
*/
|
|
201
212
|
export interface OrderApi {
|
|
202
|
-
getSalesOrders(input:
|
|
213
|
+
getSalesOrders(input: GetSalesOrdersInput): Promise<SalesOrder[]>;
|
|
203
214
|
getSalesOrderDetails(input: GetSalesOrderDetailsInput): Promise<SalesOrder>;
|
|
204
215
|
}
|
|
205
216
|
|
|
@@ -214,3 +225,11 @@ export interface AddressApi {
|
|
|
214
225
|
updateAddress(address: Address): Promise<Address>;
|
|
215
226
|
deleteAddress(addressId: string): Promise<string>;
|
|
216
227
|
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Invoice Data Actions
|
|
231
|
+
*/
|
|
232
|
+
export interface InvoiceApi {
|
|
233
|
+
getInvoices(input: GetInvoicesInput): Promise<Invoice[]>;
|
|
234
|
+
getInvoiceDetails(input: GetInvoicesDetailsInput): Promise<InvoiceDetails>;
|
|
235
|
+
}
|
package/src/auth/better-auth.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { betterAuth } from "better-auth";
|
|
2
|
-
import { genericOAuth, customSession } from "better-auth/plugins";
|
|
3
2
|
import { nextCookies } from "better-auth/next-js";
|
|
3
|
+
import { customSession, genericOAuth } from "better-auth/plugins";
|
|
4
4
|
|
|
5
5
|
const tenantName = process.env.NEXT_PUBLIC_ENTRA_EXTERNAL_ID_TENANT_NAME || "";
|
|
6
6
|
const loginAuthority = `https://${tenantName}.ciamlogin.com/${tenantName}.onmicrosoft.com`;
|
|
@@ -3,12 +3,14 @@ import type { Block } from "@/cms/blocks/interfaces";
|
|
|
3
3
|
import {
|
|
4
4
|
BrandLogosScroller,
|
|
5
5
|
CategoryCarousel,
|
|
6
|
+
CtaBanner,
|
|
6
7
|
FeaturedCategories,
|
|
7
8
|
FeaturedProducts,
|
|
8
9
|
FeaturePill,
|
|
9
10
|
Footer,
|
|
10
11
|
Hero,
|
|
11
12
|
HeroCarousel,
|
|
13
|
+
HeroImage,
|
|
12
14
|
Image,
|
|
13
15
|
ProductCarousel,
|
|
14
16
|
ProductSectionFullWidth,
|
|
@@ -18,6 +20,8 @@ import {
|
|
|
18
20
|
|
|
19
21
|
const componentMap = {
|
|
20
22
|
"blocks.hero-section": Hero,
|
|
23
|
+
"blocks.hero-image": HeroImage,
|
|
24
|
+
"blocks.cta-banner": CtaBanner,
|
|
21
25
|
"blocks.features-pill": FeaturePill, // Should replace this with bazaar-react-ts/src/pages-sections/market-2/section-2/section-2.tsx
|
|
22
26
|
"blocks.featured-products": FeaturedProducts,
|
|
23
27
|
"blocks.featured-categories": FeaturedCategories,
|
|
@@ -40,8 +44,16 @@ const getBlockComponent: <B extends Block>(
|
|
|
40
44
|
) => JSX.Element | null = (block: Block) => {
|
|
41
45
|
const Component = componentMap[
|
|
42
46
|
block.__component as keyof typeof componentMap
|
|
43
|
-
] as React.ComponentType<Block
|
|
44
|
-
|
|
47
|
+
] as React.ComponentType<Block> | undefined;
|
|
48
|
+
if (!Component) {
|
|
49
|
+
console.warn(
|
|
50
|
+
`BlockManager: no component mapped for "${block.__component}"`,
|
|
51
|
+
);
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return (
|
|
55
|
+
<Component key={`block-${block.__component}-${block.id}`} {...block} />
|
|
56
|
+
);
|
|
45
57
|
};
|
|
46
58
|
|
|
47
59
|
interface BlockManagerProps {
|
|
@@ -55,7 +67,10 @@ const BlockManager: React.FC<BlockManagerProps> = ({ blocks }) => {
|
|
|
55
67
|
{blocks.map((block, idx) => {
|
|
56
68
|
const isLast = idx === blocks.length - 1;
|
|
57
69
|
return (
|
|
58
|
-
<Box
|
|
70
|
+
<Box
|
|
71
|
+
key={`block-wrapper-${block.__component}-${block.id}`}
|
|
72
|
+
mb={isLast ? 0 : 6}
|
|
73
|
+
>
|
|
59
74
|
{getBlockComponent(block, idx)}
|
|
60
75
|
</Box>
|
|
61
76
|
);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Box, Button, Container, Typography } from "@mui/material";
|
|
2
|
+
import NextLink from "next/link";
|
|
3
|
+
import type { CtaBannerBlock } from "@/cms/blocks/interfaces";
|
|
4
|
+
|
|
5
|
+
const CtaBanner = ({
|
|
6
|
+
heading,
|
|
7
|
+
subtext,
|
|
8
|
+
buttonText,
|
|
9
|
+
buttonLink,
|
|
10
|
+
backgroundColor,
|
|
11
|
+
}: CtaBannerBlock) => {
|
|
12
|
+
const bgColor = backgroundColor || "#1a237e";
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Box
|
|
16
|
+
sx={{
|
|
17
|
+
backgroundColor: bgColor,
|
|
18
|
+
color: "#fff",
|
|
19
|
+
py: { xs: 6, md: 8 },
|
|
20
|
+
px: 2,
|
|
21
|
+
textAlign: "center",
|
|
22
|
+
width: "100%",
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
<Container maxWidth="md">
|
|
26
|
+
<Typography
|
|
27
|
+
variant="h3"
|
|
28
|
+
component="h2"
|
|
29
|
+
sx={{ fontWeight: 700, mb: subtext ? 2 : 3, color: "#fff" }}
|
|
30
|
+
>
|
|
31
|
+
{heading}
|
|
32
|
+
</Typography>
|
|
33
|
+
{subtext && (
|
|
34
|
+
<Typography
|
|
35
|
+
variant="h6"
|
|
36
|
+
sx={{ color: "rgba(255,255,255,0.85)", mb: 3, fontWeight: 400 }}
|
|
37
|
+
>
|
|
38
|
+
{subtext}
|
|
39
|
+
</Typography>
|
|
40
|
+
)}
|
|
41
|
+
{buttonText && buttonLink && (
|
|
42
|
+
<Button
|
|
43
|
+
component={NextLink}
|
|
44
|
+
href={buttonLink}
|
|
45
|
+
variant="contained"
|
|
46
|
+
size="large"
|
|
47
|
+
sx={{
|
|
48
|
+
backgroundColor: "#fff",
|
|
49
|
+
color: bgColor,
|
|
50
|
+
fontWeight: 700,
|
|
51
|
+
px: 4,
|
|
52
|
+
"&:hover": {
|
|
53
|
+
backgroundColor: "rgba(255,255,255,0.9)",
|
|
54
|
+
},
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
{buttonText}
|
|
58
|
+
</Button>
|
|
59
|
+
)}
|
|
60
|
+
</Container>
|
|
61
|
+
</Box>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export default CtaBanner;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { FooterBlock } from "@evenicanpm/cms/src/content/footer/types";
|
|
2
|
+
import { Box, Container } from "@mui/material";
|
|
3
|
+
import Grid from "@mui/material/Grid2";
|
|
4
|
+
import Contact from "@/cms/blocks/components/footer/sections/footer-contact";
|
|
5
|
+
import Links from "@/cms/blocks/components/footer/sections/footer-links";
|
|
6
|
+
import Logo from "@/cms/blocks/components/footer/sections/footer-logo";
|
|
7
|
+
|
|
8
|
+
const getBlockComponent = (block: FooterBlock, index: number) => {
|
|
9
|
+
switch (block.__component) {
|
|
10
|
+
case "footer.links":
|
|
11
|
+
return (
|
|
12
|
+
<Links key={`index-${index}`} title={block.title} links={block.links} />
|
|
13
|
+
);
|
|
14
|
+
case "footer.logo":
|
|
15
|
+
return (
|
|
16
|
+
<Logo
|
|
17
|
+
key={`index-${index}`}
|
|
18
|
+
logoImage={block.logoImage}
|
|
19
|
+
description={block.description}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
case "footer.contact":
|
|
23
|
+
return (
|
|
24
|
+
<Contact
|
|
25
|
+
key={`index-${index}`}
|
|
26
|
+
address={block.address}
|
|
27
|
+
email={block.email}
|
|
28
|
+
phoneNumber={block.phoneNumber}
|
|
29
|
+
social={block.social}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
default:
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const FooterContent = ({ sections }: { sections: FooterBlock[] }) => {
|
|
38
|
+
if (!sections?.length) return null;
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Box
|
|
42
|
+
component="footer"
|
|
43
|
+
bgcolor="#222935"
|
|
44
|
+
mt={{ xs: 7 }}
|
|
45
|
+
mb={{ sm: 0, xs: 7 }}
|
|
46
|
+
>
|
|
47
|
+
<Box
|
|
48
|
+
component={Container}
|
|
49
|
+
color="white"
|
|
50
|
+
overflow="hidden"
|
|
51
|
+
py={{ sm: 10, xs: 4 }}
|
|
52
|
+
>
|
|
53
|
+
<Grid container spacing={6}>
|
|
54
|
+
{sections.map((block, index) => getBlockComponent(block, index))}
|
|
55
|
+
</Grid>
|
|
56
|
+
</Box>
|
|
57
|
+
</Box>
|
|
58
|
+
);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export default FooterContent;
|
|
@@ -1,86 +1,24 @@
|
|
|
1
1
|
// MUI
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
FooterBlock,
|
|
6
|
-
FooterResponse,
|
|
7
|
-
} from "@/cms/blocks/components/footer/interfaces";
|
|
8
|
-
import Contact from "@/cms/blocks/components/footer/sections/footer-contact";
|
|
9
|
-
import Links from "@/cms/blocks/components/footer/sections/footer-links";
|
|
10
|
-
// Local Custom Components
|
|
11
|
-
import Logo from "@/cms/blocks/components/footer/sections/footer-logo";
|
|
12
|
-
import cms from "@/cms/endpoints";
|
|
2
|
+
import { cms } from "@evenicanpm/cms";
|
|
3
|
+
import type { FooterBlock } from "@evenicanpm/cms/src/content/footer/types";
|
|
4
|
+
import FooterContent from "@/cms/blocks/components/footer/footer-content";
|
|
13
5
|
|
|
14
|
-
const
|
|
15
|
-
let
|
|
16
|
-
|
|
17
|
-
switch (block.__component) {
|
|
18
|
-
case "footer.links":
|
|
19
|
-
Block = Links;
|
|
20
|
-
return (
|
|
21
|
-
<Block key={`index-${index}`} title={block.title} links={block.links} />
|
|
22
|
-
);
|
|
23
|
-
case "footer.logo":
|
|
24
|
-
Block = Logo;
|
|
25
|
-
return <Block key={`index-${index}`} description={block.description} />;
|
|
26
|
-
case "footer.contact":
|
|
27
|
-
Block = Contact;
|
|
28
|
-
return <Block key={`index-${index}`} social={block.social} />;
|
|
29
|
-
default:
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const fetchFooterData = async (): Promise<FooterResponse | null> => {
|
|
6
|
+
const Footer = async () => {
|
|
7
|
+
let sections: FooterBlock[] | undefined;
|
|
35
8
|
try {
|
|
36
|
-
const response = await
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if (!response.ok) {
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const text = await response.text();
|
|
45
|
-
|
|
46
|
-
if (!text) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return JSON.parse(text) as FooterResponse;
|
|
9
|
+
const response = await cms.footer.getFooter();
|
|
10
|
+
sections = (response as { data?: { sections?: FooterBlock[] } })?.data
|
|
11
|
+
?.sections;
|
|
51
12
|
} catch (error) {
|
|
52
13
|
console.error("Error fetching footer data:", error);
|
|
53
14
|
return null;
|
|
54
15
|
}
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const Footer = async () => {
|
|
58
|
-
const data = await fetchFooterData();
|
|
59
|
-
const sections = data?.data?.sections;
|
|
60
16
|
|
|
61
17
|
if (!sections?.length) {
|
|
62
18
|
return null;
|
|
63
19
|
}
|
|
64
20
|
|
|
65
|
-
return
|
|
66
|
-
<Box
|
|
67
|
-
component="footer"
|
|
68
|
-
bgcolor="#222935"
|
|
69
|
-
mt={{ xs: 7 }}
|
|
70
|
-
mb={{ sm: 0, xs: 7 }}
|
|
71
|
-
>
|
|
72
|
-
<Box
|
|
73
|
-
component={Container}
|
|
74
|
-
color="white"
|
|
75
|
-
overflow="hidden"
|
|
76
|
-
py={{ sm: 10, xs: 4 }}
|
|
77
|
-
>
|
|
78
|
-
<Grid container spacing={6}>
|
|
79
|
-
{sections.map((block, index) => getBlockComponent(block, index))}
|
|
80
|
-
</Grid>
|
|
81
|
-
</Box>
|
|
82
|
-
</Box>
|
|
83
|
-
);
|
|
21
|
+
return <FooterContent sections={sections} />;
|
|
84
22
|
};
|
|
85
23
|
|
|
86
24
|
export default Footer;
|
|
@@ -9,6 +9,7 @@ export interface SocialLink {
|
|
|
9
9
|
interface FooterLogoBlock {
|
|
10
10
|
id: number;
|
|
11
11
|
__component: "footer.logo";
|
|
12
|
+
logoImage?: { url: string; alternativeText?: string };
|
|
12
13
|
description: string;
|
|
13
14
|
}
|
|
14
15
|
|
|
@@ -22,6 +23,9 @@ interface FooterLinksBlock {
|
|
|
22
23
|
interface FooterContactBlock {
|
|
23
24
|
id: number;
|
|
24
25
|
__component: "footer.contact";
|
|
26
|
+
address?: string;
|
|
27
|
+
email?: string;
|
|
28
|
+
phoneNumber?: string;
|
|
25
29
|
social: SocialLink[];
|
|
26
30
|
}
|
|
27
31
|
|
|
@@ -7,6 +7,9 @@ import { Heading } from "@/cms/blocks/components/footer/styles/index";
|
|
|
7
7
|
interface Props {
|
|
8
8
|
isDark?: boolean;
|
|
9
9
|
title?: string;
|
|
10
|
+
address?: string;
|
|
11
|
+
email?: string;
|
|
12
|
+
phoneNumber?: string;
|
|
10
13
|
social?: SocialLink[];
|
|
11
14
|
}
|
|
12
15
|
|
|
@@ -15,17 +18,23 @@ const AboutLinks = (props: Props) => {
|
|
|
15
18
|
<Grid size={{ lg: 3, md: 6, sm: 6, xs: 12 }}>
|
|
16
19
|
<Heading>Contact Us</Heading>
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
{props.address && (
|
|
22
|
+
<Paragraph py={0.6} color="grey.500">
|
|
23
|
+
{props.address}
|
|
24
|
+
</Paragraph>
|
|
25
|
+
)}
|
|
21
26
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
{props.email && (
|
|
28
|
+
<Paragraph py={0.6} color="grey.500">
|
|
29
|
+
Email: {props.email}
|
|
30
|
+
</Paragraph>
|
|
31
|
+
)}
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
{props.phoneNumber && (
|
|
34
|
+
<Paragraph py={0.6} mb={2} color="grey.500">
|
|
35
|
+
Phone: {props.phoneNumber}
|
|
36
|
+
</Paragraph>
|
|
37
|
+
)}
|
|
29
38
|
|
|
30
39
|
<SocialLinks socials={props.social} />
|
|
31
40
|
</Grid>
|
|
@@ -2,20 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
import SiteLogo from "@evenicanpm/storefront-core/src/components/site-logo";
|
|
4
4
|
import { Paragraph } from "@evenicanpm/storefront-core/src/components/Typography";
|
|
5
|
+
import { Box } from "@mui/material";
|
|
5
6
|
import Grid from "@mui/material/Grid2";
|
|
6
7
|
import useTheme from "@mui/material/styles/useTheme";
|
|
7
8
|
import useMediaQuery from "@mui/material/useMediaQuery";
|
|
9
|
+
import cmsEndPoints from "@/cms/endpoints";
|
|
8
10
|
|
|
9
11
|
interface Props {
|
|
12
|
+
logoImage?: { url: string; alternativeText?: string };
|
|
10
13
|
description: string;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
export default function LogoSection(props: Readonly<Props>) {
|
|
14
17
|
const theme = useTheme();
|
|
15
|
-
const downMd = useMediaQuery(theme.breakpoints.down(1150));
|
|
18
|
+
const downMd = useMediaQuery(theme.breakpoints.down(1150));
|
|
19
|
+
const logoHeight = downMd ? "2.125rem" : "3rem";
|
|
20
|
+
|
|
16
21
|
return (
|
|
17
22
|
<Grid size={{ lg: 3, md: 6, sm: 6, xs: 12 }}>
|
|
18
|
-
|
|
23
|
+
{props.logoImage?.url ? (
|
|
24
|
+
<Box mb={1}>
|
|
25
|
+
<img
|
|
26
|
+
src={cmsEndPoints.buildCmsMediaUrl(props.logoImage.url)}
|
|
27
|
+
alt={props.logoImage.alternativeText || "Logo"}
|
|
28
|
+
style={{ height: logoHeight, width: "auto" }}
|
|
29
|
+
/>
|
|
30
|
+
</Box>
|
|
31
|
+
) : (
|
|
32
|
+
<SiteLogo {...(downMd ? { height: "2.125rem" } : {})} />
|
|
33
|
+
)}
|
|
19
34
|
|
|
20
35
|
<Paragraph mb={2.5} color="grey.500">
|
|
21
36
|
{props.description}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Box, Button, Container, Typography } from "@mui/material";
|
|
2
|
+
import NextImage from "next/image";
|
|
3
|
+
import NextLink from "next/link";
|
|
4
|
+
import type { HeroImageBlock } from "@/cms/blocks/interfaces";
|
|
5
|
+
|
|
6
|
+
const sxBanner = {
|
|
7
|
+
position: "relative",
|
|
8
|
+
backgroundColor: "grey.900",
|
|
9
|
+
color: "white",
|
|
10
|
+
height: 500,
|
|
11
|
+
width: "100%",
|
|
12
|
+
display: "flex",
|
|
13
|
+
alignItems: "center",
|
|
14
|
+
justifyContent: "center",
|
|
15
|
+
overflow: "hidden",
|
|
16
|
+
marginBottom: "50px",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const contentContainer = {
|
|
20
|
+
position: "absolute",
|
|
21
|
+
top: 0,
|
|
22
|
+
left: 0,
|
|
23
|
+
right: 0,
|
|
24
|
+
bottom: 0,
|
|
25
|
+
display: "flex",
|
|
26
|
+
flexDirection: "column",
|
|
27
|
+
justifyContent: "center",
|
|
28
|
+
alignItems: "center",
|
|
29
|
+
zIndex: 2,
|
|
30
|
+
textAlign: "center",
|
|
31
|
+
padding: "0 20px",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const overlayStyle = {
|
|
35
|
+
position: "absolute",
|
|
36
|
+
inset: 0,
|
|
37
|
+
backgroundColor: "rgba(10,25,60,0.55)",
|
|
38
|
+
zIndex: 1,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const HeroImage = ({
|
|
42
|
+
heading,
|
|
43
|
+
subheading,
|
|
44
|
+
buttonText,
|
|
45
|
+
buttonLink,
|
|
46
|
+
image,
|
|
47
|
+
}: HeroImageBlock) => {
|
|
48
|
+
const imageUrl = image?.url;
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<Box sx={sxBanner}>
|
|
52
|
+
{imageUrl && (
|
|
53
|
+
<NextImage
|
|
54
|
+
src={imageUrl}
|
|
55
|
+
alt={heading}
|
|
56
|
+
fill
|
|
57
|
+
style={{ objectFit: "cover", objectPosition: "center" }}
|
|
58
|
+
priority
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
61
|
+
<Box sx={overlayStyle} />
|
|
62
|
+
<Container sx={contentContainer}>
|
|
63
|
+
<Typography variant="h1" gutterBottom>
|
|
64
|
+
{heading}
|
|
65
|
+
</Typography>
|
|
66
|
+
{subheading && (
|
|
67
|
+
<Typography variant="h5" gutterBottom sx={{ marginBottom: "30px" }}>
|
|
68
|
+
{subheading}
|
|
69
|
+
</Typography>
|
|
70
|
+
)}
|
|
71
|
+
{buttonText && buttonLink && (
|
|
72
|
+
<NextLink href={buttonLink} passHref>
|
|
73
|
+
<Button color="primary" size="large" variant="contained">
|
|
74
|
+
{buttonText}
|
|
75
|
+
</Button>
|
|
76
|
+
</NextLink>
|
|
77
|
+
)}
|
|
78
|
+
</Container>
|
|
79
|
+
</Box>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export default HeroImage;
|
package/src/cms/blocks/index.tsx
CHANGED
|
@@ -14,3 +14,5 @@ export { default as ProductCarousel } from "@/cms/blocks/components/product-caro
|
|
|
14
14
|
export { default as ProductSectionFullWidth } from "@/cms/blocks/components/product-section-fullwidth/index";
|
|
15
15
|
export { default as RichText } from "@/cms/blocks/components/rich-text";
|
|
16
16
|
export { default as Services } from "@/cms/blocks/components/services/index";
|
|
17
|
+
export { default as CtaBanner } from "./components/cta-banner";
|
|
18
|
+
export { default as HeroImage } from "./components/hero-image";
|
|
@@ -141,6 +141,26 @@ export interface RichTextBlock {
|
|
|
141
141
|
content: string;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
export interface HeroImageBlock {
|
|
145
|
+
id: number;
|
|
146
|
+
__component: "blocks.hero-image";
|
|
147
|
+
heading: string;
|
|
148
|
+
subheading?: string | null;
|
|
149
|
+
buttonText?: string | null;
|
|
150
|
+
buttonLink?: string | null;
|
|
151
|
+
image?: Media | null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface CtaBannerBlock {
|
|
155
|
+
id: number;
|
|
156
|
+
__component: "blocks.cta-banner";
|
|
157
|
+
heading: string;
|
|
158
|
+
subtext?: string | null;
|
|
159
|
+
buttonText?: string | null;
|
|
160
|
+
buttonLink?: string | null;
|
|
161
|
+
backgroundColor?: string | null;
|
|
162
|
+
}
|
|
163
|
+
|
|
144
164
|
export interface ImageFormat {
|
|
145
165
|
ext: string;
|
|
146
166
|
url: string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Box, Chip } from "@mui/material";
|
|
2
|
+
|
|
3
|
+
export function DraftModeBadge() {
|
|
4
|
+
return (
|
|
5
|
+
<Box
|
|
6
|
+
sx={{
|
|
7
|
+
position: "fixed",
|
|
8
|
+
bottom: 16,
|
|
9
|
+
right: 16,
|
|
10
|
+
zIndex: 9999,
|
|
11
|
+
}}
|
|
12
|
+
>
|
|
13
|
+
<Chip
|
|
14
|
+
label="Draft Mode"
|
|
15
|
+
size="small"
|
|
16
|
+
sx={{
|
|
17
|
+
backgroundColor: "#f59e0b",
|
|
18
|
+
color: "#000",
|
|
19
|
+
fontWeight: 700,
|
|
20
|
+
fontSize: "0.75rem",
|
|
21
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.3)",
|
|
22
|
+
}}
|
|
23
|
+
/>
|
|
24
|
+
</Box>
|
|
25
|
+
);
|
|
26
|
+
}
|