Skip to content
Open
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
18 changes: 18 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* @type {import("@types/eslint").Linter.BaseConfig}
*/
module.exports = {
extends: [
'@remix-run/eslint-config',
'plugin:hydrogen/recommended',
'plugin:hydrogen/typescript',
],
rules: {
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/naming-convention': 'off',
'hydrogen/prefer-image-component': 'off',
'no-useless-escape': 'off',
'@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
'no-case-declarations': 'off',
},
};
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
4 changes: 1 addition & 3 deletions app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import type {EntryContext} from '@shopify/remix-oxygen';
import {RemixServer} from '@remix-run/react';
import {isbot} from 'isbot';
import ReactDOMServer from 'react-dom/server'; // default import for CJS
import {renderToReadableStream} from 'react-dom/server.browser';
import {createContentSecurityPolicy} from '@shopify/hydrogen';

const {renderToReadableStream} = ReactDOMServer; // destructure from default

export default async function handleRequest(
request: Request,
responseStatusCode: number,
Expand Down
57 changes: 46 additions & 11 deletions app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
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,
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 './styles/reset.css';
import './styles/app.css';
Expand Down Expand Up @@ -61,9 +63,29 @@ export function links() {
}


/**
* 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,7 +144,13 @@ 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} />
Expand Down Expand Up @@ -155,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 Down
21 changes: 21 additions & 0 deletions env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type {
HydrogenCart,
HydrogenEnv,
HydrogenSession,
Storefront,
} from '@shopify/hydrogen';

declare global {
/** Worker / Oxygen bindings; matches `hydrogen env pull` shape. */
interface Env extends HydrogenEnv {}
}

declare module '@remix-run/server-runtime' {
interface AppLoadContext {
cart: HydrogenCart;
env: HydrogenEnv;
session: HydrogenSession;
storefront: Storefront;
waitUntil?: (promise: Promise<unknown>) => void;
}
}
Loading
Loading