({
+ config() {
+ return {
+ links: [createHttpBatchLink(appBridgeInstance)],
+ queryClientConfig: { defaultOptions: { queries: { refetchOnWindowFocus: false } } },
+ };
+ },
+ ssr: false,
+});
diff --git a/apps/orders-export/src/modules/trpc/trpc-server.ts b/apps/orders-export/src/modules/trpc/trpc-server.ts
new file mode 100644
index 0000000..9109e3b
--- /dev/null
+++ b/apps/orders-export/src/modules/trpc/trpc-server.ts
@@ -0,0 +1,31 @@
+import { initTRPC } from "@trpc/server";
+import { TrpcContext } from "@saleor/trpc";
+import { Permission } from "@saleor/app-sdk/types";
+import { ZodError } from "zod";
+
+interface Meta {
+ requiredClientPermissions?: Permission[];
+ updateWebhooks?: boolean;
+}
+
+const t = initTRPC
+ .context()
+ .meta()
+ .create({
+ errorFormatter({ shape, error }) {
+ return {
+ ...shape,
+ data: {
+ ...shape.data,
+ zodError:
+ error.code === "BAD_REQUEST" && error.cause instanceof ZodError
+ ? error.cause.flatten()
+ : null,
+ },
+ };
+ },
+ });
+
+export const router = t.router;
+export const procedure = t.procedure;
+export const middleware = t.middleware;
diff --git a/apps/segment/src/modules/ui/app-header.tsx b/apps/orders-export/src/modules/ui/app-header.tsx
similarity index 100%
rename from apps/segment/src/modules/ui/app-header.tsx
rename to apps/orders-export/src/modules/ui/app-header.tsx
diff --git a/apps/orders-export/src/pages/_app.tsx b/apps/orders-export/src/pages/_app.tsx
new file mode 100644
index 0000000..798623c
--- /dev/null
+++ b/apps/orders-export/src/pages/_app.tsx
@@ -0,0 +1,45 @@
+import "@saleor/macaw-ui/next/style";
+import { trpcClient } from "@/modules/trpc/trpc-client";
+import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge";
+import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
+import { GraphQLProvider, NoSSRWrapper, ThemeSynchronizer } from "@saleor/apps-shared";
+import { Box, ThemeProvider } from "@saleor/macaw-ui/next";
+
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { AppProps } from "next/app";
+
+/**
+ * Ensure instance is a singleton.
+ * TODO: This is React 18 issue, consider hiding this workaround inside app-sdk
+ */
+export const appBridgeInstance = typeof window !== "undefined" ? new AppBridge() : undefined;
+
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ refetchOnWindowFocus: false,
+ },
+ },
+});
+
+function NextApp({ Component, pageProps }: AppProps) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default trpcClient.withTRPC(NextApp);
diff --git a/apps/orders-export/src/pages/_document.tsx b/apps/orders-export/src/pages/_document.tsx
new file mode 100644
index 0000000..b2fff8b
--- /dev/null
+++ b/apps/orders-export/src/pages/_document.tsx
@@ -0,0 +1,13 @@
+import { Html, Head, Main, NextScript } from "next/document";
+
+export default function Document() {
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/orders-export/src/pages/api/manifest.ts b/apps/orders-export/src/pages/api/manifest.ts
new file mode 100644
index 0000000..4c9f121
--- /dev/null
+++ b/apps/orders-export/src/pages/api/manifest.ts
@@ -0,0 +1,45 @@
+import { createManifestHandler } from "@saleor/app-sdk/handlers/next";
+import { AppManifest } from "@saleor/app-sdk/types";
+
+import packageJson from "../../../package.json";
+
+export default createManifestHandler({
+ async manifestFactory({ appBaseUrl }) {
+ const iframeBaseUrl = process.env.APP_IFRAME_BASE_URL ?? appBaseUrl;
+ const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
+
+ const manifest: AppManifest = {
+ about: "Export Saleor orders to spreadsheets",
+ appUrl: iframeBaseUrl,
+ author: "Saleor Commerce",
+ /*
+ * brand: {
+ * logo: {
+ * default: `${apiBaseURL}/logo.png`,
+ * },
+ * },
+ */
+ dataPrivacyUrl: "https://saleor.io/legal/privacy/",
+ extensions: [
+ /**
+ * Optionally, extend Dashboard with custom UIs
+ * https://docs.saleor.io/docs/3.x/developer/extending/apps/extending-dashboard-with-apps
+ */
+ ],
+ homepageUrl: "https://github.com/saleor/apps",
+ id: "saleor.app.orders-export",
+ name: "Orders Export",
+ permissions: ["MANAGE_ORDERS"],
+ requiredSaleorVersion: ">=3.14 <4",
+ supportUrl: "https://github.com/saleor/apps/discussions",
+ tokenTargetUrl: `${apiBaseURL}/api/register`,
+ version: packageJson.version,
+ /*
+ * TODO Add webhooks disabled and enable then when configured
+ */
+ webhooks: [],
+ };
+
+ return manifest;
+ },
+});
diff --git a/apps/orders-export/src/pages/api/register.ts b/apps/orders-export/src/pages/api/register.ts
new file mode 100644
index 0000000..ec1b0df
--- /dev/null
+++ b/apps/orders-export/src/pages/api/register.ts
@@ -0,0 +1,23 @@
+import { saleorApp } from "@/saleor-app";
+import { createAppRegisterHandler } from "@saleor/app-sdk/handlers/next";
+
+const allowedUrlsPattern = process.env.ALLOWED_DOMAIN_PATTERN;
+
+/**
+ * Required endpoint, called by Saleor to install app.
+ * It will exchange tokens with app, so saleorApp.apl will contain token
+ */
+export default createAppRegisterHandler({
+ apl: saleorApp.apl,
+ allowedSaleorUrls: [
+ (url) => {
+ if (allowedUrlsPattern) {
+ const regex = new RegExp(allowedUrlsPattern);
+
+ return regex.test(url);
+ }
+
+ return true;
+ },
+ ],
+});
diff --git a/apps/orders-export/src/pages/api/trpc/[trpc].ts b/apps/orders-export/src/pages/api/trpc/[trpc].ts
new file mode 100644
index 0000000..7b764a2
--- /dev/null
+++ b/apps/orders-export/src/pages/api/trpc/[trpc].ts
@@ -0,0 +1,9 @@
+import * as trpcNext from "@trpc/server/adapters/next";
+
+import { appRouter } from "../../../modules/trpc/trpc-app-router";
+import { createTrpcContext } from "@saleor/trpc";
+
+export default trpcNext.createNextApiHandler({
+ router: appRouter,
+ createContext: createTrpcContext,
+});
diff --git a/apps/orders-export/src/pages/configuration.tsx b/apps/orders-export/src/pages/configuration.tsx
new file mode 100644
index 0000000..4485a86
--- /dev/null
+++ b/apps/orders-export/src/pages/configuration.tsx
@@ -0,0 +1,28 @@
+import { AppHeader } from "@/modules/ui/app-header";
+import { useAppBridge } from "@saleor/app-sdk/app-bridge";
+import { Layout } from "@saleor/apps-ui";
+import { Box, Text } from "@saleor/macaw-ui/next";
+import { NextPage } from "next";
+
+const ConfigurationPage: NextPage = () => {
+ const { appBridgeState } = useAppBridge();
+
+ if (!appBridgeState) {
+ return null;
+ }
+
+ if (appBridgeState.user?.permissions.includes("MANAGE_APPS") === false) {
+ return You do not have permission to access this page.;
+ }
+
+ return (
+
+
+
+ todo
+
+
+ );
+};
+
+export default ConfigurationPage;
diff --git a/apps/orders-export/src/pages/index.tsx b/apps/orders-export/src/pages/index.tsx
new file mode 100644
index 0000000..554f156
--- /dev/null
+++ b/apps/orders-export/src/pages/index.tsx
@@ -0,0 +1,30 @@
+import { NextPage } from "next";
+import { useAppBridge } from "@saleor/app-sdk/app-bridge";
+import { useEffect } from "react";
+import { useIsMounted } from "usehooks-ts";
+import { useRouter } from "next/router";
+import { isInIframe } from "@saleor/apps-shared";
+
+const IndexPage: NextPage = () => {
+ const { appBridgeState } = useAppBridge();
+ const isMounted = useIsMounted();
+ const { replace } = useRouter();
+
+ useEffect(() => {
+ if (isMounted() && appBridgeState?.ready) {
+ replace("/configuration");
+ }
+ }, [isMounted, appBridgeState?.ready, replace]);
+
+ if (isInIframe()) {
+ return Loading
;
+ }
+
+ return (
+
+
Saleor App - Orders Export
+
+ );
+};
+
+export default IndexPage;
diff --git a/apps/orders-export/src/saleor-app.ts b/apps/orders-export/src/saleor-app.ts
new file mode 100644
index 0000000..67fb20d
--- /dev/null
+++ b/apps/orders-export/src/saleor-app.ts
@@ -0,0 +1,35 @@
+import { APL, FileAPL, SaleorCloudAPL, UpstashAPL } from "@saleor/app-sdk/APL";
+import { SaleorApp } from "@saleor/app-sdk/saleor-app";
+
+const aplType = process.env.APL ?? "file";
+
+export let apl: APL;
+
+switch (aplType) {
+ case "upstash":
+ apl = new UpstashAPL();
+
+ break;
+ case "file":
+ apl = new FileAPL();
+
+ break;
+ case "saleor-cloud": {
+ if (!process.env.REST_APL_ENDPOINT || !process.env.REST_APL_TOKEN) {
+ throw new Error("Rest APL is not configured - missing env variables. Check saleor-app.ts");
+ }
+
+ apl = new SaleorCloudAPL({
+ resourceUrl: process.env.REST_APL_ENDPOINT,
+ token: process.env.REST_APL_TOKEN,
+ });
+
+ break;
+ }
+ default: {
+ throw new Error("Invalid APL config, ");
+ }
+}
+export const saleorApp = new SaleorApp({
+ apl,
+});
diff --git a/apps/orders-export/src/setup-tests.ts b/apps/orders-export/src/setup-tests.ts
new file mode 100644
index 0000000..cb0ff5c
--- /dev/null
+++ b/apps/orders-export/src/setup-tests.ts
@@ -0,0 +1 @@
+export {};
diff --git a/apps/orders-export/tsconfig.json b/apps/orders-export/tsconfig.json
new file mode 100644
index 0000000..61c19ab
--- /dev/null
+++ b/apps/orders-export/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
+ "exclude": ["node_modules"]
+}
diff --git a/apps/orders-export/turbo.json b/apps/orders-export/turbo.json
new file mode 100644
index 0000000..c08adae
--- /dev/null
+++ b/apps/orders-export/turbo.json
@@ -0,0 +1,28 @@
+{
+ "extends": ["//"],
+ "$schema": "https://turbo.build/schema.json",
+ "pipeline": {
+ "build": {
+ "env": [
+ "APL",
+ "APP_DEBUG",
+ "NODE_ENV",
+ "SECRET_KEY",
+ "ALLOWED_DOMAIN_PATTERN",
+ "REST_APL_ENDPOINT",
+ "REST_APL_TOKEN",
+ "NEXT_PUBLIC_VERCEL_ENV",
+ "VERCEL_URL",
+ "PORT",
+ "SENTRY_ORG",
+ "SENTRY_PROJECT",
+ "SENTRY_DSN",
+ "SENTRY_AUTH_TOKEN",
+ "NEXT_PUBLIC_SENTRY_DSN",
+ "SENTRY_ENVIRONMENT",
+ "APP_IFRAME_BASE_URL",
+ "APP_API_BASE_URL"
+ ]
+ }
+ }
+}
diff --git a/apps/orders-export/vitest.config.ts b/apps/orders-export/vitest.config.ts
new file mode 100644
index 0000000..2f87470
--- /dev/null
+++ b/apps/orders-export/vitest.config.ts
@@ -0,0 +1,17 @@
+import react from "@vitejs/plugin-react";
+
+import { defineConfig } from "vitest/config";
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ test: {
+ passWithNoTests: true,
+ environment: "jsdom",
+ setupFiles: "./src/setup-tests.ts",
+ css: false,
+ alias: {
+ "@": "./src",
+ },
+ },
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 781c002..8131bea 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,5 +1,9 @@
lockfileVersion: '6.0'
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
overrides:
'@saleor/app-sdk': 0.43.1
@@ -896,6 +900,145 @@ importers:
specifier: 5.1.6
version: 5.1.6
+ apps/orders-export:
+ dependencies:
+ '@hookform/resolvers':
+ specifier: ^3.1.0
+ version: 3.1.0(react-hook-form@7.44.3)
+ '@saleor/app-sdk':
+ specifier: 0.43.1
+ version: 0.43.1(graphql@16.7.1)(next@13.4.8)(react-dom@18.2.0)(react@18.2.0)
+ '@saleor/apps-shared':
+ specifier: workspace:*
+ version: link:../../packages/shared
+ '@saleor/apps-ui':
+ specifier: workspace:*
+ version: link:../../packages/ui
+ '@saleor/macaw-ui':
+ specifier: 0.8.0-pre.127
+ version: 0.8.0-pre.127(@types/react-dom@18.2.5)(@types/react@18.2.5)(react-dom@18.2.0)(react@18.2.0)
+ '@saleor/react-hook-form-macaw':
+ specifier: workspace:*
+ version: link:../../packages/react-hook-form-macaw
+ '@saleor/trpc':
+ specifier: workspace:*
+ version: link:../../packages/trpc
+ '@sentry/nextjs':
+ specifier: 7.67.0
+ version: 7.67.0(next@13.4.8)(react@18.2.0)
+ '@tanstack/react-query':
+ specifier: ^4.29.19
+ version: 4.29.19(react-dom@18.2.0)(react@18.2.0)
+ '@trpc/client':
+ specifier: 10.38.1
+ version: 10.38.1(@trpc/server@10.38.1)
+ '@trpc/next':
+ specifier: 10.38.1
+ version: 10.38.1(@tanstack/react-query@4.29.19)(@trpc/client@10.38.1)(@trpc/react-query@10.38.1)(@trpc/server@10.38.1)(next@13.4.8)(react-dom@18.2.0)(react@18.2.0)
+ '@trpc/react-query':
+ specifier: 10.38.1
+ version: 10.38.1(@tanstack/react-query@4.29.19)(@trpc/client@10.38.1)(@trpc/server@10.38.1)(react-dom@18.2.0)(react@18.2.0)
+ '@trpc/server':
+ specifier: 10.38.1
+ version: 10.38.1
+ '@urql/exchange-auth':
+ specifier: ^2.1.4
+ version: 2.1.4(graphql@16.7.1)
+ '@vitejs/plugin-react':
+ specifier: 4.0.4
+ version: 4.0.4(vite@4.4.8)
+ graphql:
+ specifier: 16.7.1
+ version: 16.7.1
+ graphql-tag:
+ specifier: ^2.12.6
+ version: 2.12.6(graphql@16.7.1)
+ jsdom:
+ specifier: ^20.0.3
+ version: 20.0.3
+ next:
+ specifier: 13.4.8
+ version: 13.4.8(@babel/core@7.22.11)(react-dom@18.2.0)(react@18.2.0)
+ pino:
+ specifier: ^8.14.1
+ version: 8.14.1
+ pino-pretty:
+ specifier: ^10.0.0
+ version: 10.0.0
+ react:
+ specifier: 18.2.0
+ version: 18.2.0
+ react-dom:
+ specifier: 18.2.0
+ version: 18.2.0(react@18.2.0)
+ react-error-boundary:
+ specifier: 4.0.10
+ version: 4.0.10(react@18.2.0)
+ react-hook-form:
+ specifier: ^7.43.9
+ version: 7.44.3(react@18.2.0)
+ urql:
+ specifier: ^4.0.4
+ version: 4.0.4(graphql@16.7.1)(react@18.2.0)
+ usehooks-ts:
+ specifier: ^2.9.1
+ version: 2.9.1(react-dom@18.2.0)(react@18.2.0)
+ vite:
+ specifier: 4.4.8
+ version: 4.4.8(@types/node@18.15.3)
+ vitest:
+ specifier: 0.34.1
+ version: 0.34.1(jsdom@20.0.3)
+ zod:
+ specifier: 3.21.4
+ version: 3.21.4
+ devDependencies:
+ '@graphql-codegen/cli':
+ specifier: 4.0.1
+ version: 4.0.1(@babel/core@7.22.11)(@types/node@18.15.3)(graphql@16.7.1)
+ '@graphql-codegen/introspection':
+ specifier: 4.0.0
+ version: 4.0.0(graphql@16.7.1)
+ '@graphql-codegen/typed-document-node':
+ specifier: 5.0.1
+ version: 5.0.1(graphql@16.7.1)
+ '@graphql-codegen/typescript':
+ specifier: 4.0.1
+ version: 4.0.1(graphql@16.7.1)
+ '@graphql-codegen/typescript-operations':
+ specifier: 4.0.1
+ version: 4.0.1(graphql@16.7.1)
+ '@graphql-codegen/typescript-urql':
+ specifier: 3.7.3
+ version: 3.7.3(graphql-tag@2.12.6)(graphql@16.7.1)
+ '@graphql-typed-document-node/core':
+ specifier: 3.2.0
+ version: 3.2.0(graphql@16.7.1)
+ '@testing-library/react':
+ specifier: ^14.0.0
+ version: 14.0.0(react-dom@18.2.0)(react@18.2.0)
+ '@testing-library/react-hooks':
+ specifier: ^8.0.1
+ version: 8.0.1(@types/react@18.2.5)(react-dom@18.2.0)(react@18.2.0)
+ '@types/react':
+ specifier: 18.2.5
+ version: 18.2.5
+ '@types/react-dom':
+ specifier: 18.2.5
+ version: 18.2.5
+ eslint:
+ specifier: 8.46.0
+ version: 8.46.0
+ eslint-config-saleor:
+ specifier: workspace:*
+ version: link:../../packages/eslint-config-saleor
+ node-mocks-http:
+ specifier: ^1.12.2
+ version: 1.12.2
+ typescript:
+ specifier: 5.1.6
+ version: 5.1.6
+
apps/products-feed:
dependencies:
'@aws-sdk/client-s3':
@@ -5782,6 +5925,7 @@ packages:
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.13.11
+ dev: true
/@babel/template@7.22.5:
resolution: {integrity: sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==}
@@ -12876,6 +13020,7 @@ packages:
/chevrotain@6.5.0:
resolution: {integrity: sha512-BwqQ/AgmKJ8jcMEjaSnfMybnKMgGTrtDKowfTP3pX4jwVy0kNjRsT/AP6h+wC3+3NC+X8X15VWBnTCQlX+wQFg==}
+ requiresBuild: true
dependencies:
regexp-to-ast: 0.4.0
dev: false
@@ -15738,7 +15883,7 @@ packages:
graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
dependencies:
graphql: 16.7.1
- tslib: 2.6.1
+ tslib: 2.6.2
/graphql-ws@5.14.0(graphql@16.7.1):
resolution: {integrity: sha512-itrUTQZP/TgswR4GSSYuwWUzrE/w5GhbwM2GX3ic2U7aw33jgEsayfIlvaj7/GcIvZgNMzsPTrE5hqPuFUiE5g==}
@@ -19455,7 +19600,7 @@ packages:
peerDependencies:
react: '>=16.13.1'
dependencies:
- '@babel/runtime': 7.22.6
+ '@babel/runtime': 7.22.11
react: 18.2.0
dev: false
@@ -19948,6 +20093,7 @@ packages:
/regexp-to-ast@0.4.0:
resolution: {integrity: sha512-4qf/7IsIKfSNHQXSwial1IFmfM1Cc/whNBQqRwe0V2stPe7KmN1U0tWQiIx6JiirgSrisjE0eECdNf7Tav1Ntw==}
+ requiresBuild: true
dev: false
optional: true
@@ -21181,6 +21327,7 @@ packages:
/tiny-emitter@2.1.0:
resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==}
+ requiresBuild: true
dev: false
optional: true
@@ -21342,6 +21489,7 @@ packages:
/tslib@2.6.1:
resolution: {integrity: sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==}
+ dev: false
/tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
@@ -21697,6 +21845,7 @@ packages:
/unorm@1.6.0:
resolution: {integrity: sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==}
engines: {node: '>= 0.4.0'}
+ requiresBuild: true
dev: false
optional: true
@@ -22615,7 +22764,3 @@ packages:
/zod@3.21.4:
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
-
-settings:
- autoInstallPeers: true
- excludeLinksFromLockfile: false