Skip to content

Commit f38b60d

Browse files
authored
Merge pull request #159 from contentstack/development
Development
2 parents 002e50f + 917f9d1 commit f38b60d

25 files changed

Lines changed: 5081 additions & 1640 deletions

.eslintrc.cjs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* @type {import("@types/eslint").Linter.BaseConfig}
3+
*/
4+
module.exports = {
5+
extends: [
6+
'@remix-run/eslint-config',
7+
'plugin:hydrogen/recommended',
8+
'plugin:hydrogen/typescript',
9+
],
10+
rules: {
11+
'@typescript-eslint/ban-ts-comment': 'off',
12+
'@typescript-eslint/naming-convention': 'off',
13+
'hydrogen/prefer-image-component': 'off',
14+
'no-useless-escape': 'off',
15+
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
16+
'no-case-declarations': 'off',
17+
},
18+
};

CODEOWNERS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
* @contentstack/tso-integration-pr-reviewers
1+
* @contentstack/marketplace-solutions-pr-reviewers
22

33
.github/workflows/sca-scan.yml @contentstack/security-admin
44

app/components/Footer.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,12 @@ const MenuSection = ({
7979
export function Footer(fetchdata: any) {
8080
const footerMetaObject = fetchdata?.footerMetaObject;
8181
const footerData = footerMetaObject?.metaobjects?.nodes?.[0]?.fields;
82-
const sortedFooterData = footerData.sort((a: any, b: any) => {
83-
return MENU_ORDER.indexOf(a.key) - MENU_ORDER.indexOf(b.key);
84-
});
82+
const sortedFooterData = Array.isArray(footerData)
83+
? [...footerData].sort(
84+
(a: any, b: any) =>
85+
MENU_ORDER.indexOf(a.key) - MENU_ORDER.indexOf(b.key),
86+
)
87+
: [];
8588

8689
return (
8790
<footer className="footer-wrapper">

app/components/Header.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export function HeaderMenu({
3939
primaryDomainUrl: HeaderQuery['shop']['primaryDomain']['url'];
4040
viewport: Viewport;
4141
}) {
42-
const {publicStoreDomain} = useRootLoaderData();
42+
const publicStoreDomain = useRootLoaderData()?.publicStoreDomain ?? '';
4343
const className = `header-menu-${viewport}`;
4444

4545
function closeAside(event: React.MouseEvent<HTMLAnchorElement>) {
@@ -65,13 +65,24 @@ export function HeaderMenu({
6565
{(menu || FALLBACK_HEADER_MENU).items.map((item) => {
6666
if (!item.url) return null;
6767

68-
// if the url is internal, we strip the domain
69-
const url =
68+
const isStoreHost =
7069
item.url.includes('myshopify.com') ||
71-
item.url.includes(publicStoreDomain) ||
72-
item.url.includes(primaryDomainUrl)
73-
? new URL(item.url).pathname
74-
: item.url;
70+
(publicStoreDomain !== '' && item.url.includes(publicStoreDomain)) ||
71+
(primaryDomainUrl !== '' && item.url.includes(primaryDomainUrl));
72+
73+
// Relative menu paths (e.g. /collections) are already fine for <NavLink to>.
74+
// new URL(relative) throws without a base; only parse absolute store URLs.
75+
let url = item.url;
76+
if (
77+
isStoreHost &&
78+
(item.url.startsWith('http://') || item.url.startsWith('https://'))
79+
) {
80+
try {
81+
url = new URL(item.url).pathname;
82+
} catch {
83+
/* keep original */
84+
}
85+
}
7586
return (
7687
<NavLink
7788
className="header-menu-item"

app/components/Layout.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ export type LayoutProps = {
2020
footer: Promise<FooterQuery>;
2121
header: HeaderQuery;
2222
isLoggedIn: boolean;
23-
fetchData: any;
24-
footerMetaObject: any;
23+
/** Passed through on error boundaries; unused by layout chrome. */
24+
fetchData?: unknown;
25+
footerMetaObject?: unknown;
2526
};
2627

2728
export function Layout({

app/entry.server.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type {EntryContext} from '@shopify/remix-oxygen';
22
import {RemixServer} from '@remix-run/react';
3-
import isbot from 'isbot';
4-
import {renderToReadableStream} from 'react-dom/server';
3+
import {isbot} from 'isbot';
4+
import {renderToReadableStream} from 'react-dom/server.browser';
55
import {createContentSecurityPolicy} from '@shopify/hydrogen';
66

77
export default async function handleRequest(
@@ -12,19 +12,18 @@ export default async function handleRequest(
1212
) {
1313
const {nonce, header, NonceProvider} = createContentSecurityPolicy();
1414

15-
//Sanitize URL to prevent open redirect
15+
// Sanitize URL (path + query only)
1616
const url = new URL(request.url);
17-
const safeUrl = `${url.pathname}${url.search}`;
17+
const safeUrl = new URL(`${url.pathname}${url.search}`, url.origin);
1818

1919
const body = await renderToReadableStream(
2020
<NonceProvider>
21-
<RemixServer context={remixContext} url={safeUrl} />
21+
<RemixServer context={remixContext} url={safeUrl.toString()} />
2222
</NonceProvider>,
2323
{
2424
nonce,
2525
signal: request.signal,
2626
onError(error) {
27-
// eslint-disable-next-line no-console
2827
console.error(error);
2928
responseStatusCode = 500;
3029
},

app/root.tsx

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
11
import {useNonce} from '@shopify/hydrogen';
2-
import {
3-
defer,
4-
type SerializeFrom,
5-
type LoaderFunctionArgs,
6-
} from '@shopify/remix-oxygen';
2+
import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
73
import {
84
Links,
95
Meta,
106
Outlet,
117
Scripts,
12-
useMatches,
8+
useRouteLoaderData,
139
useRouteError,
1410
useLoaderData,
1511
ScrollRestoration,
1612
isRouteErrorResponse,
1713
type ShouldRevalidateFunction,
1814
} from '@remix-run/react';
1915
import type {CustomerAccessToken} from '@shopify/hydrogen/storefront-api-types';
16+
import type {
17+
CartApiQueryFragment,
18+
FooterQuery,
19+
HeaderQuery,
20+
RootMetaObjectQuery,
21+
} from 'storefrontapi.generated';
2022
import favicon from '../public/favicon.svg';
21-
import resetStyles from './styles/reset.css';
22-
import appStyles from './styles/app.css';
23+
import './styles/reset.css';
24+
import './styles/app.css';
25+
2326
import {Layout} from '~/components/Layout';
2427

2528
/**
@@ -45,8 +48,8 @@ export const shouldRevalidate: ShouldRevalidateFunction = ({
4548

4649
export function links() {
4750
return [
48-
{rel: 'stylesheet', href: resetStyles},
49-
{rel: 'stylesheet', href: appStyles},
51+
{ rel: 'stylesheet', href: '/app/styles/reset.css' },
52+
{ rel: 'stylesheet', href: '/app/styles/app.css' },
5053
{
5154
rel: 'preconnect',
5255
href: 'https://cdn.shopify.com',
@@ -55,13 +58,34 @@ export function links() {
5558
rel: 'preconnect',
5659
href: 'https://shop.app',
5760
},
58-
{rel: 'icon', type: 'image/svg+xml', href: favicon},
61+
{ rel: 'icon', type: 'image/svg+xml', href: favicon },
5962
];
6063
}
6164

65+
66+
/**
67+
* Remix `SerializeFrom` loses top-level keys on `defer()` payloads; this matches runtime loader data.
68+
*/
69+
export type RootLoaderClientData = {
70+
cart: Promise<CartApiQueryFragment | null>;
71+
footer: Promise<FooterQuery>;
72+
header: HeaderQuery;
73+
isLoggedIn: boolean;
74+
publicStoreDomain: string;
75+
footerMetaObject: RootMetaObjectQuery;
76+
};
77+
78+
const FALLBACK_HEADER: HeaderQuery = {
79+
shop: {
80+
id: 'gid://shopify/Shop/0',
81+
name: '',
82+
description: '',
83+
primaryDomain: {url: ''},
84+
},
85+
};
86+
6287
export const useRootLoaderData = () => {
63-
const [root] = useMatches();
64-
return root?.data as SerializeFrom<typeof loader>;
88+
return useRouteLoaderData('root') as unknown as RootLoaderClientData | undefined;
6589
};
6690

6791
export async function loader({context}: LoaderFunctionArgs) {
@@ -110,7 +134,7 @@ export async function loader({context}: LoaderFunctionArgs) {
110134

111135
export default function App() {
112136
const nonce = useNonce();
113-
const data = useLoaderData<typeof loader>();
137+
const data = useLoaderData<typeof loader>() as unknown as RootLoaderClientData;
114138
return (
115139
<html lang="en">
116140
<head>
@@ -120,7 +144,13 @@ export default function App() {
120144
<Links />
121145
</head>
122146
<body>
123-
<Layout footerMetaObject={data?.footerMetaObject} {...data}>
147+
<Layout
148+
cart={data.cart}
149+
footer={data.footer}
150+
header={data.header}
151+
isLoggedIn={data.isLoggedIn}
152+
footerMetaObject={data.footerMetaObject}
153+
>
124154
<Outlet />
125155
</Layout>
126156
<ScrollRestoration nonce={nonce} />
@@ -153,7 +183,14 @@ export function ErrorBoundary() {
153183
<Links />
154184
</head>
155185
<body>
156-
<Layout {...rootData}>
186+
<Layout
187+
fetchData={rootData}
188+
cart={rootData?.cart ?? Promise.resolve(null)}
189+
footer={rootData?.footer ?? Promise.resolve({} as FooterQuery)}
190+
header={rootData?.header ?? FALLBACK_HEADER}
191+
isLoggedIn={rootData?.isLoggedIn ?? false}
192+
footerMetaObject={rootData?.footerMetaObject}
193+
>
157194
<div className="route-error">
158195
<h1>Oops</h1>
159196
<h2>{errorStatus}</h2>
@@ -278,7 +315,7 @@ const FOOTER_QUERY = `#graphql
278315
` as const;
279316

280317
const FOOTER_META_OBJECT_QUERY = `#graphql
281-
query MetaObject($country: CountryCode, $language: LanguageCode)
318+
query RootMetaObject($country: CountryCode, $language: LanguageCode)
282319
@inContext(country: $country, language: $language) {
283320
metaobjects(first: 100, type: "footer") {
284321
nodes {

app/routes/($locale)._index.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ function RecommendedProducts({
162162
<>
163163
{bannerField?.key === 'heading' && (
164164
<h5 className="page-banner-heading">
165-
{bannerField?.value}
165+
{bannerField?.value} :
166166
</h5>
167167
)}
168168
{bannerField?.key === 'title' && (
@@ -1092,7 +1092,7 @@ const COLLECTIONS_QUERY = `#graphql
10921092
` as const;
10931093

10941094
const NEW_ARRIVALS_QUERY = `#graphql
1095-
query FeaturedCollection($country: CountryCode, $language: LanguageCode)
1095+
query FeaturedCollectionNewArrivals($country: CountryCode, $language: LanguageCode)
10961096
@inContext(country: $country, language: $language) {
10971097
collection(handle: "new-arrivals") {
10981098
handle
@@ -1116,7 +1116,7 @@ query FeaturedCollection($country: CountryCode, $language: LanguageCode)
11161116
}` as const;
11171117

11181118
const TOP_CATEGORIES_QUERY = `#graphql
1119-
query FeaturedCollection($country: CountryCode, $language: LanguageCode)
1119+
query FeaturedCollectionTopCategories($country: CountryCode, $language: LanguageCode)
11201120
@inContext(country: $country, language: $language) {
11211121
collections(first:8, reverse:true) {
11221122
edges {
@@ -1148,7 +1148,7 @@ const TOP_CATEGORIES_QUERY = `#graphql
11481148
` as const;
11491149

11501150
const BEST_SELLER_QUERY = `#graphql
1151-
query FeaturedCollection($country: CountryCode, $language: LanguageCode)
1151+
query FeaturedCollectionBestSeller($country: CountryCode, $language: LanguageCode)
11521152
@inContext(country: $country, language: $language) {
11531153
collection(handle: "womens-fashion") {
11541154
handle
@@ -1172,7 +1172,7 @@ query FeaturedCollection($country: CountryCode, $language: LanguageCode)
11721172
}` as const;
11731173

11741174
const META_OBJECT_QUERY = `#graphql
1175-
query MetaObject($country: CountryCode, $language: LanguageCode)
1175+
query MetaObjectHome($country: CountryCode, $language: LanguageCode)
11761176
@inContext(country: $country, language: $language) {
11771177
metaobjects(first: 100, type: "home") {
11781178
nodes {

app/routes/($locale).about.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ function RecommendedProducts({metaObject}: {metaObject: any}) {
5858
}
5959

6060
const META_OBJECT_QUERY = `#graphql
61-
query MetaObject($country: CountryCode, $language: LanguageCode)
61+
query MetaObjectAbout($country: CountryCode, $language: LanguageCode)
6262
@inContext(country: $country, language: $language) {
6363
metaobjects(first: 100, type: "about_us") {
6464
nodes {

app/routes/($locale).collections._index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ const COLLECTIONS_QUERY = `#graphql
114114
height
115115
}
116116
}
117-
query StoreCollections(
117+
query StoreCollectionsIndex(
118118
$country: CountryCode
119119
$endCursor: String
120120
$first: Int
@@ -142,7 +142,7 @@ const COLLECTIONS_QUERY = `#graphql
142142
` as const;
143143

144144
const HEADING_QUERY = `#graphql
145-
query MetaObject($country: CountryCode, $language: LanguageCode)
145+
query MetaObjectCollectionsPageHeading($country: CountryCode, $language: LanguageCode)
146146
@inContext(country: $country, language: $language) {
147147
metaobjects(first: 100, type: "product_page_contents") {
148148
nodes {

0 commit comments

Comments
 (0)