// app/root.tsx
import {
json,
type LoaderFunctionArgs,
type LinksFunction,
type MetaFunction,
} from '@shopify/remix-oxygen';
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useLoaderData,
} from '@remix-run/react';
import {
Analytics,
getShopAnalytics,
useNonce,
createContentSecurityPolicy,
} from '@shopify/hydrogen';
import { GloodProvider, createGlood } from '@glood/hydrogen';
import { recommendations, search, wishlist } from '@glood/hydrogen';
import favicon from '../public/favicon.ico';
import resetStyles from './styles/reset.css';
import appStyles from './styles/app.css';
// Create Glood client
const glood = createGlood({
apiKey: process.env.GLOOD_API_KEY!,
myShopifyDomain: process.env.PUBLIC_STORE_DOMAIN!,
debug: process.env.NODE_ENV === 'development',
})
.use(recommendations())
.use(search())
.use(wishlist());
export const links: LinksFunction = () => {
return [
{rel: 'stylesheet', href: resetStyles},
{rel: 'stylesheet', href: appStyles},
{
rel: 'preconnect',
href: 'https://cdn.shopify.com',
},
{
rel: 'preconnect',
href: 'https://shop.app',
},
{rel: 'icon', type: 'image/svg+xml', href: favicon},
];
};
export const meta: MetaFunction = () => {
return [{title: 'Hydrogen | Glood Integration'}];
};
export async function loader({context}: LoaderFunctionArgs) {
const {storefront, session, cart, env} = context;
// Configure CSP with Glood domains
const {nonce, header} = createContentSecurityPolicy({
shop: {
checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN,
storeDomain: env.PUBLIC_STORE_DOMAIN,
},
connectSrc: [
'https://cdn.shopify.com',
'https://*.shopifycloud.com',
'https://events.glood.ai',
'https://s-pixel.glood.ai',
'https://w-pixel.glood.ai'
],
});
const analytics = getShopAnalytics({
storefront,
publicStorefrontId: env.PUBLIC_STOREFRONT_ID,
});
return json(
{
analytics,
nonce,
},
{
headers: {
'Content-Security-Policy': header,
},
}
);
}
export default function App() {
const nonce = useNonce();
const data = useLoaderData<typeof loader>();
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<Analytics analytics={data.analytics}>
<GloodProvider client={glood} loaderData={data}>
<Layout>
<Outlet />
</Layout>
</GloodProvider>
</Analytics>
<ScrollRestoration nonce={nonce} />
<Scripts nonce={nonce} />
</body>
</html>
);
}
export function ErrorBoundary() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return (
<div>
<h1>
{error.status} {error.statusText}
</h1>
<p>{error.data}</p>
</div>
);
} else if (error instanceof Error) {
return (
<div>
<h1>Error</h1>
<p>{error.message}</p>
<p>The stack trace is:</p>
<pre>{error.stack}</pre>
</div>
);
} else {
return <h1>Unknown Error</h1>;
}
}