You haven't placed any orders yet.
} 14 | 15 | {!!orders?.length &&{path})
22 | title)
19 | >
20 | ),
21 | options: {field: 'title'},
22 | validation: (rule) =>
23 | rule.max(50).warning('Longer titles may be truncated by search engines'),
24 | }),
25 | defineField({
26 | name: 'description',
27 | title: 'Description',
28 | type: 'text',
29 | rows: 2,
30 | validation: (rule) =>
31 | rule.max(150).warning('Longer descriptions may be truncated by search engines'),
32 | }),
33 | defineField({
34 | name: 'image',
35 | title: 'Image',
36 | type: 'image',
37 | }),
38 | ],
39 | })
40 |
--------------------------------------------------------------------------------
/apps/storefront/app/queries/sanity/fragments/modules.ts:
--------------------------------------------------------------------------------
1 | import groq from "groq";
2 |
3 | import { MODULE_ACCORDION } from "./modules/accordion";
4 | import { MODULE_CALLOUT } from "./modules/callout";
5 | import { MODULE_CALL_TO_ACTION } from "./modules/callToAction";
6 | import { MODULE_COLLECTION } from "./modules/collection";
7 | import { MODULE_IMAGE } from "./modules/image";
8 | import { MODULE_INSTAGRAM } from "./modules/instagram";
9 | import { MODULE_PRODUCT } from "./modules/product";
10 |
11 | export const MODULES = groq`
12 | _key,
13 | _type,
14 | (_type == "module.accordion") => {
15 | ${MODULE_ACCORDION}
16 | },
17 | (_type == "module.callout") => {
18 | ${MODULE_CALLOUT}
19 | },
20 | (_type == 'module.callToAction') => {
21 | ${MODULE_CALL_TO_ACTION}
22 | },
23 | (_type == "module.collection") => {
24 | ${MODULE_COLLECTION}
25 | },
26 | (_type == "module.image") => {
27 | ${MODULE_IMAGE}
28 | },
29 | (_type == "module.instagram") => {
30 | ${MODULE_INSTAGRAM}
31 | },
32 | (_type == "module.product") => {
33 | ${MODULE_PRODUCT}
34 | },
35 | `;
36 |
--------------------------------------------------------------------------------
/packages/sanity/src/schema/objects/shopifyCollectionRule.tsx:
--------------------------------------------------------------------------------
1 | import {FilterIcon} from '@sanity/icons'
2 | import {defineField} from 'sanity'
3 |
4 | export default defineField({
5 | title: 'Collection rule',
6 | name: 'collectionRule',
7 | type: 'object',
8 | icon: FilterIcon,
9 | readOnly: true,
10 | fields: [
11 | // Column
12 | defineField({
13 | title: 'Column',
14 | name: 'column',
15 | type: 'string',
16 | }),
17 | // Values
18 | defineField({
19 | title: 'Relation',
20 | name: 'relation',
21 | type: 'string',
22 | }),
23 | // Condition
24 | defineField({
25 | title: 'Condition',
26 | name: 'condition',
27 | type: 'string',
28 | }),
29 | ],
30 | preview: {
31 | select: {
32 | condition: 'condition',
33 | name: 'column',
34 | relation: 'relation',
35 | },
36 | prepare(selection) {
37 | const {condition, name, relation} = selection
38 |
39 | return {
40 | subtitle: `${relation} ${condition}`,
41 | title: name,
42 | }
43 | },
44 | },
45 | })
46 |
--------------------------------------------------------------------------------
/apps/storefront/app/queries/sanity/fragments/modules/image.ts:
--------------------------------------------------------------------------------
1 | import groq from "groq";
2 |
3 | import { IMAGE } from "../image";
4 | import { LINK_EXTERNAL } from "../linkExternal";
5 | import { LINK_INTERNAL } from "../linkInternal";
6 | import { PRODUCT_HOTSPOT } from "../productHotspot";
7 | import { PRODUCT_WITH_VARIANT } from "../productWithVariant";
8 |
9 | export const MODULE_IMAGE = groq`
10 | image {
11 | ${IMAGE}
12 | },
13 | (variant == 'callToAction') => {
14 | callToAction {
15 | "link": links[0] {
16 | (_type == 'linkExternal') => {
17 | ${LINK_EXTERNAL}
18 | },
19 | (_type == 'linkInternal') => {
20 | ${LINK_INTERNAL}
21 | },
22 | },
23 | title,
24 | }
25 | },
26 | (variant == 'caption') => {
27 | caption,
28 | },
29 | (variant == 'productHotspots') => {
30 | productHotspots[] {
31 | _key,
32 | ${PRODUCT_HOTSPOT}
33 | }
34 | },
35 | (variant == 'productTags') => {
36 | productTags[] {
37 | _key,
38 | ...${PRODUCT_WITH_VARIANT}
39 | },
40 | },
41 | variant,
42 | `;
43 |
--------------------------------------------------------------------------------
/apps/storefront/app/routes/[robots.txt].tsx:
--------------------------------------------------------------------------------
1 | import { type LoaderFunctionArgs } from "@shopify/remix-oxygen";
2 |
3 | export const loader = ({ request }: LoaderFunctionArgs) => {
4 | const url = new URL(request.url);
5 |
6 | return new Response(robotsTxtData({ url: url.origin }), {
7 | status: 200,
8 | headers: {
9 | "content-type": "text/plain",
10 | // Cache for 24 hours
11 | "cache-control": `max-age=${60 * 60 * 24}`,
12 | },
13 | });
14 | };
15 |
16 | function robotsTxtData({ url }: { url: string }) {
17 | const sitemapUrl = url ? `${url}/sitemap.xml` : undefined;
18 |
19 | return `
20 | User-agent: *
21 | Disallow: /admin
22 | Disallow: /cart
23 | Disallow: /orders
24 | Disallow: /checkouts/
25 | Disallow: /checkout
26 | Disallow: /carts
27 | Disallow: /account
28 | ${sitemapUrl ? `Sitemap: ${sitemapUrl}` : ""}
29 |
30 | # Google adsbot ignores robots.txt unless specifically named!
31 | User-agent: adsbot-google
32 | Disallow: /checkouts/
33 | Disallow: /checkout
34 | Disallow: /carts
35 | Disallow: /orders
36 |
37 | User-agent: Pinterest
38 | Crawl-delay: 1
39 | `.trim();
40 | }
41 |
--------------------------------------------------------------------------------
/packages/sanity/src/schema/blocks/simpleBlockContent.tsx:
--------------------------------------------------------------------------------
1 | export default {
2 | title: 'Simple Block Content',
3 | name: 'simpleBlockContent',
4 | type: 'array',
5 | of: [
6 | {
7 | title: 'Block',
8 | type: 'block',
9 | styles: [],
10 | lists: [],
11 | marks: {
12 | decorators: [
13 | {title: 'Strong', value: 'strong'},
14 | {title: 'Italic', value: 'em'},
15 | ],
16 | annotations: [
17 | // product
18 | {
19 | name: 'annotationProduct',
20 | type: 'annotationProduct',
21 | },
22 | // Email
23 | {
24 | name: 'annotationLinkEmail',
25 | type: 'annotationLinkEmail',
26 | },
27 | // Internal link
28 | {
29 | name: 'annotationLinkInternal',
30 | type: 'annotationLinkInternal',
31 | },
32 | // URL
33 | {
34 | name: 'annotationLinkExternal',
35 | type: 'annotationLinkExternal',
36 | },
37 | ],
38 | },
39 | },
40 | ],
41 | }
42 |
--------------------------------------------------------------------------------
/packages/sanity/src/schema/objects/hero/home.tsx:
--------------------------------------------------------------------------------
1 | import {defineField} from 'sanity'
2 |
3 | export default defineField({
4 | name: 'hero.home',
5 | title: 'Home hero',
6 | type: 'object',
7 | fields: [
8 | // Title
9 | defineField({
10 | name: 'title',
11 | title: 'Title',
12 | type: 'text',
13 | rows: 3,
14 | }),
15 | // Link
16 | defineField({
17 | name: 'links',
18 | title: 'Link',
19 | type: 'array',
20 | of: [{type: 'linkInternal'}, {type: 'linkExternal'}],
21 | validation: (rule) => rule.max(1),
22 | }),
23 | // Content
24 | defineField({
25 | name: 'content',
26 | title: 'Content',
27 | type: 'array',
28 | validation: (rule) => rule.max(1),
29 | of: [
30 | {
31 | name: 'productWithVariant',
32 | title: 'Product with variant',
33 | type: 'productWithVariant',
34 | },
35 | {
36 | name: 'imageWithProductHotspots',
37 | title: 'Image',
38 | type: 'imageWithProductHotspots',
39 | },
40 | ],
41 | }),
42 | ],
43 | })
44 |
--------------------------------------------------------------------------------
/apps/storefront/app/components/global/Header.tsx:
--------------------------------------------------------------------------------
1 | import clsx from "clsx";
2 |
3 | import HeaderActions from "~/components/global/HeaderActions";
4 | import HeaderBackground from "~/components/global/HeaderBackground";
5 | import MobileNavigation from "~/components/global/MobileNavigation";
6 | import Navigation from "~/components/global/Navigation";
7 | import { useRootLoaderData } from "~/root";
8 |
9 | /**
10 | * A server component that specifies the content of the header on the website
11 | */
12 | export default function Header() {
13 | const { layout } = useRootLoaderData();
14 | const { menuLinks } = layout || {};
15 |
16 | return (
17 | store.title)
20 | >
21 | ),
22 | options: {
23 | field: 'store.title',
24 | },
25 | validation: (rule) =>
26 | rule.max(50).warning('Longer titles may be truncated by search engines'),
27 | },
28 | {
29 | name: 'description',
30 | title: 'Description',
31 | type: 'text',
32 | rows: 2,
33 | validation: (rule) =>
34 | rule.max(150).warning('Longer descriptions may be truncated by search engines'),
35 | },
36 | {
37 | name: 'image',
38 | title: 'Image',
39 | type: 'image',
40 | },
41 | ],
42 | })
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2022 Sanity.io
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/sanity/src/schema/objects/module/callout.ts:
--------------------------------------------------------------------------------
1 | import {BulbOutlineIcon} from '@sanity/icons'
2 | import {defineField} from 'sanity'
3 |
4 | export default defineField({
5 | name: 'module.callout',
6 | title: 'Callout',
7 | type: 'object',
8 | icon: BulbOutlineIcon,
9 | fields: [
10 | // Text
11 | defineField({
12 | name: 'text',
13 | title: 'Text',
14 | type: 'text',
15 | rows: 2,
16 | validation: (rule) => [
17 | rule.required(),
18 | rule.max(70).warning(`Callout length shouldn't be more than 70 characters.`),
19 | ],
20 | }),
21 | // Link
22 | defineField({
23 | name: 'links',
24 | title: 'Link',
25 | type: 'array',
26 | of: [{type: 'linkInternal'}, {type: 'linkExternal'}],
27 | validation: (rule) => rule.max(1),
28 | }),
29 | ],
30 | preview: {
31 | select: {
32 | text: 'text',
33 | url: 'url',
34 | },
35 | prepare(selection) {
36 | const {text} = selection
37 | return {
38 | subtitle: 'Callout',
39 | title: text,
40 | media: BulbOutlineIcon,
41 | }
42 | },
43 | },
44 | })
45 |
--------------------------------------------------------------------------------
/apps/storefront/app/hooks/usePageAnalytics.tsx:
--------------------------------------------------------------------------------
1 | import { useMatches } from "@remix-run/react";
2 | import type { ShopifyPageViewPayload } from "@shopify/hydrogen";
3 | import { useMemo } from "react";
4 |
5 | import { DEFAULT_LOCALE } from "~/lib/utils";
6 |
7 | export function usePageAnalytics({
8 | hasUserConsent,
9 | }: {
10 | hasUserConsent: boolean;
11 | }) {
12 | const matches = useMatches();
13 |
14 | return useMemo(() => {
15 | const data: Record58 | {children} 59 |
60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /apps/storefront/app/components/portableText/blocks/TaggedProducts.tsx: -------------------------------------------------------------------------------- 1 | import type { PortableTextBlock } from "@portabletext/types"; 2 | import clsx from "clsx"; 3 | 4 | import ProductModule from "~/components/modules/Product"; 5 | import type { SanityModuleTaggedProducts } from "~/lib/sanity"; 6 | 7 | type Props = { 8 | value: PortableTextBlock & SanityModuleTaggedProducts; 9 | }; 10 | 11 | export default function TaggedProductsBlock({ value }: Props) { 12 | if (!Array.isArray(value?.products)) { 13 | return null; 14 | } 15 | 16 | const products = value?.products.slice(0, value?.number ?? 0); 17 | 18 | const multipleProducts = products.length > 1; 19 | return ( 20 | <> 21 | {products.length > 0 && ( 22 |{description}
23 | 24 | {error?.stack && ( 25 | 38 | )} 39 |{productGuide.title}
34 |