Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
* @contentstack/tso-integration-pr-reviewers
* @contentstack/marketplace-solutions-pr-reviewers

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

Expand Down
9 changes: 6 additions & 3 deletions app/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,12 @@ const MenuSection = ({
export function Footer(fetchdata: any) {
const footerMetaObject = fetchdata?.footerMetaObject;
const footerData = footerMetaObject?.metaobjects?.nodes?.[0]?.fields;
const sortedFooterData = footerData.sort((a: any, b: any) => {
return MENU_ORDER.indexOf(a.key) - MENU_ORDER.indexOf(b.key);
});
const sortedFooterData = Array.isArray(footerData)
? [...footerData].sort(
(a: any, b: any) =>
MENU_ORDER.indexOf(a.key) - MENU_ORDER.indexOf(b.key),
)
: [];

return (
<footer className="footer-wrapper">
Expand Down
25 changes: 18 additions & 7 deletions app/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function HeaderMenu({
primaryDomainUrl: HeaderQuery['shop']['primaryDomain']['url'];
viewport: Viewport;
}) {
const {publicStoreDomain} = useRootLoaderData();
const publicStoreDomain = useRootLoaderData()?.publicStoreDomain ?? '';
const className = `header-menu-${viewport}`;

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

// if the url is internal, we strip the domain
const url =
const isStoreHost =
item.url.includes('myshopify.com') ||
item.url.includes(publicStoreDomain) ||
item.url.includes(primaryDomainUrl)
? new URL(item.url).pathname
: item.url;
(publicStoreDomain !== '' && item.url.includes(publicStoreDomain)) ||
(primaryDomainUrl !== '' && item.url.includes(primaryDomainUrl));

// Relative menu paths (e.g. /collections) are already fine for <NavLink to>.
// new URL(relative) throws without a base; only parse absolute store URLs.
let url = item.url;
if (
isStoreHost &&
(item.url.startsWith('http://') || item.url.startsWith('https://'))
) {
try {
url = new URL(item.url).pathname;
} catch {
/* keep original */
}
}
return (
<NavLink
className="header-menu-item"
Expand Down
5 changes: 3 additions & 2 deletions app/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ export type LayoutProps = {
footer: Promise<FooterQuery>;
header: HeaderQuery;
isLoggedIn: boolean;
fetchData: any;
footerMetaObject: any;
/** Passed through on error boundaries; unused by layout chrome. */
fetchData?: unknown;
footerMetaObject?: unknown;
};

export function Layout({
Expand Down
11 changes: 5 additions & 6 deletions app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {EntryContext} from '@shopify/remix-oxygen';
import {RemixServer} from '@remix-run/react';
import isbot from 'isbot';
import {renderToReadableStream} from 'react-dom/server';
import {isbot} from 'isbot';
import {renderToReadableStream} from 'react-dom/server.browser';
import {createContentSecurityPolicy} from '@shopify/hydrogen';

export default async function handleRequest(
Expand All @@ -12,19 +12,18 @@ export default async function handleRequest(
) {
const {nonce, header, NonceProvider} = createContentSecurityPolicy();

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

const body = await renderToReadableStream(
<NonceProvider>
<RemixServer context={remixContext} url={safeUrl} />
<RemixServer context={remixContext} url={safeUrl.toString()} />
</NonceProvider>,
{
nonce,
signal: request.signal,
onError(error) {
// eslint-disable-next-line no-console
console.error(error);
responseStatusCode = 500;
},
Expand Down
75 changes: 54 additions & 21 deletions app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import {useNonce} from '@shopify/hydrogen';
import {
defer,
type SerializeFrom,
type LoaderFunctionArgs,
} from '@shopify/remix-oxygen';
import {defer, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
import {
Links,
Meta,
Outlet,
Scripts,
LiveReload,
useMatches,
useRouteLoaderData,
useRouteError,
useLoaderData,
ScrollRestoration,
isRouteErrorResponse,
type ShouldRevalidateFunction,
} from '@remix-run/react';
import type {CustomerAccessToken} from '@shopify/hydrogen/storefront-api-types';
import type {
CartApiQueryFragment,
FooterQuery,
HeaderQuery,
RootMetaObjectQuery,
} from 'storefrontapi.generated';
import favicon from '../public/favicon.svg';
import resetStyles from './styles/reset.css';
import appStyles from './styles/app.css';
import './styles/reset.css';
import './styles/app.css';

import {Layout} from '~/components/Layout';

/**
Expand All @@ -46,9 +48,8 @@ export const shouldRevalidate: ShouldRevalidateFunction = ({

export function links() {
return [
...(cssBundleHref ? [{rel: 'stylesheet', href: cssBundleHref}] : []),
{rel: 'stylesheet', href: resetStyles},
{rel: 'stylesheet', href: appStyles},
{ rel: 'stylesheet', href: '/app/styles/reset.css' },
{ rel: 'stylesheet', href: '/app/styles/app.css' },
{
rel: 'preconnect',
href: 'https://cdn.shopify.com',
Expand All @@ -57,13 +58,34 @@ export function links() {
rel: 'preconnect',
href: 'https://shop.app',
},
{rel: 'icon', type: 'image/svg+xml', href: favicon},
{ rel: 'icon', type: 'image/svg+xml', href: favicon },
];
}


/**
* Remix `SerializeFrom` loses top-level keys on `defer()` payloads; this matches runtime loader data.
*/
export type RootLoaderClientData = {
cart: Promise<CartApiQueryFragment | null>;
footer: Promise<FooterQuery>;
header: HeaderQuery;
isLoggedIn: boolean;
publicStoreDomain: string;
footerMetaObject: RootMetaObjectQuery;
};

const FALLBACK_HEADER: HeaderQuery = {
shop: {
id: 'gid://shopify/Shop/0',
name: '',
description: '',
primaryDomain: {url: ''},
},
};

export const useRootLoaderData = () => {
const [root] = useMatches();
return root?.data as SerializeFrom<typeof loader>;
return useRouteLoaderData('root') as unknown as RootLoaderClientData | undefined;
};

export async function loader({context}: LoaderFunctionArgs) {
Expand Down Expand Up @@ -112,7 +134,7 @@ export async function loader({context}: LoaderFunctionArgs) {

export default function App() {
const nonce = useNonce();
const data = useLoaderData<typeof loader>();
const data = useLoaderData<typeof loader>() as unknown as RootLoaderClientData;
return (
<html lang="en">
<head>
Expand All @@ -122,12 +144,17 @@ export default function App() {
<Links />
</head>
<body>
<Layout footerMetaObject={data?.footerMetaObject} {...data}>
<Layout
cart={data.cart}
footer={data.footer}
header={data.header}
isLoggedIn={data.isLoggedIn}
footerMetaObject={data.footerMetaObject}
>
<Outlet />
</Layout>
<ScrollRestoration nonce={nonce} />
<Scripts nonce={nonce} />
<LiveReload nonce={nonce} />
</body>
</html>
);
Expand Down Expand Up @@ -156,7 +183,14 @@ export function ErrorBoundary() {
<Links />
</head>
<body>
<Layout {...rootData}>
<Layout
fetchData={rootData}
cart={rootData?.cart ?? Promise.resolve(null)}
footer={rootData?.footer ?? Promise.resolve({} as FooterQuery)}
header={rootData?.header ?? FALLBACK_HEADER}
isLoggedIn={rootData?.isLoggedIn ?? false}
footerMetaObject={rootData?.footerMetaObject}
>
<div className="route-error">
<h1>Oops</h1>
<h2>{errorStatus}</h2>
Expand All @@ -169,7 +203,6 @@ export function ErrorBoundary() {
</Layout>
<ScrollRestoration nonce={nonce} />
<Scripts nonce={nonce} />
<LiveReload nonce={nonce} />
</body>
</html>
);
Expand Down Expand Up @@ -282,7 +315,7 @@ const FOOTER_QUERY = `#graphql
` as const;

const FOOTER_META_OBJECT_QUERY = `#graphql
query MetaObject($country: CountryCode, $language: LanguageCode)
query RootMetaObject($country: CountryCode, $language: LanguageCode)
@inContext(country: $country, language: $language) {
metaobjects(first: 100, type: "footer") {
nodes {
Expand Down
10 changes: 5 additions & 5 deletions app/routes/($locale)._index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ function RecommendedProducts({
<>
{bannerField?.key === 'heading' && (
<h5 className="page-banner-heading">
{bannerField?.value}
{bannerField?.value} :
</h5>
)}
{bannerField?.key === 'title' && (
Expand Down Expand Up @@ -1092,7 +1092,7 @@ const COLLECTIONS_QUERY = `#graphql
` as const;

const NEW_ARRIVALS_QUERY = `#graphql
query FeaturedCollection($country: CountryCode, $language: LanguageCode)
query FeaturedCollectionNewArrivals($country: CountryCode, $language: LanguageCode)
@inContext(country: $country, language: $language) {
collection(handle: "new-arrivals") {
handle
Expand All @@ -1116,7 +1116,7 @@ query FeaturedCollection($country: CountryCode, $language: LanguageCode)
}` as const;

const TOP_CATEGORIES_QUERY = `#graphql
query FeaturedCollection($country: CountryCode, $language: LanguageCode)
query FeaturedCollectionTopCategories($country: CountryCode, $language: LanguageCode)
@inContext(country: $country, language: $language) {
collections(first:8, reverse:true) {
edges {
Expand Down Expand Up @@ -1148,7 +1148,7 @@ const TOP_CATEGORIES_QUERY = `#graphql
` as const;

const BEST_SELLER_QUERY = `#graphql
query FeaturedCollection($country: CountryCode, $language: LanguageCode)
query FeaturedCollectionBestSeller($country: CountryCode, $language: LanguageCode)
@inContext(country: $country, language: $language) {
collection(handle: "womens-fashion") {
handle
Expand All @@ -1172,7 +1172,7 @@ query FeaturedCollection($country: CountryCode, $language: LanguageCode)
}` as const;

const META_OBJECT_QUERY = `#graphql
query MetaObject($country: CountryCode, $language: LanguageCode)
query MetaObjectHome($country: CountryCode, $language: LanguageCode)
@inContext(country: $country, language: $language) {
metaobjects(first: 100, type: "home") {
nodes {
Expand Down
2 changes: 1 addition & 1 deletion app/routes/($locale).about.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function RecommendedProducts({metaObject}: {metaObject: any}) {
}

const META_OBJECT_QUERY = `#graphql
query MetaObject($country: CountryCode, $language: LanguageCode)
query MetaObjectAbout($country: CountryCode, $language: LanguageCode)
@inContext(country: $country, language: $language) {
metaobjects(first: 100, type: "about_us") {
nodes {
Expand Down
4 changes: 2 additions & 2 deletions app/routes/($locale).collections._index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ const COLLECTIONS_QUERY = `#graphql
height
}
}
query StoreCollections(
query StoreCollectionsIndex(
$country: CountryCode
$endCursor: String
$first: Int
Expand Down Expand Up @@ -142,7 +142,7 @@ const COLLECTIONS_QUERY = `#graphql
` as const;

const HEADING_QUERY = `#graphql
query MetaObject($country: CountryCode, $language: LanguageCode)
query MetaObjectCollectionsPageHeading($country: CountryCode, $language: LanguageCode)
@inContext(country: $country, language: $language) {
metaobjects(first: 100, type: "product_page_contents") {
nodes {
Expand Down
8 changes: 4 additions & 4 deletions app/routes/($locale).collections.all.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ function RecommendedProducts({
}

const RECOMMENDED_PRODUCTS_QUERY = `#graphql
fragment Product on Product {
fragment CollectionProduct on Product {
id
title
handle
Expand Down Expand Up @@ -214,7 +214,7 @@ const RECOMMENDED_PRODUCTS_QUERY = `#graphql
}
}
}
query RecommendedProducts ( $country: CountryCode
query RecommendedProductsAllPage ( $country: CountryCode
$endCursor: String
$first: Int
$language: LanguageCode
Expand All @@ -229,7 +229,7 @@ const RECOMMENDED_PRODUCTS_QUERY = `#graphql
reverse: true
) {
nodes {
...Product
...CollectionProduct
}
pageInfo {
hasNextPage
Expand All @@ -242,7 +242,7 @@ const RECOMMENDED_PRODUCTS_QUERY = `#graphql
` as const;

const HEADING_QUERY = `#graphql
query MetaObject($country: CountryCode, $language: LanguageCode)
query MetaObjectAllProductsPageHeading($country: CountryCode, $language: LanguageCode)
@inContext(country: $country, language: $language) {
metaobjects(first: 100, type: "product_page_contents") {
nodes {
Expand Down
Loading