diff --git a/.graphqlrc.ts b/.graphqlrc.ts
deleted file mode 100644
index ad9c5f81..00000000
--- a/.graphqlrc.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import fs from "fs";
-import { LATEST_API_VERSION } from "@shopify/shopify-api";
-import { shopifyApiProject, ApiType } from "@shopify/api-codegen-preset";
-import type { IGraphQLConfig } from "graphql-config";
-
-function getConfig() {
- const config: IGraphQLConfig = {
- projects: {
- default: shopifyApiProject({
- apiType: ApiType.Admin,
- apiVersion: LATEST_API_VERSION,
- documents: ["./app/**/*.{js,ts,jsx,tsx}", "./app/.server/**/*.{js,ts,jsx,tsx}"],
- outputDir: "./app/types",
- }),
- },
- };
-
- let extensions: string[] = [];
- 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}/**/*.graphql`],
- };
- }
-
- return config;
-}
-
-module.exports = getConfig();
diff --git a/app/db.server.ts b/app/db.server.ts
deleted file mode 100644
index bafa6cc2..00000000
--- a/app/db.server.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { PrismaClient } from "@prisma/client";
-
-declare global {
- var prisma: PrismaClient;
-}
-
-if (process.env.NODE_ENV !== "production") {
- if (!global.prisma) {
- global.prisma = new PrismaClient();
- }
-}
-
-const prisma: PrismaClient = global.prisma || new PrismaClient();
-
-export default prisma;
diff --git a/app/entry.server.tsx b/app/entry.server.tsx
deleted file mode 100644
index 86274311..00000000
--- a/app/entry.server.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { PassThrough } from "stream";
-import { renderToPipeableStream } from "react-dom/server";
-import { RemixServer } from "@remix-run/react";
-import {
- createReadableStreamFromReadable,
- type EntryContext,
-} from "@remix-run/node";
-import { isbot } from "isbot";
-import { addDocumentResponseHeaders } from "./shopify.server";
-
-export const streamTimeout = 5000;
-
-export default async function handleRequest(
- request: Request,
- responseStatusCode: number,
- responseHeaders: Headers,
- remixContext: EntryContext
-) {
- addDocumentResponseHeaders(request, responseHeaders);
- const userAgent = request.headers.get("user-agent");
- const callbackName = isbot(userAgent ?? '')
- ? "onAllReady"
- : "onShellReady";
-
- return new Promise((resolve, reject) => {
- const { pipe, abort } = renderToPipeableStream(
- ,
- {
- [callbackName]: () => {
- const body = new PassThrough();
- const stream = createReadableStreamFromReadable(body);
-
- responseHeaders.set("Content-Type", "text/html");
- resolve(
- new Response(stream, {
- headers: responseHeaders,
- status: responseStatusCode,
- })
- );
- pipe(body);
- },
- onShellError(error) {
- reject(error);
- },
- onError(error) {
- responseStatusCode = 500;
- console.error(error);
- },
- }
- );
-
- // Automatically timeout the React renderer after 6 seconds, which ensures
- // React has enough time to flush down the rejected boundary contents
- setTimeout(abort, streamTimeout + 1000);
- });
-}
diff --git a/app/globals.d.ts b/app/globals.d.ts
deleted file mode 100644
index cbe652db..00000000
--- a/app/globals.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-declare module "*.css";
diff --git a/app/root.tsx b/app/root.tsx
deleted file mode 100644
index 805f121f..00000000
--- a/app/root.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import {
- Links,
- Meta,
- Outlet,
- Scripts,
- ScrollRestoration,
-} from "@remix-run/react";
-
-export default function App() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/app/routes.ts b/app/routes.ts
deleted file mode 100644
index 83892841..00000000
--- a/app/routes.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import { flatRoutes } from "@remix-run/fs-routes";
-
-export default flatRoutes();
diff --git a/app/routes/_index/route.tsx b/app/routes/_index/route.tsx
deleted file mode 100644
index 2de9dd47..00000000
--- a/app/routes/_index/route.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import type { LoaderFunctionArgs } from "@remix-run/node";
-import { redirect } from "@remix-run/node";
-import { Form, useLoaderData } from "@remix-run/react";
-
-import { login } from "../../shopify.server";
-
-import styles from "./styles.module.css";
-
-export const loader = async ({ request }: LoaderFunctionArgs) => {
- const url = new URL(request.url);
-
- if (url.searchParams.get("shop")) {
- throw redirect(`/app?${url.searchParams.toString()}`);
- }
-
- return { showForm: Boolean(login) };
-};
-
-export default function App() {
- const { showForm } = useLoaderData();
-
- return (
-
-
-
A short heading about [your app]
-
- A tagline about [your app] that describes your value proposition.
-
- {showForm && (
-
- )}
-
- -
- Product feature. Some detail about your feature and
- its benefit to your customer.
-
- -
- Product feature. Some detail about your feature and
- its benefit to your customer.
-
- -
- Product feature. Some detail about your feature and
- its benefit to your customer.
-
-
-
-
- );
-}
diff --git a/app/routes/app._index.tsx b/app/routes/app._index.tsx
deleted file mode 100644
index 18b215b0..00000000
--- a/app/routes/app._index.tsx
+++ /dev/null
@@ -1,334 +0,0 @@
-import { useEffect } from "react";
-import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
-import { useFetcher } from "@remix-run/react";
-import {
- Page,
- Layout,
- Text,
- Card,
- Button,
- BlockStack,
- Box,
- List,
- Link,
- InlineStack,
-} from "@shopify/polaris";
-import { TitleBar, useAppBridge } from "@shopify/app-bridge-react";
-import { authenticate } from "../shopify.server";
-
-export const loader = async ({ request }: LoaderFunctionArgs) => {
- await authenticate.admin(request);
-
- return null;
-};
-
-export const action = async ({ request }: ActionFunctionArgs) => {
- 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 {
- id
- title
- handle
- status
- variants(first: 10) {
- edges {
- node {
- id
- price
- barcode
- createdAt
- }
- }
- }
- }
- }
- }`,
- {
- variables: {
- product: {
- title: `${color} Snowboard`,
- },
- },
- },
- );
- 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 shopifyRemixTemplateUpdateVariant($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();
-
- return {
- product: responseJson!.data!.productCreate!.product,
- variant:
- variantResponseJson!.data!.productVariantsBulkUpdate!.productVariants,
- };
-};
-
-export default function Index() {
- const fetcher = useFetcher();
-
- const shopify = useAppBridge();
- const isLoading =
- ["loading", "submitting"].includes(fetcher.state) &&
- fetcher.formMethod === "POST";
- const productId = fetcher.data?.product?.id.replace(
- "gid://shopify/Product/",
- "",
- );
-
- useEffect(() => {
- if (productId) {
- shopify.toast.show("Product created");
- }
- }, [productId, shopify]);
- const generateProduct = () => fetcher.submit({}, { method: "POST" });
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- Congrats on creating a new Shopify app 🎉
-
-
- 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.
-
-
-
-
- Get started with products
-
-
- Generate a product with GraphQL and get the JSON output for
- that product. Learn more about the{" "}
-
- productCreate
- {" "}
- mutation in our API references.
-
-
-
-
- {fetcher.data?.product && (
-
- )}
-
- {fetcher.data?.product && (
- <>
-
- {" "}
- productCreate mutation
-
-
-
-
- {JSON.stringify(fetcher.data.product, null, 2)}
-
-
-
-
- {" "}
- productVariantsBulkUpdate mutation
-
-
-
-
- {JSON.stringify(fetcher.data.variant, null, 2)}
-
-
-
- >
- )}
-
-
-
-
-
-
-
-
- App template specs
-
-
-
-
- Framework
-
-
- Remix
-
-
-
-
- Database
-
-
- Prisma
-
-
-
-
- Interface
-
-
-
- Polaris
-
- {", "}
-
- App Bridge
-
-
-
-
-
- API
-
-
- GraphQL API
-
-
-
-
-
-
-
-
- Next steps
-
-
-
- Build an{" "}
-
- {" "}
- example app
- {" "}
- to get started
-
-
- Explore Shopify’s API with{" "}
-
- GraphiQL
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/app/routes/app.additional.tsx b/app/routes/app.additional.tsx
deleted file mode 100644
index eb9b0cfd..00000000
--- a/app/routes/app.additional.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import {
- Box,
- Card,
- Layout,
- Link,
- List,
- Page,
- Text,
- BlockStack,
-} from "@shopify/polaris";
-import { TitleBar } from "@shopify/app-bridge-react";
-
-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 <NavMenu>
component found
- in app/routes/app.jsx
.
-
-
-
-
-
-
-
-
- Resources
-
-
-
-
- App nav best practices
-
-
-
-
-
-
-
-
- );
-}
-
-function Code({ children }: { children: React.ReactNode }) {
- return (
-
- {children}
-
- );
-}
diff --git a/app/routes/app.tsx b/app/routes/app.tsx
deleted file mode 100644
index bdcf1162..00000000
--- a/app/routes/app.tsx
+++ /dev/null
@@ -1,41 +0,0 @@
-import type { HeadersFunction, LoaderFunctionArgs } from "@remix-run/node";
-import { Link, Outlet, useLoaderData, useRouteError } from "@remix-run/react";
-import { boundary } from "@shopify/shopify-app-remix/server";
-import { AppProvider } from "@shopify/shopify-app-remix/react";
-import { NavMenu } from "@shopify/app-bridge-react";
-import polarisStyles from "@shopify/polaris/build/esm/styles.css?url";
-
-import { authenticate } from "../shopify.server";
-
-export const links = () => [{ rel: "stylesheet", href: polarisStyles }];
-
-export const loader = async ({ request }: LoaderFunctionArgs) => {
- await authenticate.admin(request);
-
- return { apiKey: process.env.SHOPIFY_API_KEY || "" };
-};
-
-export default function App() {
- const { apiKey } = useLoaderData();
-
- return (
-
-
-
- Home
-
- Additional page
-
-
-
- );
-}
-
-// Shopify needs Remix to catch some thrown responses, so that their headers are included in the response.
-export function ErrorBoundary() {
- return boundary.error(useRouteError());
-}
-
-export const headers: HeadersFunction = (headersArgs) => {
- return boundary.headers(headersArgs);
-};
diff --git a/app/routes/auth.$.tsx b/app/routes/auth.$.tsx
deleted file mode 100644
index 8919320d..00000000
--- a/app/routes/auth.$.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import type { LoaderFunctionArgs } from "@remix-run/node";
-import { authenticate } from "../shopify.server";
-
-export const loader = async ({ request }: LoaderFunctionArgs) => {
- await authenticate.admin(request);
-
- return null;
-};
diff --git a/app/routes/auth.login/error.server.tsx b/app/routes/auth.login/error.server.tsx
deleted file mode 100644
index 2c794974..00000000
--- a/app/routes/auth.login/error.server.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import type { LoginError } from "@shopify/shopify-app-remix/server";
-import { LoginErrorType } from "@shopify/shopify-app-remix/server";
-
-interface LoginErrorMessage {
- shop?: string;
-}
-
-export function loginErrorMessage(loginErrors: LoginError): LoginErrorMessage {
- if (loginErrors?.shop === LoginErrorType.MissingShop) {
- return { shop: "Please enter your shop domain to log in" };
- } else if (loginErrors?.shop === LoginErrorType.InvalidShop) {
- return { shop: "Please enter a valid shop domain to log in" };
- }
-
- return {};
-}
diff --git a/app/routes/auth.login/route.tsx b/app/routes/auth.login/route.tsx
deleted file mode 100644
index 0e9aece7..00000000
--- a/app/routes/auth.login/route.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import { useState } from "react";
-import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
-import { Form, useActionData, useLoaderData } from "@remix-run/react";
-import {
- AppProvider as PolarisAppProvider,
- Button,
- Card,
- FormLayout,
- Page,
- Text,
- TextField,
-} from "@shopify/polaris";
-import polarisTranslations from "@shopify/polaris/locales/en.json";
-import polarisStyles from "@shopify/polaris/build/esm/styles.css?url";
-
-import { login } from "../../shopify.server";
-
-import { loginErrorMessage } from "./error.server";
-
-export const links = () => [{ rel: "stylesheet", href: polarisStyles }];
-
-export const loader = async ({ request }: LoaderFunctionArgs) => {
- const errors = loginErrorMessage(await login(request));
-
- return { errors, polarisTranslations };
-};
-
-export const action = async ({ request }: ActionFunctionArgs) => {
- const errors = loginErrorMessage(await login(request));
-
- return {
- errors,
- };
-};
-
-export default function Auth() {
- const loaderData = useLoaderData();
- const actionData = useActionData();
- const [shop, setShop] = useState("");
- const { errors } = actionData || loaderData;
-
- return (
-
-
-
-
-
-
-
- );
-}
diff --git a/app/routes/webhooks.app.uninstalled.tsx b/app/routes/webhooks.app.uninstalled.tsx
deleted file mode 100644
index 54d3161c..00000000
--- a/app/routes/webhooks.app.uninstalled.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { ActionFunctionArgs } from "@remix-run/node";
-import { authenticate } from "../shopify.server";
-import db from "../db.server";
-
-export const action = async ({ request }: ActionFunctionArgs) => {
- const { shop, session, topic } = await authenticate.webhook(request);
-
- console.log(`Received ${topic} webhook for ${shop}`);
-
- // Webhook requests can trigger multiple times and after an app has already been uninstalled.
- // If this webhook already ran, the session may have been deleted previously.
- if (session) {
- await db.session.deleteMany({ where: { shop } });
- }
-
- return new Response();
-};
diff --git a/app/shopify.server.js b/app/shopify.server.js
index ab03ab11..ec980711 100644
--- a/app/shopify.server.js
+++ b/app/shopify.server.js
@@ -5,7 +5,6 @@ import {
shopifyApp,
} from "@shopify/shopify-app-remix/server";
import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
-import { restResources } from "@shopify/shopify-api/rest/admin/2024-10";
import prisma from "./db.server";
const shopify = shopifyApp({
@@ -17,9 +16,9 @@ const shopify = shopifyApp({
authPathPrefix: "/auth",
sessionStorage: new PrismaSessionStorage(prisma),
distribution: AppDistribution.AppStore,
- restResources,
future: {
unstable_newEmbeddedAuthStrategy: true,
+ removeRest: true,
},
...(process.env.SHOP_CUSTOM_DOMAIN
? { customShopDomains: [process.env.SHOP_CUSTOM_DOMAIN] }
diff --git a/app/shopify.server.ts b/app/shopify.server.ts
deleted file mode 100644
index ec980711..00000000
--- a/app/shopify.server.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-import "@shopify/shopify-app-remix/adapters/node";
-import {
- ApiVersion,
- AppDistribution,
- shopifyApp,
-} from "@shopify/shopify-app-remix/server";
-import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
-import prisma from "./db.server";
-
-const shopify = shopifyApp({
- apiKey: process.env.SHOPIFY_API_KEY,
- apiSecretKey: process.env.SHOPIFY_API_SECRET || "",
- apiVersion: ApiVersion.October24,
- scopes: process.env.SCOPES?.split(","),
- appUrl: process.env.SHOPIFY_APP_URL || "",
- authPathPrefix: "/auth",
- sessionStorage: new PrismaSessionStorage(prisma),
- distribution: AppDistribution.AppStore,
- future: {
- unstable_newEmbeddedAuthStrategy: true,
- removeRest: true,
- },
- ...(process.env.SHOP_CUSTOM_DOMAIN
- ? { customShopDomains: [process.env.SHOP_CUSTOM_DOMAIN] }
- : {}),
-});
-
-export default shopify;
-export const apiVersion = ApiVersion.October24;
-export const addDocumentResponseHeaders = shopify.addDocumentResponseHeaders;
-export const authenticate = shopify.authenticate;
-export const unauthenticated = shopify.unauthenticated;
-export const login = shopify.login;
-export const registerWebhooks = shopify.registerWebhooks;
-export const sessionStorage = shopify.sessionStorage;
diff --git a/vite.config.js b/vite.config.js
index 204a574d..16fd5997 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -54,7 +54,7 @@ export default defineConfig({
v3_relativeSplatPath: true,
v3_throwAbortReason: true,
v3_lazyRouteDiscovery: true,
- v3_singleFetch: true,
+ v3_singleFetch: false,
v3_routeConfig: true,
},
}),
diff --git a/vite.config.ts b/vite.config.ts
deleted file mode 100644
index 82142f42..00000000
--- a/vite.config.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { vitePlugin as remix } from "@remix-run/dev";
-import { installGlobals } from "@remix-run/node";
-import { defineConfig, type UserConfig } from "vite";
-import tsconfigPaths from "vite-tsconfig-paths";
-
-installGlobals({ nativeFetch: true });
-
-// Related: https://github.com/remix-run/remix/issues/2835#issuecomment-1144102176
-// Replace the HOST env var with SHOPIFY_APP_URL so that it doesn't break the remix server. The CLI will eventually
-// stop passing in HOST, so we can remove this workaround after the next major release.
-if (
- process.env.HOST &&
- (!process.env.SHOPIFY_APP_URL ||
- process.env.SHOPIFY_APP_URL === process.env.HOST)
-) {
- process.env.SHOPIFY_APP_URL = process.env.HOST;
- delete process.env.HOST;
-}
-
-const host = new URL(process.env.SHOPIFY_APP_URL || "http://localhost")
- .hostname;
-
-let hmrConfig;
-if (host === "localhost") {
- hmrConfig = {
- protocol: "ws",
- host: "localhost",
- port: 64999,
- clientPort: 64999,
- };
-} else {
- hmrConfig = {
- protocol: "wss",
- host: host,
- port: parseInt(process.env.FRONTEND_PORT!) || 8002,
- clientPort: 443,
- };
-}
-
-export default defineConfig({
- server: {
- port: Number(process.env.PORT || 3000),
- hmr: hmrConfig,
- fs: {
- // See https://vitejs.dev/config/server-options.html#server-fs-allow for more information
- allow: ["app", "node_modules"],
- },
- },
- plugins: [
- remix({
- ignoredRouteFiles: ["**/.*"],
- future: {
- v3_fetcherPersist: true,
- v3_relativeSplatPath: true,
- v3_throwAbortReason: true,
- v3_lazyRouteDiscovery: true,
- v3_singleFetch: false,
- v3_routeConfig: true,
- },
- }),
- tsconfigPaths(),
- ],
- build: {
- assetsInlineLimit: 0,
- },
-}) satisfies UserConfig;