diff --git a/sample-apps/payment-customizations/.eslintignore b/sample-apps/payment-customizations/.eslintignore index 3796499f..d2d6eed6 100644 --- a/sample-apps/payment-customizations/.eslintignore +++ b/sample-apps/payment-customizations/.eslintignore @@ -1,6 +1,5 @@ node_modules build public/build -shopify-app-remix */*.yml .shopify diff --git a/sample-apps/payment-customizations/.eslintrc.cjs b/sample-apps/payment-customizations/.eslintrc.cjs new file mode 100644 index 00000000..dfab9374 --- /dev/null +++ b/sample-apps/payment-customizations/.eslintrc.cjs @@ -0,0 +1,38 @@ +/** + * This is intended to be a basic starting point for linting in your app. + * It relies on recommended configs out of the box for simplicity, but you can + * and should modify this configuration to best suit your team's needs. + */ + +/** @type {import('eslint').Linter.Config} */ +module.exports = { + env: { + browser: true, + es2022: true, + node: true, + }, + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:react-hooks/recommended', + 'plugin:jsx-a11y/recommended', + ], + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 'latest', + sourceType: 'module', + }, + plugins: ['react', 'react-hooks', 'jsx-a11y', 'import'], + rules: { + 'react/react-in-jsx-scope': 'off', + 'react/prop-types': 'off', + 'react/no-unknown-property': ['error', { ignore: ['variant', 'tone'] }], + }, + settings: { + react: { + version: 'detect', + }, + }, +}; diff --git a/sample-apps/payment-customizations/.eslintrc.js b/sample-apps/payment-customizations/.eslintrc.js deleted file mode 100644 index a42d9754..00000000 --- a/sample-apps/payment-customizations/.eslintrc.js +++ /dev/null @@ -1,13 +0,0 @@ -/** @type {import('@types/eslint').Linter.BaseConfig} */ -module.exports = { - root: true, - extends: [ - "@remix-run/eslint-config", - "@remix-run/eslint-config/node", - "@remix-run/eslint-config/jest-testing-library", - "prettier", - ], - globals: { - shopify: "readonly" - }, -}; diff --git a/sample-apps/payment-customizations/.gitignore b/sample-apps/payment-customizations/.gitignore index f99453d7..9ddf1b85 100644 --- a/sample-apps/payment-customizations/.gitignore +++ b/sample-apps/payment-customizations/.gitignore @@ -4,11 +4,25 @@ node_modules /build /app/build /public/build/ +/public/_dev /app/public/build /prisma/dev.sqlite /prisma/dev.sqlite-journal database.sqlite .env +.env.* + package-lock.json yarn.lock +pnpm-lock.yaml + +/extensions/*/dist + +# Ignore shopify files created during app dev +.shopify/* +.shopify.lock + +# Hide files auto-generated by react router +.react-router/ + diff --git a/sample-apps/payment-customizations/.graphqlrc.js b/sample-apps/payment-customizations/.graphqlrc.js index f40b3306..a7e9d621 100644 --- a/sample-apps/payment-customizations/.graphqlrc.js +++ b/sample-apps/payment-customizations/.graphqlrc.js @@ -1,36 +1,41 @@ -const fs = require('node:fs'); -const apiVersion = require("@shopify/shopify-app-remix").LATEST_API_VERSION; +import fs from "fs"; +import { LATEST_API_VERSION } from "@shopify/shopify-api"; +import { shopifyApiProject, ApiType } from "@shopify/api-codegen-preset"; function getConfig() { - const config = { - projects: { - shopifyAdminApi: { - schema: `https://shopify.dev/admin-graphql-direct-proxy/${apiVersion}`, - documents: ['./app/**/*.{graphql,js,ts,jsx,tsx}'] - } - } - } + const config = { + projects: { + default: shopifyApiProject({ + apiType: ApiType.Admin, + apiVersion: LATEST_API_VERSION, + documents: ["./app/**/*.{js,jsx}", "./app/.server/**/*.{js,jsx}"], + outputDir: "./app/types", + }), + }, + }; - let extensions = [] - try { - extensions = fs.readdirSync('./extensions'); - } catch { - // ignore if no extensions - } + let extensions = []; + try { + extensions = fs.readdirSync("./extensions"); + } catch { + // ignore if no extensions + } - for (const entry of extensions) { - const extensionPath = `./extensions/${entry}`; - const schema = `${extensionPath}/schema.graphql`; - if(!fs.existsSync(schema)) { - continue; - } - config.projects[entry] = { - schema, - documents: [`${extensionPath}/input.graphql`] - } + for (const entry of extensions) { + const extensionPath = `./extensions/${entry}`; + const schema = `${extensionPath}/schema.graphql`; + if (!fs.existsSync(schema)) { + continue; } + config.projects[entry] = { + schema, + documents: [`${extensionPath}/**/*.graphql`], + }; + } - return config; + return config; } -module.exports = getConfig(); +const config = getConfig(); + +export default config; \ No newline at end of file diff --git a/sample-apps/payment-customizations/.npmrc b/sample-apps/payment-customizations/.npmrc index 46714a62..7343d300 100644 --- a/sample-apps/payment-customizations/.npmrc +++ b/sample-apps/payment-customizations/.npmrc @@ -1,4 +1,4 @@ engine-strict=true auto-install-peers=true shamefully-hoist=true -@shopify:registry=https://registry.npmjs.org +enable-pre-post-scripts=true diff --git a/sample-apps/payment-customizations/.prettierignore b/sample-apps/payment-customizations/.prettierignore index 4aa60e14..82667c50 100644 --- a/sample-apps/payment-customizations/.prettierignore +++ b/sample-apps/payment-customizations/.prettierignore @@ -1,13 +1,7 @@ package.json -.cache .shadowenv.d .vscode -build node_modules prisma public -shopify-app-remix -.github -tmp -*.yml .shopify diff --git a/sample-apps/payment-customizations/.vscode/extensions.json b/sample-apps/payment-customizations/.vscode/extensions.json deleted file mode 100644 index c30e3165..00000000 --- a/sample-apps/payment-customizations/.vscode/extensions.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "recommendations": [ - "graphql.vscode-graphql", - "shopify.polaris-for-vscode" - ] -} diff --git a/sample-apps/payment-customizations/Dockerfile b/sample-apps/payment-customizations/Dockerfile index 4d6703cf..07bc9cf7 100644 --- a/sample-apps/payment-customizations/Dockerfile +++ b/sample-apps/payment-customizations/Dockerfile @@ -1,14 +1,21 @@ FROM node:18-alpine +RUN apk add --no-cache openssl EXPOSE 3000 + WORKDIR /app + +ENV NODE_ENV=production + +COPY package.json package-lock.json* ./ + +RUN npm ci --omit=dev && npm cache clean --force +# Remove CLI packages since we don't need them in production by default. +# Remove this line if you want to run CLI commands in your container. +RUN npm remove @shopify/cli + COPY . . -RUN npm install RUN npm run build -# You'll probably want to remove this in production, it's here to make it easier to test things! -RUN rm prisma/dev.sqlite -RUN npx prisma migrate dev --name init - -CMD ["npm", "run", "start"] +CMD ["npm", "run", "docker-start"] diff --git a/sample-apps/payment-customizations/README.md b/sample-apps/payment-customizations/README.md index 9c286779..49aa9ec6 100644 --- a/sample-apps/payment-customizations/README.md +++ b/sample-apps/payment-customizations/README.md @@ -1,61 +1,35 @@ -# Shopify App Template - Remix +# Shopify App Template - React Router -This is a template for building a [Shopify app](https://shopify.dev/docs/apps/getting-started) using the [Remix](https://remix.run) framework. +This is a template for building a [Shopify app](https://shopify.dev/docs/apps/getting-started) using [React Router](https://reactrouter.com/). It was forked from the [Shopify Remix app template](https://github.com/Shopify/shopify-app-template-remix) and converted to React Router. - - +Rather than cloning this repo, you can use your preferred package manager and the Shopify CLI with [these steps](https://shopify.dev/docs/apps/getting-started/create). + +Visit the [`shopify.dev` documentation](https://shopify.dev/docs/api/shopify-app-react-router) for more details on the React Router app package. ## Quick start ### Prerequisites -1. You must [download and install Node.js](https://nodejs.org/en/download/) if you don't already have it. -1. You must [create a Shopify partner account](https://partners.shopify.com/signup) if you don’t have one. -1. You must create a store for testing if you don't have one, either a [development store](https://help.shopify.com/en/partners/dashboard/development-stores#create-a-development-store) or a [Shopify Plus sandbox store](https://help.shopify.com/en/partners/dashboard/managing-stores/plus-sandbox-store). - - - -### Setup - -If you used the CLI to create the template, you can skip this section. - -Using yarn: +Before you begin, you'll need the following: +1. **Node.js**: [Download and install](https://nodejs.org/en/download/) it if you haven't already. +2. **Shopify Partner Account**: [Create an account](https://partners.shopify.com/signup) if you don't have one. +3. **Test Store**: Set up either a [development store](https://help.shopify.com/en/partners/dashboard/development-stores#create-a-development-store) or a [Shopify Plus sandbox store](https://help.shopify.com/en/partners/dashboard/managing-stores/plus-sandbox-store) for testing your app. +4. **Shopify CLI**: [Download and install](https://shopify.dev/docs/apps/tools/cli/getting-started) it if you haven't already. ```shell -yarn install +npm install -g @shopify/cli@latest ``` -Using npm: - -```shell -npm install -``` - -Using pnpm: +### Setup ```shell -pnpm install +shopify app init --template=https://github.com/Shopify/shopify-app-template-react-router ``` ### Local Development -Using yarn: - -```shell -yarn dev -``` - -Using npm: - -```shell -npm run dev -``` - -Using pnpm: - ```shell -pnpm run setup -pnpm run dev +shopify app dev ``` Press P to open the URL to your app. Once you click install, you can start development. @@ -86,17 +60,17 @@ export async function loader({ request }) { }, } = await response.json(); - return json(nodes); + return nodes; } ``` -This template come preconfigured with examples of: +This template comes pre-configured with examples of: -1. Setting up your Shopify app in [/app/shopify.server.js](https://github.com/Shopify/shopify-app-template-remix/blob/main/app/shopify.server.js) -2. Querying data using Graphql. Please see: [/app/routes/app.\_index.tsx](https://github.com/Shopify/shopify-app-template-remix/blob/main/app/routes/app._index.jsx). -3. Responding to mandatory webhooks in [/app/routes/webhooks.jsx](https://github.com/Shopify/shopify-app-template-remix/blob/main/app/routes/webhooks.jsx) +1. Setting up your Shopify app in [/app/shopify.server.ts](https://github.com/Shopify/shopify-app-template-react-router/blob/main/app/shopify.server.ts) +2. Querying data using Graphql. Please see: [/app/routes/app.\_index.tsx](https://github.com/Shopify/shopify-app-template-react-router/blob/main/app/routes/app._index.tsx). +3. Responding to webhooks. Please see [/app/routes/webhooks.tsx](https://github.com/Shopify/shopify-app-template-react-router/blob/main/app/routes/webhooks.app.uninstalled.tsx). -Please read the [documentation for @shopify/shopify-app-remix](https://www.npmjs.com/package/@shopify/shopify-app-remix#authenticating-admin-requests) to understand what other API's are available. +Please read the [documentation for @shopify/shopify-app-react-router](https://shopify.dev/docs/api/shopify-app-react-router) to see what other API's are available. ## Deployment @@ -107,21 +81,20 @@ The database is defined as a Prisma schema in `prisma/schema.prisma`. This use of SQLite works in production if your app runs as a single instance. The database that works best for you depends on the data your app needs and how it is queried. -You can run your database of choice on a server yourself or host it with a SaaS company. Here’s a short list of databases providers that provide a free tier to get started: | Database | Type | Hosters | | ---------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| MySQL | SQL | [Digital Ocean](https://www.digitalocean.com/try/managed-databases-mysql), [Planet Scale](https://planetscale.com/), [Amazon Aurora](https://aws.amazon.com/rds/aurora/), [Google Cloud SQL](https://cloud.google.com/sql/docs/mysql) | -| PostgreSQL | SQL | [Digital Ocean](https://www.digitalocean.com/try/managed-databases-postgresql), [Amazon Aurora](https://aws.amazon.com/rds/aurora/), [Google Cloud SQL](https://cloud.google.com/sql/docs/postgres) | -| Redis | Key-value | [Digital Ocean](https://www.digitalocean.com/try/managed-databases-redis), [Amazon MemoryDB](https://aws.amazon.com/memorydb/) | -| MongoDB | NoSQL / Document | [Digital Ocean](https://www.digitalocean.com/try/managed-databases-mongodb), [MongoDB Atlas](https://www.mongodb.com/atlas/database) | +| MySQL | SQL | [Digital Ocean](https://www.digitalocean.com/products/managed-databases-mysql), [Planet Scale](https://planetscale.com/), [Amazon Aurora](https://aws.amazon.com/rds/aurora/), [Google Cloud SQL](https://cloud.google.com/sql/docs/mysql) | +| PostgreSQL | SQL | [Digital Ocean](https://www.digitalocean.com/products/managed-databases-postgresql), [Amazon Aurora](https://aws.amazon.com/rds/aurora/), [Google Cloud SQL](https://cloud.google.com/sql/docs/postgres) | +| Redis | Key-value | [Digital Ocean](https://www.digitalocean.com/products/managed-databases-redis), [Amazon MemoryDB](https://aws.amazon.com/memorydb/) | +| MongoDB | NoSQL / Document | [Digital Ocean](https://www.digitalocean.com/products/managed-databases-mongodb), [MongoDB Atlas](https://www.mongodb.com/atlas/database) | -To use one of these, you can use a different [datasource provider](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#datasource) in your `schema.prisma` file, or a different [SessionStorage adapter package](https://github.com/Shopify/shopify-api-js/tree/main/docs/guides/session-storage.md). +To use one of these, you can use a different [datasource provider](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#datasource) in your `schema.prisma` file, or a different [SessionStorage adapter package](https://github.com/Shopify/shopify-api-js/blob/main/packages/shopify-api/docs/guides/session-storage.md). ### Build -Remix handles building the app for you, by running the command below with the package manager of your choice: +Build the app by running the command below with the package manager of your choice: Using yarn: @@ -147,70 +120,150 @@ When you're ready to set up your app in production, you can follow [our deployme When you reach the step for [setting up environment variables](https://shopify.dev/docs/apps/deployment/web#set-env-vars), you also need to set the variable `NODE_ENV=production`. + ## Gotchas / Troubleshooting ### Database tables don't exist -If you run the app right after creating it, you'll get this error: +If you get an error like: ``` The table `main.Session` does not exist in the current database. ``` -This will happen when the Prisma database hasn't been created. -You can solve this by running the `setup` script in your app. +Create the database for Prisma. Run the `setup` script in `package.json` using `npm`, `yarn` or `pnpm`. + +### Navigating/redirecting breaks an embedded app + +Embedded apps must maintain the user session, which can be tricky inside an iFrame. To avoid issues: + +1. Use `Link` from `react-router` or `@shopify/polaris`. Do not use ``. +2. Use `redirect` returned from `authenticate.admin`. Do not use `redirect` from `react-router` +3. Use `useSubmit` from `react-router`. + +This only applies if your app is embedded, which it will be by default. + +### App goes into a loop when I change my app's scopes + +If you change your app's scopes and authentication goes into a loop before failing after trying too many times, you might have forgotten to update your scopes with Shopify. Update your scopes. + +Using yarn: + +```shell +yarn deploy +``` + +Using npm: + +```shell +npm run deploy +``` + +Using pnpm: + +```shell +pnpm run deploy +``` + +### Webhooks: shop-specific webhook subscriptions aren't updated + +If you are registering webhooks in the `afterAuth` hook, using `shopify.registerWebhooks`, you may find that your subscriptions aren't being updated. + +Instead of using the `afterAuth` hook declare app-specific webhooks in the `shopify.app.toml` file. This approach is easier since Shopify will automatically sync changes every time you run `deploy` (e.g: `npm run deploy`). Please read these guides to understand more: + +1. [app-specific vs shop-specific webhooks](https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-subscriptions) +2. [Create a subscription tutorial](https://shopify.dev/docs/apps/build/webhooks/subscribe/get-started?framework=remix&deliveryMethod=https) + +If you do need shop-specific webhooks, keep in mind that the package calls `afterAuth` in 2 scenarios: + +- After installing the app +- When an access token expires + +During normal development, the app won't need to re-authenticate most of the time, so shop-specific subscriptions aren't updated. To force your app to update the subscriptions, uninstall and reinstall the app. Revisiting the app will call the `afterAuth` hook. + +### Webhooks: Admin created webhook failing HMAC validation + +Webhooks subscriptions created in the [Shopify admin](https://help.shopify.com/en/manual/orders/notifications/webhooks) will fail HMAC validation. This is because the webhook payload is not signed with your app's secret key. -### Navigating to other pages breaks +The recommended solution is to use [app-specific webhooks](https://shopify.dev/docs/apps/build/webhooks/subscribe#app-specific-subscriptions) defined in your toml file instead. Test your webhooks by triggering events manually in the Shopify admin(e.g. Updating the product title to trigger a `PRODUCTS_UPDATE`). -In Remix apps, you can navigate to a different page either by adding an `` tag, or using the `` component from `@remix-run/react`. +### Webhooks: Admin object undefined on webhook events triggered by the CLI -In Shopify Remix apps you should avoid using ``. Use ` `from `@remix-run/react` instead. This ensures that your user remains authenticated. +When you trigger a webhook event using the Shopify CLI, the `admin` object will be `undefined`. This is because the CLI triggers an event with a valid, but non-existent, shop. The `admin` object is only available when the webhook is triggered by a shop that has installed the app. This is expected. -### Non Embedded +Webhooks triggered by the CLI are intended for initial experimentation testing of your webhook configuration. For more information on how to test your webhooks, see the [Shopify CLI documentation](https://shopify.dev/docs/apps/tools/cli/commands#webhook-trigger). -Shopify apps are best when they are embedded into the Shopify Admin. This template is configured that way. If you have a reason to not embed your please make 2 changes: +### Incorrect GraphQL Hints -1. Remove the `