diff --git a/app/routes/_index/route.jsx b/app/routes/_index/route.jsx index 7efaa16..8a38611 100644 --- a/app/routes/_index/route.jsx +++ b/app/routes/_index/route.jsx @@ -18,34 +18,30 @@ export default function App() { return (
-

A short heading about [your app]

+

Native Product Reviews

- A tagline about [your app] that describes your value proposition. + Collect and display verified customer reviews without external databases.

{showForm && (
)}
diff --git a/app/routes/app._index.jsx b/app/routes/app._index.jsx index b9380fc..8db137f 100644 --- a/app/routes/app._index.jsx +++ b/app/routes/app._index.jsx @@ -1,331 +1,69 @@ -import { useEffect } from "react"; -import { useFetcher } from "react-router"; -import { useAppBridge } from "@shopify/app-bridge-react"; -import { boundary } from "@shopify/shopify-app-react-router/server"; import { authenticate } from "../shopify.server"; +import { useLoaderData } from "react-router"; export const loader = async ({ request }) => { - await authenticate.admin(request); + const { admin, session } = await authenticate.admin(request); - return null; -}; - -export const action = async ({ request }) => { - const { admin } = await authenticate.admin(request); - const color = ["Red", "Orange", "Yellow", "Green"][ - Math.floor(Math.random() * 4) - ]; - const response = await admin.graphql( - `#graphql - mutation populateProduct($product: ProductCreateInput!) { - productCreate(product: $product) { - product { + // Fetch some reviews to show in the admin dashboard + const response = await admin.graphql(`#graphql + query { + metaobjects(first: 10, type: "product_review") { + edges { + node { id - title - handle - status - variants(first: 10) { - edges { - node { - id - price - barcode - createdAt - } - } - } - demoInfo: metafield(namespace: "$app", key: "demo_info") { - jsonValue - } + fields { key value } } } - }`, - { - variables: { - product: { - title: `${color} Snowboard`, - metafields: [ - { - namespace: "$app", - key: "demo_info", - value: "Created by React Router Template", - }, - ], - }, - }, - }, - ); - const responseJson = await response.json(); - const product = responseJson.data.productCreate.product; - const variantId = product.variants.edges[0].node.id; - const variantResponse = await admin.graphql( - `#graphql - mutation shopifyReactRouterTemplateUpdateVariant($productId: ID!, $variants: [ProductVariantsBulkInput!]!) { - productVariantsBulkUpdate(productId: $productId, variants: $variants) { - productVariants { - id - price - barcode - createdAt - } } - }`, - { - variables: { - productId: product.id, - variants: [{ id: variantId, price: "100.00" }], - }, - }, - ); - const variantResponseJson = await variantResponse.json(); - const metaobjectResponse = await admin.graphql( - `#graphql - mutation shopifyReactRouterTemplateUpsertMetaobject($handle: MetaobjectHandleInput!, $metaobject: MetaobjectUpsertInput!) { - metaobjectUpsert(handle: $handle, metaobject: $metaobject) { - metaobject { - id - handle - title: field(key: "title") { - jsonValue - } - description: field(key: "description") { - jsonValue - } - } - userErrors { - field - message - } - } - }`, - { - variables: { - handle: { - type: "$app:example", - handle: "demo-entry", - }, - metaobject: { - fields: [ - { key: "title", value: "Demo Entry" }, - { - key: "description", - value: - "This metaobject was created by the Shopify app template to demonstrate the metaobject API.", - }, - ], - }, - }, - }, - ); - const metaobjectResponseJson = await metaobjectResponse.json(); + } + `); + const data = await response.json(); + const reviews = (data.data?.metaobjects?.edges || []).map(edge => { + const fields = {}; + edge.node.fields.forEach(f => { fields[f.key] = f.value; }); + return { id: edge.node.id, ...fields }; + }); - return { - product: responseJson.data.productCreate.product, - variant: variantResponseJson.data.productVariantsBulkUpdate.productVariants, - metaobject: metaobjectResponseJson.data.metaobjectUpsert.metaobject, - }; + return { reviews }; }; export default function Index() { - const fetcher = useFetcher(); - const shopify = useAppBridge(); - const isLoading = - ["loading", "submitting"].includes(fetcher.state) && - fetcher.formMethod === "POST"; - - useEffect(() => { - if (fetcher.data?.product?.id) { - shopify.toast.show("Product created"); - } - }, [fetcher.data?.product?.id, shopify]); - const generateProduct = () => fetcher.submit({}, { method: "POST" }); + const { reviews } = useLoaderData(); return ( - - - Generate a product - - - + + - This embedded app template uses{" "} - - App Bridge - {" "} - interface examples like an{" "} - additional page in the app nav - , as well as an{" "} - - Admin GraphQL - {" "} - mutation demo, to provide a starting point for app development. + All reviews are stored directly in Shopify Metaobjects. You can view, edit, or delete them here. - - - - Generate a product with GraphQL and get the JSON output for that - product. Learn more about the{" "} - - productCreate - {" "} - mutation in our API references. Includes a product{" "} - - metafield - {" "} - and{" "} - - metaobject - - . - - - - Generate a product - - {fetcher.data?.product && ( - { - shopify.intents.invoke?.("edit:shopify/Product", { - value: fetcher.data?.product?.id, - }); - }} - target="_blank" - variant="tertiary" - > - Edit product - - )} - - {fetcher.data?.product && ( - - - -
-                  {JSON.stringify(fetcher.data.product, null, 2)}
-                
-
- productVariantsBulkUpdate mutation - -
-                  {JSON.stringify(fetcher.data.variant, null, 2)}
-                
+ {reviews.length === 0 ? ( + No reviews found yet. Try adding one from your store! + ) : ( + + {reviews.map((r) => ( + + + {r.customer_name || 'Anonymous'} + {r.content} + Product ID: {r.product_id} + - - metaobjectUpsert mutation - -
-                  
-                    {JSON.stringify(fetcher.data.metaobject, null, 2)}
-                  
-                
-
-
-
+ ))} + )}
- + - Framework: - - React Router - + This app is 100% database-less. - - Interface: - - Polaris web components - - - - API: - - GraphQL - - - - Custom data: - - Metafields & metaobjects - - - - Database: - - Prisma - - - - - - - Build an{" "} - - example app - - - - Explore Shopify's API with{" "} - - GraphiQL - - + Storage: Shopify Metaobjects + Auth: App Proxy & Admin API + Framework: React Router
); } - -export const headers = (headersArgs) => { - return boundary.headers(headersArgs); -}; diff --git a/app/routes/app.additional.jsx b/app/routes/app.additional.jsx deleted file mode 100644 index e7c8c72..0000000 --- a/app/routes/app.additional.jsx +++ /dev/null @@ -1,37 +0,0 @@ -export default function AdditionalPage() { - return ( - - - - The app template comes with an additional page which demonstrates how - to create multiple pages within app navigation using{" "} - - App Bridge - - . - - - To create your own page and have it show up in the app navigation, add - a page inside app/routes, and a link to it in the{" "} - <ui-nav-menu> component found in{" "} - app/routes/app.jsx. - - - - - - - App nav best practices - - - - - - ); -} diff --git a/app/routes/app.jsx b/app/routes/app.jsx index 95524f6..34c8be2 100644 --- a/app/routes/app.jsx +++ b/app/routes/app.jsx @@ -5,8 +5,6 @@ import { authenticate } from "../shopify.server"; export const loader = async ({ request }) => { await authenticate.admin(request); - - // eslint-disable-next-line no-undef return { apiKey: process.env.SHOPIFY_API_KEY || "" }; }; @@ -16,15 +14,13 @@ export default function App() { return ( - Home - Additional page + Reviews Dashboard ); } -// Shopify needs React Router to catch some thrown responses, so that their headers are included in the response. export function ErrorBoundary() { return boundary.error(useRouteError()); }